fix(transport) : upload décharge différé à l'enregistrement/validation (évite les orphelins) (ERP-171)

This commit is contained in:
2026-06-17 15:22:25 +02:00
parent 1d5110d000
commit 7668d77c78
4 changed files with 99 additions and 20 deletions
@@ -68,7 +68,9 @@ export function useCarrierForm() {
const mainErrors = useFormErrors()
// Upload de la décharge (RG-4.02) — infra partagée /api/uploaded_documents.
// L'upload est DIFFÉRÉ : le fichier choisi attend ici jusqu'à l'enregistrement.
const { uploading: dischargeUploading, upload: uploadFile } = useUpload()
const pendingDischargeFile = ref<File | null>(null)
// ── État du transporteur créé ─────────────────────────────────────────────
const carrierId = ref<number | null>(null)
@@ -144,8 +146,9 @@ export function useCarrierForm() {
valid = false
}
// RG-4.02 : décharge obligatoire si certification AUTRE.
if (main.certificationType === 'AUTRE' && !main.dischargeDocumentIri) {
// RG-4.02 : décharge obligatoire si certification AUTRE — satisfaite par un
// IRI déjà posé OU un fichier en attente d'upload (différé à l'enregistrement).
if (main.certificationType === 'AUTRE' && !main.dischargeDocumentIri && !pendingDischargeFile.value) {
mainErrors.setError('dischargeDocument', t('transport.carriers.form.errors.dischargeRequired'))
valid = false
}
@@ -170,20 +173,41 @@ export function useCarrierForm() {
}
/**
* Upload de la décharge (RG-4.02) déclenché par `@file-selected` du champ
* Décharge : envoie le fichier, pose l'IRI résultant sur le brouillon. Au 422
* (MIME hors whitelist / taille), le message back s'affiche sous le champ
* (pas de toast) ; l'IRI est remis à null pour bloquer la validation.
* Sélection de la décharge (RG-4.02) via `@file-selected` : le fichier est mis
* EN ATTENTE, l'upload réel est DIFFÉRÉ à l'enregistrement (`submitMain` /
* `updateMain`). Évite les binaires orphelins si l'utilisateur abandonne le
* formulaire après avoir choisi un fichier.
*/
async function uploadDischarge(file: File): Promise<void> {
function selectDischarge(file: File): void {
mainErrors.clearError('dischargeDocument')
pendingDischargeFile.value = file
}
/** Annulation du choix de décharge : oublie le fichier en attente et l'IRI. */
function clearDischarge(): void {
pendingDischargeFile.value = null
main.dischargeDocumentIri = null
}
/**
* Résout l'upload différé au moment de l'enregistrement : s'il y a un fichier
* en attente, l'envoie (POST /uploaded_documents) et pose l'IRI sur le
* brouillon. Retourne false au 422 (MIME / taille → message inline) pour
* interrompre la sauvegarde du transporteur. Pas de fichier en attente → no-op.
*/
async function resolveDischargeUpload(): Promise<boolean> {
if (!pendingDischargeFile.value) {
return true
}
try {
main.dischargeDocumentIri = await uploadFile(file)
main.dischargeDocumentIri = await uploadFile(pendingDischargeFile.value)
pendingDischargeFile.value = null
return true
} catch (error) {
main.dischargeDocumentIri = null
const message = extractApiErrorMessage((error as { data?: unknown })?.data)
|| t('transport.carriers.form.errors.uploadFailed')
mainErrors.setError('dischargeDocument', message)
return false
}
}
@@ -249,6 +273,9 @@ export function useCarrierForm() {
mainSubmitting.value = true
try {
// Upload différé de la décharge : envoyé seulement maintenant (au Valider).
if (!(await resolveDischargeUpload())) return false
const created = await api.post<CarrierMainResponse>('/carriers', buildMainPayload(), {
headers: { Accept: 'application/ld+json' },
toast: false,
@@ -299,6 +326,9 @@ export function useCarrierForm() {
mainSubmitting.value = true
try {
// Upload différé de la décharge : envoyé seulement maintenant (à l'Enregistrer).
if (!(await resolveDischargeUpload())) return false
const updated = await api.patch<CarrierMainResponse>(
`/carriers/${carrierId.value}`,
buildMainPayload(),
@@ -791,7 +821,8 @@ export function useCarrierForm() {
removePrice,
submitPrices,
// actions
uploadDischarge,
selectDischarge,
clearDischarge,
validateMainFront,
buildMainPayload,
submitMain,