feat : cycle de vie brouillon/validé du ticket de pesée (ERP-193)
Une pesée (bascule ou manuelle) s'enregistre désormais dès la validation de sa
modale, sans exiger la contrepartie ni l'immatriculation : le ticket naît
« brouillon » (status DRAFT, sans numéro). Le bouton « Valider » finalise quand
les 3 champs du haut (contrepartie + champ associé + immatriculation) ET les 2
pesées sont renseignés : attribution du numéro {siteCode}-TP-{NNNN} et passage
en VALIDATED, puis ouverture du bon de pesée PDF.
Back : counterparty_type/immatriculation/number nullables + colonne status
(migration racine), contraintes strictes déplacées en groupe de validation
finalize, opération PATCH /weighing_tickets/{id}/validate, numéro attribué à la
validation. Front : 4 champs en haut hors blocs, persistance immédiate des
pesées, écrans Ajouter/Modifier refondus, colonne Statut dans la liste, form à
plat pleine largeur. Tests back (lifecycle brouillon/validate) + front à jour.
This commit is contained in:
@@ -11,12 +11,13 @@ import type { WeighbridgeMode } from '~/modules/logistique/composables/useWeighb
|
||||
* - **Contrepartie conditionnelle (RG-5.03)** : `counterpartyType` (CLIENT /
|
||||
* FOURNISSEUR / AUTRE) pilote le champ requis (client / supplier / otherLabel).
|
||||
* Changer de type purge les champs des autres types — aucune donnée fantôme.
|
||||
* - **Immatriculation + « Tout format » partagés entre les 2 blocs (RG-5.01)** :
|
||||
* une seule valeur (refs uniques) — modifier l'un met à jour l'autre puisque
|
||||
* les 2 blocs bindent la même ref.
|
||||
* - **Workflow 2 temps** : `buildCreatePayload()` (POST à l'« Enregistrer » du
|
||||
* bloc vide) crée le ticket avec la pesée à vide ; `buildFullPayload()` (PATCH
|
||||
* au « Valider ») ajoute la pesée à plein (net recalculé serveur, RG-5.05).
|
||||
* - **Immatriculation + « Tout format »** font partie des 4 champs du haut, hors
|
||||
* blocs (ERP-193). Une seule valeur, partagée entre les 2 pesées (RG-5.01).
|
||||
* - **Cycle brouillon -> validé (ERP-193)** : `buildDraftPayload()` persiste l'état
|
||||
* courant (pesée enregistrée dès la validation de sa modale, même sans
|
||||
* contrepartie/immat) via POST (création du brouillon) puis PATCH ; quand les 3
|
||||
* champs du haut + les 2 pesées sont là, `buildValidatePayload()` finalise via
|
||||
* `PATCH /weighing_tickets/{id}/validate` (numéro attribué, status VALIDATED).
|
||||
*
|
||||
* Composable UI-agnostique et testable : aucune dépendance API ici (les appels
|
||||
* vivent dans l'écran via `useApi`). Instancié PAR écran (refs locales).
|
||||
@@ -39,10 +40,14 @@ export interface WeighingBlockState {
|
||||
manualNumber: string | null
|
||||
}
|
||||
|
||||
/** Cycle de vie du ticket (miroir back, ERP-193). */
|
||||
export type WeighingTicketStatus = 'DRAFT' | 'VALIDATED'
|
||||
|
||||
/** Forme minimale d'un détail de ticket consommée par `hydrate` (cf. useWeighingTicket). */
|
||||
export interface WeighingTicketHydration {
|
||||
id: number
|
||||
counterpartyType: CounterpartyType
|
||||
status?: WeighingTicketStatus
|
||||
counterpartyType?: CounterpartyType | null
|
||||
client?: { '@id': string } | null
|
||||
supplier?: { '@id': string } | null
|
||||
otherLabel?: string | null
|
||||
@@ -124,9 +129,13 @@ export function useWeighingTicketForm() {
|
||||
const empty = reactive<WeighingBlockState>(emptyBlock(now))
|
||||
const full = reactive<WeighingBlockState>(emptyBlock(now))
|
||||
|
||||
// Id du ticket créé (POST du bloc vide) — pilote le PATCH du bloc plein.
|
||||
// Id du ticket persisté (POST du 1er enregistrement de pesée) — pilote ensuite
|
||||
// les PATCH (brouillon) puis la validation. Null tant que rien n'est persisté.
|
||||
const ticketId = ref<number | null>(null)
|
||||
|
||||
// Cycle de vie courant (DRAFT tant que non validé, ERP-193).
|
||||
const status = ref<WeighingTicketStatus>('DRAFT')
|
||||
|
||||
/**
|
||||
* Champ de contrepartie attendu selon le type courant — utilisé par l'écran
|
||||
* pour afficher conditionnellement le bon champ (RG-5.03).
|
||||
@@ -183,22 +192,35 @@ export function useWeighingTicketForm() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload de CRÉATION (POST /weighing_tickets, spec-back § 4.3) : contrepartie
|
||||
* + véhicule + pesée à VIDE. Le numéro, le site et le net sont attribués
|
||||
* serveur (rien à envoyer). Les noms de champs miroir des `propertyPath` back
|
||||
* pour que `useFormErrors` mappe les 422 inline.
|
||||
* Champs d'un bloc de pesée, UNIQUEMENT s'il a été pesé (poids renseigné) — on
|
||||
* n'envoie pas la date par défaut d'un bloc vierge (sinon le back stockerait une
|
||||
* date de pesée sans poids). Noms de clés alignés sur les `propertyPath` back.
|
||||
*/
|
||||
function buildCreatePayload(): Record<string, unknown> {
|
||||
function blockPayload(prefix: 'empty' | 'full', block: WeighingBlockState): Record<string, unknown> {
|
||||
if (block.weight === null) return {}
|
||||
return {
|
||||
[`${prefix}Date`]: block.date,
|
||||
[`${prefix}Weight`]: block.weight,
|
||||
[`${prefix}Dsd`]: block.dsd,
|
||||
[`${prefix}Mode`]: block.mode,
|
||||
[`${prefix}ManualNumber`]: block.manualNumber || null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload de BROUILLON (POST création / PATCH mise à jour, ERP-193) : l'état
|
||||
* courant complet (4 champs du haut + pesées effectuées). Aucun champ n'est
|
||||
* requis ici (le back valide en mode relâché) — une pesée s'enregistre sans
|
||||
* contrepartie ni immatriculation. Numéro/site/net attribués serveur.
|
||||
*/
|
||||
function buildDraftPayload(): Record<string, unknown> {
|
||||
return compact({
|
||||
counterpartyType: counterpartyType.value,
|
||||
...counterpartyPayload(),
|
||||
immatriculation: immatriculation.value || null,
|
||||
plateFreeFormat: plateFreeFormat.value,
|
||||
emptyDate: empty.date || null,
|
||||
emptyWeight: empty.weight,
|
||||
emptyDsd: empty.dsd,
|
||||
emptyMode: empty.mode,
|
||||
emptyManualNumber: empty.manualNumber || null,
|
||||
...blockPayload('empty', empty),
|
||||
...blockPayload('full', full),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -211,6 +233,7 @@ export function useWeighingTicketForm() {
|
||||
*/
|
||||
function hydrate(detail: WeighingTicketHydration): void {
|
||||
ticketId.value = detail.id
|
||||
status.value = detail.status ?? 'DRAFT'
|
||||
counterpartyType.value = detail.counterpartyType ?? null
|
||||
clientIri.value = detail.client?.['@id'] ?? null
|
||||
supplierIri.value = detail.supplier?.['@id'] ?? null
|
||||
@@ -232,30 +255,18 @@ export function useWeighingTicketForm() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload de MODIFICATION (PATCH /weighing_tickets/{id}, ERP-190) : tous les
|
||||
* champs éditables (contrepartie + véhicule + les 2 pesées). Le numéro et le
|
||||
* site sont immuables (RG-5.09, ignorés par le back même si envoyés). Le net
|
||||
* est recalculé serveur (RG-5.05).
|
||||
* Payload de VALIDATION (PATCH /weighing_tickets/{id}/validate, ERP-193) : les
|
||||
* 4 champs du haut (contrepartie + immatriculation + « Tout format »). Les pesées
|
||||
* sont déjà persistées par les enregistrements brouillon ; le back rejoue ici la
|
||||
* validation stricte (groupe `finalize` : 3 champs requis + 2 pesées) et attribue
|
||||
* le numéro. Les `propertyPath` des 422 sont mappés inline par useFormErrors.
|
||||
*/
|
||||
function buildUpdatePayload(): Record<string, unknown> {
|
||||
return { ...buildCreatePayload(), ...buildFullPayload() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload de FINALISATION (PATCH /weighing_tickets/{id}, spec-back § 4.4) :
|
||||
* pesée à PLEIN. Le véhicule (immat / tout format) peut avoir été ajusté entre
|
||||
* les 2 blocs → on le repousse aussi (valeur partagée, RG-5.01). Le net est
|
||||
* recalculé serveur (RG-5.05).
|
||||
*/
|
||||
function buildFullPayload(): Record<string, unknown> {
|
||||
function buildValidatePayload(): Record<string, unknown> {
|
||||
return compact({
|
||||
counterpartyType: counterpartyType.value,
|
||||
...counterpartyPayload(),
|
||||
immatriculation: immatriculation.value || null,
|
||||
plateFreeFormat: plateFreeFormat.value,
|
||||
fullDate: full.date || null,
|
||||
fullWeight: full.weight,
|
||||
fullDsd: full.dsd,
|
||||
fullMode: full.mode,
|
||||
fullManualNumber: full.manualNumber || null,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -277,9 +288,9 @@ export function useWeighingTicketForm() {
|
||||
missingWeighingFields,
|
||||
// workflow
|
||||
ticketId,
|
||||
status,
|
||||
hydrate,
|
||||
buildCreatePayload,
|
||||
buildFullPayload,
|
||||
buildUpdatePayload,
|
||||
buildDraftPayload,
|
||||
buildValidatePayload,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user