fix(transport) : bloc prix par défaut (CLIENT), sens seul en ligne 1, payload omet scalaires vides (422 inline au lieu de 400) (ERP-169)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m0s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m26s

This commit is contained in:
2026-06-17 10:24:32 +02:00
parent 9ec7d78a4c
commit 49125d61b0
5 changed files with 63 additions and 46 deletions
@@ -15,46 +15,46 @@ function isFilled(value: string | null | undefined): boolean {
/**
* Payload de la sous-ressource prix (groupe `carrier:write:prices`). Envoie les
* communs + UNIQUEMENT la branche active (l'autre branche à null, exigée par les
* CHECK BDD). Les relations partent en IRI (string|null). Le back re-valide
* l'obligation conditionnelle + l'appartenance de l'adresse (422 inline).
* CHECK BDD). Les relations partent en IRI (string|null).
*
* IMPORTANT : les scalaires obligatoires (direction / containerType / pricingUnit /
* price / priceState) sont OMIS s'ils sont vides — on n'envoie JAMAIS `null` sur un
* champ string. Sinon API Platform lève un 400 « The type of the "price" attribute
* must be "string", "NULL" given. » AVANT la validation (non mappable inline). Omis,
* le champ reste null côté entité → l'Assert\NotBlank renvoie un 422 propre rattaché
* au champ, affiché sous l'input comme les autres blocs (ERP-101). Le back re-valide
* aussi l'obligation conditionnelle de branche + l'appartenance de l'adresse.
*/
export function buildCarrierPricePayload(price: CarrierPriceFormDraft): Record<string, unknown> {
const common: Record<string, unknown> = {
direction: price.direction,
containerType: price.containerType || null,
pricingUnit: price.pricingUnit || null,
price: price.price || null,
priceState: price.priceState || null,
}
const payload: Record<string, unknown> = {}
// Scalaires : présents seulement si remplis (jamais `null` → évite le 400 de type).
if (isFilled(price.direction)) payload.direction = price.direction
if (isFilled(price.containerType)) payload.containerType = price.containerType
if (isFilled(price.pricingUnit)) payload.pricingUnit = price.pricingUnit
if (isFilled(price.price)) payload.price = price.price
if (isFilled(price.priceState)) payload.priceState = price.priceState
// Branche active en IRI (null toléré sur une relation, ne déclenche pas le 400 de
// type) ; branche inactive forcée à null (CHECK BDD chk_carrier_price_*_branch).
if (price.direction === 'CLIENT') {
return {
...common,
client: price.clientIri || null,
clientDeliveryAddress: price.clientDeliveryAddressIri || null,
departureSite: price.departureSiteIri || null,
// Branche FOURNISSEUR forcée à null (CHECK chk_carrier_price_client_branch).
supplier: null,
supplierSupplyAddress: null,
deliverySite: null,
}
payload.client = price.clientIri || null
payload.clientDeliveryAddress = price.clientDeliveryAddressIri || null
payload.departureSite = price.departureSiteIri || null
payload.supplier = null
payload.supplierSupplyAddress = null
payload.deliverySite = null
}
else if (price.direction === 'FOURNISSEUR') {
payload.supplier = price.supplierIri || null
payload.supplierSupplyAddress = price.supplierSupplyAddressIri || null
payload.deliverySite = price.deliverySiteIri || null
payload.client = null
payload.clientDeliveryAddress = null
payload.departureSite = null
}
if (price.direction === 'FOURNISSEUR') {
return {
...common,
supplier: price.supplierIri || null,
supplierSupplyAddress: price.supplierSupplyAddressIri || null,
deliverySite: price.deliverySiteIri || null,
// Branche CLIENT forcée à null (CHECK chk_carrier_price_supplier_branch).
client: null,
clientDeliveryAddress: null,
departureSite: null,
}
}
// Direction non choisie : on envoie les communs ; le back 422 sur `direction`.
return common
return payload
}
/**