179 lines
7.3 KiB
TypeScript
179 lines
7.3 KiB
TypeScript
import { computed, reactive, ref } from 'vue'
|
|
import { todayIso } from '~/shared/utils/date'
|
|
import type { WeighbridgeMode } from '~/modules/logistique/composables/useWeighbridge'
|
|
|
|
/**
|
|
* État et logique du formulaire « Ajouter / Modifier un ticket de pesée » (M5,
|
|
* ERP-189). L'écran est composé de DEUX blocs empilés — pesée à vide puis pesée
|
|
* à plein — qui partagent un même véhicule.
|
|
*
|
|
* Points clés (spec-front § Écran Ajouter, spec-back § 2.4 / 2.9 / 2.10) :
|
|
* - **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).
|
|
*
|
|
* Composable UI-agnostique et testable : aucune dépendance API ici (les appels
|
|
* vivent dans l'écran via `useApi`). Instancié PAR écran (refs locales).
|
|
*/
|
|
|
|
/** Type de contrepartie — miroir de l'enum back (spec-back § 2.9). */
|
|
export type CounterpartyType = 'CLIENT' | 'FOURNISSEUR' | 'AUTRE'
|
|
|
|
/** Saisie d'une pesée (bloc vide OU bloc plein). */
|
|
export interface WeighingBlockState {
|
|
/** Date de la pesée (ISO `YYYY-MM-DD`) — jour par défaut (RG-5.07). */
|
|
date: string | null
|
|
/** Poids en kg — readonly, rempli par la pesée (bascule ou manuelle). */
|
|
weight: number | null
|
|
/** DSD — readonly, rempli par la pesée (RG-5.04). */
|
|
dsd: number | null
|
|
/** Mode de la dernière pesée appliquée au bloc. */
|
|
mode: WeighbridgeMode | null
|
|
/** Numéro de pesée (rempli uniquement en pesée manuelle). */
|
|
manualNumber: string | null
|
|
}
|
|
|
|
/** Crée l'état initial d'un bloc de pesée (date = aujourd'hui, RG-5.07). */
|
|
function emptyBlock(today: string): WeighingBlockState {
|
|
return {
|
|
date: today,
|
|
weight: null,
|
|
dsd: null,
|
|
mode: null,
|
|
manualNumber: null,
|
|
}
|
|
}
|
|
|
|
export function useWeighingTicketForm() {
|
|
const today = todayIso()
|
|
|
|
// ── Contrepartie (RG-5.03) ───────────────────────────────────────────────
|
|
const counterpartyType = ref<CounterpartyType | null>(null)
|
|
const clientIri = ref<string | null>(null)
|
|
const supplierIri = ref<string | null>(null)
|
|
const otherLabel = ref<string | null>(null)
|
|
|
|
/**
|
|
* Change le type de contrepartie et purge les champs devenus hors-sujet :
|
|
* un seul de client / supplier / otherLabel est conservé selon le type
|
|
* (RG-5.03 — pas de FK fantôme envoyée au back).
|
|
*/
|
|
function setCounterpartyType(type: CounterpartyType | null): void {
|
|
counterpartyType.value = type
|
|
if (type !== 'CLIENT') clientIri.value = null
|
|
if (type !== 'FOURNISSEUR') supplierIri.value = null
|
|
if (type !== 'AUTRE') otherLabel.value = null
|
|
}
|
|
|
|
// ── Véhicule : partagé entre les 2 blocs (RG-5.01) ────────────────────────
|
|
// Refs UNIQUES : les 2 blocs bindent la même valeur → connexion automatique.
|
|
const immatriculation = ref<string | null>(null)
|
|
const plateFreeFormat = ref<boolean>(false)
|
|
|
|
// ── Les deux pesées ───────────────────────────────────────────────────────
|
|
const empty = reactive<WeighingBlockState>(emptyBlock(today))
|
|
const full = reactive<WeighingBlockState>(emptyBlock(today))
|
|
|
|
// Id du ticket créé (POST du bloc vide) — pilote le PATCH du bloc plein.
|
|
const ticketId = ref<number | null>(null)
|
|
|
|
/**
|
|
* Champ de contrepartie attendu selon le type courant — utilisé par l'écran
|
|
* pour afficher conditionnellement le bon champ (RG-5.03).
|
|
*/
|
|
const counterpartyField = computed<'client' | 'supplier' | 'other' | null>(() => {
|
|
switch (counterpartyType.value) {
|
|
case 'CLIENT': return 'client'
|
|
case 'FOURNISSEUR': return 'supplier'
|
|
case 'AUTRE': return 'other'
|
|
default: return null
|
|
}
|
|
})
|
|
|
|
/** Applique une lecture de pesée (bascule/manuelle) à un bloc. */
|
|
function applyReading(
|
|
block: WeighingBlockState,
|
|
reading: { weight: number, dsd: number, mode: WeighbridgeMode, manualNumber?: string },
|
|
): void {
|
|
block.weight = reading.weight
|
|
block.dsd = reading.dsd
|
|
block.mode = reading.mode
|
|
block.manualNumber = reading.manualNumber ?? null
|
|
}
|
|
|
|
/** Partie « contrepartie » du payload (FK en IRI ou libellé libre). */
|
|
function counterpartyPayload(): Record<string, unknown> {
|
|
switch (counterpartyType.value) {
|
|
case 'CLIENT': return { client: clientIri.value }
|
|
case 'FOURNISSEUR': return { supplier: supplierIri.value }
|
|
case 'AUTRE': return { otherLabel: otherLabel.value || null }
|
|
default: return {}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
function buildCreatePayload(): Record<string, unknown> {
|
|
return {
|
|
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,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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> {
|
|
return {
|
|
immatriculation: immatriculation.value || null,
|
|
plateFreeFormat: plateFreeFormat.value,
|
|
fullDate: full.date || null,
|
|
fullWeight: full.weight,
|
|
fullDsd: full.dsd,
|
|
fullMode: full.mode,
|
|
fullManualNumber: full.manualNumber || null,
|
|
}
|
|
}
|
|
|
|
return {
|
|
// contrepartie
|
|
counterpartyType,
|
|
counterpartyField,
|
|
clientIri,
|
|
supplierIri,
|
|
otherLabel,
|
|
setCounterpartyType,
|
|
// véhicule partagé
|
|
immatriculation,
|
|
plateFreeFormat,
|
|
// pesées
|
|
empty,
|
|
full,
|
|
applyReading,
|
|
// workflow
|
|
ticketId,
|
|
buildCreatePayload,
|
|
buildFullPayload,
|
|
}
|
|
}
|