Skip to content

feat(25-6-b): le PDF d'une facture est figé — une pièce émise ne change plus (closes #387) - #506

Merged
guycorbaz merged 19 commits into
mainfrom
story/25-6-b-pdf-facture-archive
Oct 5, 2026
Merged

guycorbaz merged 19 commits into
mainfrom
story/25-6-b-pdf-facture-archive

Conversation

@guycorbaz

Copy link
Copy Markdown
Owner

Closes #387.

Ce qui change

Le PDF d'une facture ne change plus après son émission. Il était régénéré à chaque téléchargement, à partir des données du moment : modifier ses coordonnées, l'adresse du client ou la mise en page modifiait aussi les factures déjà envoyées, et deux téléchargements ne rendaient jamais le même fichier.

  • Figé au premier rendu : le premier téléchargement ou envoi par e-mail qui suit la validation produit le PDF, dans la langue du client. Kesh l'écrit sous KESH_DOCUMENTS_DIR et en garde l'empreinte SHA-256. Ensuite, chaque téléchargement et chaque envoi rendent ce même fichier, octet pour octet. Le gel est audité (invoice.pdf_frozen) au nom de l'acteur : utilisateur, consultation comprise, ou clé d'API.
  • Un seul chemin vers le PDF d'une facture : routes/issued_invoice_pdf.rs. L'ancienne fonction de rendu direct est retirée.
  • Gardes contre les courses : la pose est un UPDATE conditionnel (pdf_storage_path IS NULL, status = 'validated', version lue au rendu), dans une transaction avec son audit. Une seule nouvelle tentative, puis 409 INVOICE_CHANGED.
  • Intégrité : fichier absent → 410 INVOICE_PDF_GONE, dont le message nomme le fichier à restaurer. Fichier altéré → 500, rien n'est servi. Jamais de régénération silencieuse.
  • Refiger (Admin, refusé aux clés d'API) : POST /api/v1/invoices/{id}/pdf/refreeze. Il produit un nouveau document quand le fichier a disparu ; l'audit invoice.pdf_refrozen garde les deux empreintes. Il est impossible pour une facture annulée.
  • Cycle de vie : la dévalidation détache le document. Une facture annulée par un avoir garde son PDF figé, mais ne s'envoie plus.
  • Export de souveraineté : invoices.csv gagne pdf_sha256, pdf_frozen_at et pdf_language, insérés avant version.
  • Écran : bouton PDF aussi sur une facture annulée figée, mention « Document figé le », bouton « Refiger le document » pour un administrateur après un 410.
  • Manuels FR (et PDF) : section « Le PDF émis est figé » ; côté admin, le répertoire des documents doit être sauvegardé et restauré avec la base, car le .keshbackup ne contient pas les fichiers ([CR] La sauvegarde .keshbackup doit emporter les fichiers — justificatifs et PDF figés #503).

Migration 20261003000001_invoices_frozen_pdf.sql : quatre colonnes nullables et une contrainte tout-ou-rien. Elle est non breaking (ADD COLUMN nullable), sans bump de min_required.

Corrigé en route (revue)

  • Un import de l'inbox en échec supprimait un fichier archivé nommé par son contenu, qui pouvait être un PDF figé ou le justificatif d'une autre société.
  • « Trop de lignes pour le PDF » affichait { $count } brut sur les fiches facture et avoir (défaut antérieur). Les deux fiches partagent désormais shared/utils/pdf-error.ts.
  • Les messages d'erreur à argument ne portent plus les marques d'isolation invisibles de Fluent.

Revue et gates

  • Validation de la spec en 4 passes. Revue de code en 4 passes : P1 3 MEDIUM → P2 5 MEDIUM → P3 0 au-dessus de LOW → P4 ciblée 0. Prompts versionnés 25-6-b-review-prompt-p1…p4.md.
  • Mutations tuées sur toutes les gardes (base, service, écran). Les courses non reproductibles sont déclarées sans test dans la fiche.
  • Sur l'état fusionné avec main (25-6-a) : backend 2602/2602, frontend 889/889, build OK, E2E complet 233 passés, 9 échecs attendus (KF-029 ×7, KF-051 ×2 avant 12:00 UTC).

Hors périmètre

⚠️ Des conflits sont attendus avec la chaîne 25-4 (#493 et suivantes) : rounding_amount dans la liste unique des colonnes d'Invoice (invoice_columns!), et les compteurs de migrations (migrations_upgrade_path, audit d'idempotence).

🤖 Generated with Claude Code

guycorbaz and others added 19 commits October 3, 2026 19:30
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he (refs #387)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… constante, sites du manuel, cas limites (refs #387)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e (refs #387)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…garde du manuel admin, gardes structurelles (refs #387)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion P3 (refs #387)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ersion, six erreurs nommées (refs #387)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cours (refs #387)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efs #387)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Service unique issued_invoice_pdf (téléchargement et e-mail) : facture
figée → fichier relu et empreinte vérifiée ; validée non figée → rendu
dans la langue du client, écrit sous KESH_DOCUMENTS_DIR, posé par un
UPDATE conditionnel (pdf_storage_path IS NULL, status, version) avec
l'audit invoice.pdf_frozen dans la même transaction. Fichier absent →
410 INVOICE_PDF_GONE ; altéré → 500. Refigeage Admin
(POST /invoices/{id}/pdf/refreeze, audit invoice.pdf_refrozen). La
dévalidation détache. Liste unique des colonnes d'Invoice. Export CSV
(pdf_sha256, pdf_frozen_at, pdf_language). Fiche facture : bouton PDF
pour une annulée figée, mention du gel, refigeage après un 410.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…387)

Garde d'export : pdf_storage_path écartée avec son motif. Spec E2E du PDF
figé (montage : compte bancaire principal). Manuels FR (gel, refigeage,
sauvegarde du répertoire des documents) régénérés, CHANGELOG [0.12.1],
README, docs/testing.md. Gates : backend 2583/2583, frontend 853/853,
E2E 227 passés + 9 échecs attendus (KF-029 x7, KF-051 x2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (refs #387)

B1 : une facture annulée figée ne s'envoie plus par e-mail. M1 : le toast
du 410 nomme le fichier (message du serveur, pas du catalogue). M2 : la
note de l'export dit que les PDF émis n'y sont pas. L3 : l'import de
l'inbox ne supprime plus un fichier archivé partagé. Bouton de refigeage
réservé aux factures validées, retiré après un téléchargement réussi.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, refus de PDF partagé (refs #387)

Marques d'isolation Fluent retirées des messages d'erreur ; 410 d'une
facture annulée sans promesse de refigeage ; garde d'envoi tenue par le
service ; facture relue à chaque tentative ; fiche relue après refigeage.
pdfErrorMessage partagé par les fiches facture et avoir (« trop de
lignes » affichait { $count } brut, défaut antérieur). Tests : clé d'API,
langue du document, 410 d'une annulée ; manuels (HEAD, restauration).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ge, relecture isolée (refs #387)

Une facture figée entre deux tentatives est adoptée ; la garde d'envoi
vaut aussi à l'adoption. Refus du refigeage : statut avant empreinte.
Relecture de la fiche après refigeage isolée de son try. CHANGELOG
(clé d'API, HEAD, annulée, restauration), décompte de P1 corrigé.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(refs #387)

Gates complets : backend 2588/2588, frontend 862/862, E2E 229 passés +
9 échecs attendus (KF-029 x7, KF-051 x2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ure-archive

# Conflicts:
#	CHANGELOG.md
#	_bmad-output/implementation-artifacts/sprint-status.yaml
#	docs/manual/fr/user-manual.pdf
#	frontend/src/lib/shared/i18n-keys.test.ts
@guycorbaz
guycorbaz merged commit d25007c into main Oct 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Le PDF d'une facture n'est jamais archivé — il est régénéré à la volée

1 participant