/** * Composable du formulaire de creation d'un stockage (M7 — ERP-217). * * Porte l'etat du formulaire principal (a plat, PAS d'onglets — HP-M7-06), les * referentiels des selects et la soumission `POST /api/storages` avec mapping des * erreurs 422/409 inline (useFormErrors, ERP-101). Reference : ecran « Ajouter un * produit » (M6) / ecran Client. * * Referentiel des types de stockage : PLAT (RG-6.06 / decision back). Le concept * type<->site a ete retire en M6 (jointure storage_type_site droppee, migration * Version20260626100000) et `StorageType` n'a plus de relation Site ; le provider * ignore tout filtre `?siteId[]`. La cascade Site->Type de RG-7.03 n'est donc PAS * portee (decision produit du 30/06 : referentiel plat, fidele au back ; RG-7.03 a * reclarifier cote spec). On charge donc TOUS les types une fois, Site et Type * independants. * * Etat 100 % local a l'instance. */ import { reactive, ref } from 'vue' import { useSiteOptions, useStorageTypeOptions, } from '~/modules/catalog/composables/useProductOptions' /** Etats d'un stockage (miroir de l'enum back Storage::STATE_*, RG-7.04). */ export const STORAGE_STATES = ['RECEPTION', 'PRODUCTION', 'TRIAGE'] as const export function useStorageForm() { const api = useApi() const { t } = useI18n() const toast = useToast() const formErrors = useFormErrors() const sites = useSiteOptions() const storageTypes = useStorageTypeOptions() // ── Etat du formulaire ─────────────────────────────────────────────────── // Les relations (site, storageType) sont stockees en IRI (envoyees telles // quelles au POST) ; `states` porte les codes enum. const form = reactive({ siteIri: null as string | null, storageTypeIri: null as string | null, numero: null as string | null, states: [] as string[], }) const submitting = ref(false) /** Met a jour le site (select simple, RG-7.02). */ function setSite(iri: string | null): void { form.siteIri = iri } /** Met a jour le type de stockage (select simple, referentiel plat). */ function setStorageType(iri: string | null): void { form.storageTypeIri = iri } /** Met a jour les etats (multi-select, >= 1, RG-7.04). */ function setStates(states: string[]): void { form.states = states } /** * Charge les referentiels (sites + TOUS les types de stockage). Resilient : * un referentiel en echec reste vide sans casser l'autre. Pas de cascade par * site (referentiel plat, cf. docblock). */ async function loadReferentials(): Promise { await Promise.allSettled([sites.load(), storageTypes.load()]) } /** * Soumet la creation. Retourne true au succes (la page redirige), false sinon. * 422 → mapping inline par champ (useFormErrors, `{ toast: false }`) ; 409 * doublon du triplet (site, type, numero, RG-7.01) → erreur inline sur `numero` * (propertyPath exploitable cote back) + toast explicite. */ async function submit(): Promise { if (submitting.value) { return false } submitting.value = true formErrors.clearErrors() try { const payload: Record = { // Chaine vide (jamais null) : le setter back setNumero attend un // `string` non-nullable -> envoyer null leverait une erreur de type // (denormalisation) qui court-circuiterait les autres violations. // Avec '', la contrainte NotBlank renvoie un message propre par champ. numero: form.numero ?? '', states: form.states, } // `site` / `storageType` attendent un IRI (string) : envoyer null // declencherait une erreur de denormalisation API Platform qui // court-circuiterait TOUTES les autres violations. On omet la cle quand // la relation n'est pas choisie -> la contrainte NotNull renvoie un // message propre, et les autres champs sont valides dans la meme 422. if (form.siteIri) { payload.site = form.siteIri } if (form.storageTypeIri) { payload.storageType = form.storageTypeIri } const options = { headers: { Accept: 'application/ld+json' }, toast: false } await api.post('/storages', payload, options) toast.success({ title: t('admin.storages.toast.createSuccess') }) return true } catch (error) { const status = (error as { response?: { status?: number } })?.response?.status if (status === 409) { // Doublon (site, type, numero) RG-7.01 : inline sur `numero` + toast. const message = t('admin.storages.form.duplicateNumero') formErrors.setError('numero', message) toast.error({ title: t('admin.storages.toast.error'), message }) } else { formErrors.handleApiError(error, { fallbackMessage: t('admin.storages.toast.error') }) } return false } finally { submitting.value = false } } return { form, errors: formErrors.errors, submitting, siteOptions: sites.options, storageTypeOptions: storageTypes.options, setSite, setStorageType, setStates, loadReferentials, submit, } }