fix(logistique) : bloque les caractères spéciaux dans le champ « Autre »
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Failing after 1m7s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m36s

Assert\Regex(FREE_TEXT) sur otherLabel (miroir companyName/competitors) + masque
FREE_TEXT_MASK sur l'input « Autre » (new.vue / edit.vue).
This commit is contained in:
2026-06-29 10:52:02 +02:00
parent 4ce1bafb2f
commit 8a042fb578
4 changed files with 43 additions and 0 deletions
@@ -58,6 +58,7 @@
<MalioInputText
v-else-if="form.counterpartyField.value === 'other'"
:model-value="form.otherLabel.value"
:mask="FREE_TEXT_MASK"
:label="t('logistique.weighingTickets.form.counterparty.other')"
:required="true"
:error="errors.otherLabel"
@@ -193,6 +194,7 @@ import { useWeighbridge } from '~/modules/logistique/composables/useWeighbridge'
import { useWeighingTicket, type WeighingTicketDetail } from '~/modules/logistique/composables/useWeighingTicket'
import { useWeighingTicketReferentials, type RefOption } from '~/modules/logistique/composables/useWeighingTicketReferentials'
import { MANUAL_NUMERIC_MASK, PLATE_MASK, FREE_PLATE_MASK } from '~/modules/logistique/utils/weighingMasks'
import { FREE_TEXT_MASK } from '~/shared/utils/textSanitize'
import { mapViolationsToRecord } from '~/shared/utils/api'
const { t } = useI18n()
@@ -53,6 +53,7 @@
<MalioInputText
v-else-if="form.counterpartyField.value === 'other'"
:model-value="form.otherLabel.value"
:mask="FREE_TEXT_MASK"
:label="t('logistique.weighingTickets.form.counterparty.other')"
:required="true"
:error="errors.otherLabel"
@@ -182,6 +183,7 @@ import { useWeighingTicketForm, type WeighingBlockState } from '~/modules/logist
import { useWeighbridge } from '~/modules/logistique/composables/useWeighbridge'
import { useWeighingTicketReferentials, type RefOption } from '~/modules/logistique/composables/useWeighingTicketReferentials'
import { MANUAL_NUMERIC_MASK, PLATE_MASK, FREE_PLATE_MASK } from '~/modules/logistique/utils/weighingMasks'
import { FREE_TEXT_MASK } from '~/shared/utils/textSanitize'
import { mapViolationsToRecord } from '~/shared/utils/api'
const { t } = useI18n()
@@ -20,6 +20,7 @@ use App\Shared\Domain\Attribute\Auditable;
use App\Shared\Domain\Contract\BlamableInterface;
use App\Shared\Domain\Contract\TimestampableInterface;
use App\Shared\Domain\Trait\TimestampableBlamableTrait;
use App\Shared\Domain\Validation\TextInputPattern;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
@@ -226,6 +227,7 @@ class WeighingTicket implements TimestampableInterface, BlamableInterface
/** Libelle libre — requis ssi counterpartyType = AUTRE (RG-5.03). */
#[ORM\Column(name: 'other_label', length: 255, nullable: true)]
#[Assert\Length(max: 255, maxMessage: 'Le libellé ne peut pas dépasser {{ limit }} caractères.', normalizer: 'trim')]
#[Assert\Regex(pattern: TextInputPattern::FREE_TEXT, message: TextInputPattern::FREE_TEXT_MESSAGE)]
#[Groups(['weighing_ticket:read', 'weighing_ticket:write'])]
private ?string $otherLabel = null;
@@ -79,6 +79,43 @@ final class WeighingTicketLifecycleTest extends AbstractWeighingTicketApiTestCas
self::assertNull($body['counterpartyType'] ?? null);
}
public function testOtherLabelWithSpecialCharsIsRejected(): void
{
$http = $this->authManageOnSite($this->siteByCode('86'));
// Le back reste l'autorite (le masque front FREE_TEXT_MASK filtre deja a la
// frappe) : un libelle « Autre » avec des caracteres parasites -> 422 sur
// otherLabel (Assert\Regex FREE_TEXT), mappee inline cote front (ERP-101).
$response = $this->postTicket($http, [
'counterpartyType' => 'AUTRE',
'otherLabel' => 'Chantier ~#|<>{}',
'emptyDate' => '2026-06-17T09:00:00+02:00',
'emptyWeight' => 7150,
'emptyMode' => 'AUTO',
]);
self::assertResponseStatusCodeSame(422);
self::assertViolationOnPath($response, 'otherLabel');
}
public function testOtherLabelLegitimateIsAccepted(): void
{
$http = $this->authManageOnSite($this->siteByCode('86'));
// Lettres accentuees, chiffres, espaces, parentheses, °, & : tout autorise
// par FREE_TEXT (miroir des raisons sociales Client/Fournisseur).
$body = $this->postTicket($http, [
'counterpartyType' => 'AUTRE',
'otherLabel' => 'Chantier Léon (Pôle n°2) & Cie',
'emptyDate' => '2026-06-17T09:00:00+02:00',
'emptyWeight' => 7150,
'emptyMode' => 'AUTO',
])->toArray();
self::assertResponseStatusCodeSame(201);
self::assertSame('Chantier Léon (Pôle n°2) & Cie', $body['otherLabel']);
}
public function testValidateRequiresCounterparty(): void
{
$http = $this->authManageOnSite($this->siteByCode('86'));