From 9c4df1134e01032b3ca03a4fe83dd4b2a2edc278 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sat, 26 Sep 2026 23:48:05 +0200 Subject: [PATCH 01/34] =?UTF-8?q?docs(25-4):=20d=C3=A9coup=C3=A9e=20en=20a?= =?UTF-8?q?/b/c=20;=2025-4-a=20sp=C3=A9cifi=C3=A9e=20=E2=80=94=20l'avoir?= =?UTF-8?q?=20compt=C3=A9=20TTC,=20refus=C3=A9=20sur=20une=20facture=20r?= =?UTF-8?q?=C3=A9gl=C3=A9e=20(refs=20#455,=20refs=20#456,=20refs=20#416,?= =?UTF-8?q?=20refs=20#420)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-residuel-juste.md | 308 ++++++++++++++++++ .../25-4-propager-le-residuel.md | 87 +++++ .../sprint-status.yaml | 6 +- 3 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 _bmad-output/implementation-artifacts/25-4-a-residuel-juste.md create mode 100644 _bmad-output/implementation-artifacts/25-4-propager-le-residuel.md diff --git a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md new file mode 100644 index 00000000..88396a44 --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md @@ -0,0 +1,308 @@ +# Story 25.4-a : Le résiduel juste — l'avoir compté TTC, et refusé sur une facture réglée + +Status: ready-for-dev + +**Issues : [#455], [#456]**, qu'elle **ferme** : `closes #455, closes #456` dans le **titre ET le +corps** de la PR (squash ; un `refs` partout laisserait les issues ouvertes sans signal). + +**Mère : `25-4-propager-le-residuel.md`** (`split`) — l'inventaire vérifié et la raison du +découpage y sont. **Sœurs à venir** : 25-4-b (#416, agrégats et relances), 25-4-c (#420, +rapprochement), qui **réemploient la formule** que cette story corrige : ⛔ elle passe en premier. + +## Story + +En tant que comptable, +je veux que le montant « reste dû » d'une facture soit calculé dans la même unité que ce que le +grand livre a passé, et qu'un avoir ne puisse pas viser une facture déjà réglée en partie, +afin que le reste dû ne mente jamais — ni en gardant la TVA d'une facture créditée, ni en laissant +une créance devenir créditrice sans que rien ne le signale. + +## Le défaut, vérifié dans le code + +### #455 — l'avoir est soustrait HORS TAXES d'un total TTC + +`amount_due` (`crates/kesh-db/src/repositories/invoice_settlements.rs:131-145`) calcule +`TTC − avoir émis − Σ règlements` : + +- le TTC vient de `INVOICE_TTC_SUBQUERY_SQL` (`invoices.rs:161-162`) : + `Σ (line_total + ROUND(line_total × vat_rate / 100, 2))` sur `invoice_lines` ; +- l'avoir vient de `INVOICE_CREDITED_SUBQUERY_SQL` (`invoice_settlements.rs:51-52`) : + `SUM(cn.total_amount)` — et `credit_notes.total_amount` est **HT** + (`20260627000001_credit_notes.sql:19` : « `total_amount` stocké = HT (Σ line_total) » ; + `credit_notes.rs:514-529`, `.bind(total_ht)`). + +Or l'écriture de l'avoir crédite la créance du **TTC** (`generate_credit_note_journal_lines`, +`credit_notes.rs:217-223` : `credit: total_ht + total_vat`, la TVA arrondie **ligne par ligne** par +`kesh_core::accounting::vat::line_vat_amount`, `:194` et `:209`). Le grand livre et le résiduel ne parlent +donc pas la même unité : une facture de 100.— HT à 8,1 % entièrement créditée garde **8.10** de +reste dû. La forme jointe `INVOICE_CREDITED_DERIVED_JOIN_SQL` (`:56-58`) porte le même défaut. + +⚠️ **Pourquoi aucun test ne l'a vu** : les factures des tests de règlement sont à **0 % de TVA** +(`crates/kesh-db/tests/invoice_settlement.rs:32`), où HT = TTC ; et aucun test n'associe un avoir +et `amount_due`. + +⚠️ **Latent sur les données neuves, pas sur les anciennes.** L'avoir est **total** et fait passer +la facture à `cancelled` (`credit_notes.rs:560-575`) ; le terme « avoir » n'est donc non nul que sur +une facture annulée, où le reste dû ne s'affiche que si `amountSettled > 0` +(`invoices/[id]/+page.svelte:1022-1041`) — c'est-à-dire **exactement l'état que #456 permet de +produire**. Et un `.keshbackup` de la v0.12.0 **peut contenir** cet état : l'import le restaure. + +### #456 — un avoir peut viser une facture partiellement réglée + +`create_credit_note` (`credit_notes.rs:292-308`) refuse une facture `paid_at IS NOT NULL` (garde +AC2bis) — **et rien d'autre**. Depuis la 24-2, `paid_at` n'est posé qu'**au solde** : une facture +réglée en partie a une ligne `invoice_settlements`, un reste dû positif, `paid_at` à `NULL` — elle +passe. + +Conséquence comptable : la créance reçoit **D TTC** (vente), **C réglé** (règlement), **C TTC** +(avoir) — elle finit **créditrice du montant encaissé**, sans flux de remboursement. C'est le solde +contre nature que la garde AC2bis voulait empêcher, atteint par un chemin qu'elle ne voit pas. La +facture passe ensuite à `cancelled`, et la ligne de règlement **survit** sur une facture annulée — +où `settlement_cancel_blocker` la déclare inannulable (`SettlementCancelBlocker::InvoiceCredited`, +`invoice_settlements_write.rs:327-333`). + +⛔ **Le patron de la bonne garde existe déjà** : la dévalidation lit « **l'existence d'une ligne** +`invoice_settlements` **OU** `paid_at IS NOT NULL` — les deux, jamais l'une seule » +(`UnvalidationBlocker::Settled`, `errors.rs:134-160` ; `invoices.rs:1430-1448`). + +⚠️ **Un test encode le défaut comme attendu** : la fixture `monter` +(`crates/kesh-db/tests/invoice_settlement.rs:937-990`) règle 40 sur 100 puis appelle +`create_credit_note` avec `.expect("avoir après règlement partiel — le vrai chemin l'accepte")`. + +⚠️ **Le frontend affiche le bouton sur une facture partiellement réglée** : +`invoices/[id]/+page.svelte:836` n'exige que `!invoice.paidAt`. Le bouton voisin « Dévalider » a +retiré cette condition pour cette raison même (commentaire `:865`) ; « Créer un avoir » n'a pas été +aligné. + +## Acceptance Criteria + +### Volet 1 — l'avoir compté TTC (#455) + +**AC 1** — `INVOICE_CREDITED_SUBQUERY_SQL` rend le **TTC** de l'avoir émis : +`Σ (cl.line_total + ROUND(cl.line_total × cl.vat_rate / 100, 2))` sur `credit_note_lines cl` jointe à +`credit_notes cn` (`cn.id = cl.credit_note_id`), filtrée `cn.invoice_id = i.id AND +cn.status = 'issued'`, `COALESCE(…, 0)`. **Même formule d'arrondi, ligne par ligne**, que +`INVOICE_TTC_SUBQUERY_SQL` et que `line_vat_amount` : c'est ce qui la fait concorder avec +l'écriture. + +**AC 2** — `INVOICE_CREDITED_DERIVED_JOIN_SQL` rend la même grandeur sous forme de table dérivée +(alias `cnt`, colonne `credited`), agrégée par `invoice_id`. + +**AC 3** — Les doc-comments des deux constantes disent **l'unité** (TTC, arrondi ligne par ligne) et +**pourquoi pas `credit_notes.total_amount`** (HT, miroir de `invoices.total_amount`) ; celui +d'`amount_due` dit que ses trois termes sont TTC. + +**AC 4** — ⛔ **Aucune migration, aucune colonne.** Le résiduel se **calcule** (24-2, D3) ; stocker +un TTC d'avoir recréerait un chiffre qui peut diverger. `credit_notes.total_amount` reste HT et +n'est pas renommé. + +**AC 5** — **DRY** : la formule « TTC d'une ligne » (`line_total + ROUND(line_total * vat_rate / 100, +2)`) n'est pas recopiée une troisième et une quatrième fois à la main. Elle est factorisée en **une +seule source** dont dérivent les quatre constantes (TTC facture scalaire et jointe, avoir scalaire +et joint) — par exemple une macro `macro_rules!` rendant un littéral via `concat!`, les constantes +restant des `&'static str`. Si la factorisation s'avère impossible sans perdre le caractère +`const`, le test de parité de l'AC 6 en tient lieu **et le doc-comment le dit**. + +**AC 6** — **Le test de parité promis par `invoice_settlements.rs:27-29`, et qui n'existe pas**, +est écrit : pour un jeu de factures qui couvre **sans règlement / un règlement / deux règlements / +avoir émis / avoir brouillon / deux taux de TVA dont 8,1 %**, la forme scalaire et la forme jointe +rendent **la même valeur, facture par facture**, pour le réglé (`st.settled`) **et** pour l'avoir +(`cnt.credited`). ⚠️ Patron : `crates/kesh-db/tests/invoice_ttc_parity.rs`. + +**AC 7** — ⛔ **Concordance avec le grand livre** : pour une facture multi-taux (dont 8,1 %) +créditée, le montant d'avoir que rend la sous-requête est **égal au crédit porté au compte de +créance par l'écriture de l'avoir** (`credit_notes.journal_entry_id` → ligne de crédit du compte +`default_receivable_account_id`). C'est l'assertion qui aurait attrapé #455 ; une comparaison au +seul HT ou à une valeur recalculée en Rust **ne suffit pas**. + +**AC 8** — Une facture validée à 8,1 %, **non réglée, créditée** : `amount_due` = **0** (et non la +TVA). Une facture validée à 8,1 %, **réglée de 40 puis créditée** (état **monté en SQL**, cf. +AC 13) : `amount_due` = **−40**, le montant encaissé en trop sur une vente annulée — et non +`TVA − 40`. ⚠️ **Pas d'écrêtage à zéro** : le doc-comment d'`amount_due` l'interdit déjà. + +### Volet 2 — l'avoir refusé sur une facture réglée (#456) + +**AC 9** — `create_credit_note` refuse une facture qui porte **au moins une ligne +`invoice_settlements` OU** `paid_at IS NOT NULL` — **les deux, jamais l'une seule**, comme +`UnvalidationBlocker::Settled`. La lecture des règlements se fait **après** le verrou de la facture +(`FOR UPDATE`, première instruction de la transaction, `credit_notes.rs:280-289`) et **dans** la +transaction ; elle est **verrouillante** (`… FOR UPDATE` ou `LOCK IN SHARE MODE`). ⛔ Leçon de la +25-3-b : sous REPEATABLE READ, une lecture non verrouillante fige l'instantané — ne pas l'ajouter +**avant** le verrou. + +**AC 10** — Le refus est une **variante dédiée** de `DbError`, pas `IllegalStateTransition` — même +motif que `InvoiceNotUnvalidatable` (`errors.rs:119-130`) : le générique ne dit ni ce qui bloque ni +quoi faire. Elle porte l'identifiant du premier règlement trouvé (`Option`, `None` si seul +`paid_at` est posé) et le numéro de la facture. Code machine **`CREDIT_NOTE_INVOICE_SETTLED`**, +HTTP **409**, ajouté à `DbError::code()` (`errors.rs:640-670`) et à toute garde qui énumère les +codes. + +**AC 11** — **Le cas « payée » passe par la même variante** : AC2bis cesse de rendre le générique. +Une seule garde, un seul message, qu'elle soit déclenchée par `paid_at` ou par une ligne de +règlement. + +**AC 12** — Message, **quatre locales**, clé **`error-credit-note-blocked-settled`**, mapping dans +`kesh-api/src/errors.rs` sur le patron de `error-invoice-unvalidate-blocked-settled` +(`:2528-2530`) : fr « Cette facture porte un règlement, même partiel : annulez-le d'abord pour +pouvoir émettre un avoir. » Registre du glossaire (`docs/i18n-glossaire.md` § Registre : de = +*Sie*, it = 2ᵉ personne du singulier, en neutre). `details` : `{ "invoiceId", "settlementId" }`. + +**AC 13** — La fixture `monter` (`invoice_settlement.rs:937-990`) **cesse de passer par +`create_credit_note`** pour le motif `InvoiceCredited` : elle monte l'état **en SQL** (avoir +`issued` inséré, facture `cancelled`), avec un commentaire qui dit que l'état n'est **plus +atteignable par l'application** depuis 25-4-a, mais **reste possible** par l'import d'une +sauvegarde antérieure — ce qui justifie de garder `SettlementCancelBlocker::InvoiceCredited`. +⛔ Ne **pas** supprimer ce motif ni ses tests. + +**AC 14** — Frontend : le bouton « Créer un avoir » (`invoices/[id]/+page.svelte:836`) est masqué +quand `invoice.paidAt` est posé **ou** `Number(invoice.amountSettled) > 0` — même condition que le +serveur. Si la course fait arriver le refus malgré tout, le dialogue affiche le message du serveur +(`err.message`, `:98`), déjà traduit : rien à ajouter côté client. + +**AC 15** — Rien n'est **réparé** dans les données existantes : pas de migration, pas de rejeu +(`CLAUDE.md` : aucune donnée de production à protéger ; précédent 24-2 D7). Les états hérités +restent **visibles** — reste dû négatif sur la fiche, motif `InvoiceCredited` à l'annulation du +règlement — et c'est ce que les textes disent (AC 17). + +### Volet 3 — tests, textes + +**AC 16** — Tests, au minimum (noms indicatifs) : + +| Test | Prouve | +|---|---| +| `credited_amount_is_ttc_and_matches_the_ledger` | AC 1, AC 7 — facture multi-taux, avoir émis : sous-requête = crédit créance de l'écriture d'avoir | +| `amount_due_of_a_credited_invoice_is_zero` | AC 8 — 8,1 %, non réglée : `0`, pas `8.10` | +| `amount_due_after_settlement_then_legacy_credit_is_minus_settled` | AC 8 — état hérité monté en SQL : `−40` | +| `settled_and_credited_forms_are_at_parity` | AC 6 | +| `credit_note_refused_on_partially_settled_invoice` | AC 9, AC 10 — **rien écrit** : aucun avoir, facture `validated`, `version` inchangée, solde de créance inchangé, séquence d'avoir non consommée | +| `credit_note_refused_on_paid_invoice` (**existant, réécrit**) | AC 11 — nouvelle variante, plus `IllegalStateTransition` | +| `credit_note_accepted_after_the_settlement_is_cancelled` | la promesse du message : annuler le règlement rouvre l'avoir | +| `credit_note_waits_for_a_concurrent_settlement` | AC 9 — **entrelacement** : une transaction verrouille la facture et insère un règlement ; `create_credit_note` lancé en parallèle attend, puis **refuse** après le commit. Patron : la sonde d'entrelacement de la 25-3-b | +| e2e API `credit_note_on_partially_settled_invoice_is_409` | la frontière HTTP : 409, `CREDIT_NOTE_INVOICE_SETTLED`, message fr | +| Vitest fiche facture | AC 14 — bouton absent si `amountSettled > 0`, présent sinon | + +⛔ **Chaque test porte au moins une facture à TVA non nulle.** À 0 %, HT = TTC et #455 est +invisible — c'est ainsi qu'il est passé. + +⛔ **Mutations à exécuter et à consigner** (résultat **observé**, pas supposé) : (m1) revenir à +`SUM(cn.total_amount)` dans la forme scalaire ; (m2) idem dans la forme jointe seule ; (m3) retirer +la lecture des règlements de la garde (ne garder que `paid_at`) ; (m4) retirer `paid_at` (ne garder +que la ligne) ; (m5) lire les règlements **avant** le verrou, non verrouillant ; (m6) remettre +`!invoice.paidAt` seul sur le bouton. Chacune doit faire rougir au moins un test. + +**AC 17** — Manuels et notes (`docs/manual/fr/user-manual.tex`, PDF régénéré et **contrôlé +aplati**) : +- `:1040` et `:2073` : « un avoir est refusé sur une facture **marquée payée** » devient « … sur une + facture **réglée, même en partie** » — annuler d'abord le règlement ; +- § *Avoirs* (`\label{sec:avoirs}`, `:1007`) : le refus et son motif sont nommés à côté du refus + « compte de produit archivé » ; +- `:963` et `:1455` (« la facture a été créditée par un avoir… ») : précisés comme **cas hérité** + — une facture créditée avant la v0.12.1, ou restaurée d'une sauvegarde antérieure ; +- `CHANGELOG.md` `[0.12.1]` *Fixed* : les deux défauts, en langage d'utilisateur. + +⚠️ **Greper le symptôme, pas la phrase** : `grep -rniE "payée|encaissée|réglée" docs/manual/fr/*.tex` +restreint aux lignes qui parlent d'avoir, plus le PDF aplati. Les manuels DE/IT/EN sont des stubs. + +## Tasks / Subtasks + +- [ ] **T1 — formule de l'avoir TTC** (AC 1-5) : factoriser la formule de ligne, réécrire les deux + constantes d'avoir, doc-comments. +- [ ] **T2 — parité et concordance** (AC 6-8) : nouveau fichier de test sur le patron + `invoice_ttc_parity.rs` ; montage de l'état hérité en SQL. +- [ ] **T3 — garde de l'avoir** (AC 9-11) : lecture verrouillante des règlements après le verrou, + variante `DbError`, `code()`. +- [ ] **T4 — API et i18n** (AC 12) : mapping, clé ×4 ; e2e 409. +- [ ] **T5 — fixture `monter`** (AC 13) : état monté en SQL, commentaire ; les tests d'annulation de + règlement restent verts. +- [ ] **T6 — frontend** (AC 14) : condition du bouton, Vitest. +- [ ] **T7 — tests et mutations** (AC 16) : m1 à m6 exécutées, résultats consignés. +- [ ] **T8 — textes** (AC 17) : manuel, PDF aplati, CHANGELOG. +- [ ] **T9 — gates** : backend complet (base remise à zéro), frontend complet, E2E complet. + +## Dev Notes + +### Ce qu'il ne faut pas faire + +- ⛔ **Ne pas « corriger » `credit_notes.total_amount` en TTC.** Il est HT par construction, miroir + de `invoices.total_amount`, et d'autres lecteurs le consomment comme tel (export, PDF, liste des + avoirs). Le défaut est dans **le lecteur**, pas dans la colonne. +- ⛔ **Ne pas calculer le TTC de l'avoir en Rust** puis le soustraire : `amount_due` doit rester + **une requête**, et la forme jointe est destinée aux agrégats de 25-4-b (pas de N+1). +- ⛔ **Ne pas supprimer `SettlementCancelBlocker::InvoiceCredited`** : l'import d'un `.keshbackup` + v0.12.0 peut ramener l'état qu'il décrit. +- ⚠️ **Ne pas lire `cn.total_amount` × taux** : un avoir multi-taux n'a pas un taux, et l'arrondi + doit être **par ligne** pour concorder avec l'écriture (`line_vat_amount`). + +### Où regarder + +| Fichier | Rôle | +|---|---| +| `crates/kesh-db/src/repositories/invoice_settlements.rs:24-58, 122-145` | les quatre constantes, `amount_due` | +| `crates/kesh-db/src/repositories/invoices.rs:155-190` | `INVOICE_TTC_SUBQUERY_SQL` / `…_DERIVED_JOIN_SQL`, `total_ttc` | +| `crates/kesh-db/src/repositories/credit_notes.rs:187-257, 260-330, 510-575` | écriture de l'avoir, garde AC2bis, `total_amount`, bascule `cancelled` | +| `crates/kesh-db/src/errors.rs:119-175, 300-470, 640-670` | `UnvalidationBlocker`, `DbError`, `code()` | +| `crates/kesh-db/src/repositories/invoices.rs:1430-1448` | la garde de dévalidation, patron de l'AC 9 | +| `crates/kesh-api/src/errors.rs:2515-2560, 2741-2748` | mapping `InvoiceNotUnvalidatable`, générique | +| `crates/kesh-api/src/routes/credit_notes.rs:183-205` | handler de création | +| `crates/kesh-db/tests/invoice_ttc_parity.rs` | patron du test de parité | +| `crates/kesh-db/tests/credit_notes_repository.rs:267-299` | `credit_note_refused_on_paid_invoice` | +| `crates/kesh-db/tests/invoice_settlement.rs:32, 116-125, 937-990` | helper à 0 %, `solde()`, `monter` | +| `frontend/src/routes/(app)/invoices/[id]/+page.svelte:90-104, 836, 865, 1022-1041` | dialogue d'avoir, bouton, précédent « Dévalider », reste dû | + +### Courses + +Les trois chemins qui écrivent un règlement ou un avoir, et ce qui les sérialise : + +| Chemin | Verrou sur la facture | +|---|---| +| `settle_invoice` (`invoice_settlements_write.rs:60-72`) | `FOR UPDATE`, première lecture | +| `create_credit_note` (`credit_notes.rs:280-289`) | `FOR UPDATE`, première lecture | +| `accept_one_invoice` (`kesh-api/src/routes/reconciliation.rs`) | **aucun** — lecture simple, puis `UPDATE invoices … AND version = ? AND status = 'validated'` (`:1490-1500`) | + +Le troisième se sérialise par **verrou optimiste** : si l'avoir passe d'abord, la facture est +`cancelled` et l'`UPDATE` du rapprochement n'affecte aucune ligne. Si le rapprochement passe +d'abord, son `UPDATE` tient le verrou de ligne jusqu'au commit ; `create_credit_note` l'attend sur +son `FOR UPDATE`, puis **doit** voir le règlement — d'où l'exigence d'une lecture verrouillante +(AC 9). ⚠️ Vérifier en dev que l'échec de l'`UPDATE` du rapprochement sur 0 ligne rend bien un +`FailedProposal` et annule tout (test existant ou à ajouter). + +### Gardes-fous du dépôt + +- **Aucune migration** : P1-P8 ne s'arment pas. Si une migration devient nécessaire, arrêter et + demander. +- **Gate complet** : la story touche des repositories — § *Exception `kesh-db`* : gate complet même + en cours de boucle de revue. +- **i18n** : une clé ×4 côté serveur ; `sitesTotal` ne bouge que si le frontend ajoute un + `i18nMsg(` — AC 14 n'en ajoute pas. + +### References + +- 24-2, D3 (le résiduel se calcule) et D4 (`paid_at` projection du solde) — + `24-2-encaissement-client.md:120-160`. +- 25-3-b, leçon REPEATABLE READ — mémoire `etat-epic-25`. +- Glossaire et registre — `docs/i18n-glossaire.md`. + +## Questions pour Guy + +Aucune qui bloque cette story. La question de la QR-facture jointe aux relances (Q1 de la mère) +bloque **25-4-b**, pas celle-ci. + +## Dev Agent Record + +### Agent Model Used + +### Debug Log References + +### Completion Notes List + +### File List + +## Change Log + +- **2026-09-26** — Créée (Opus 5.5) après découpage de la 25-4. Inventaire vérifié par `grep -nF` + sur chaque site cité. Deux faits hors des issues : #455 est **latent** sur les données neuves et + ne se voit que dans l'état que #456 produit — d'où leur réunion ; et la fixture `monter` encode + #456 comme attendu. + +[#455]: https://github.com/guycorbaz/kesh/issues/455 +[#456]: https://github.com/guycorbaz/kesh/issues/456 diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md new file mode 100644 index 00000000..6da60b67 --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -0,0 +1,87 @@ +# Story 25.4 : Propager le résiduel — découpée en 25-4-a / 25-4-b / 25-4-c + +Status: split + +**Issues : [#416], [#420]** (fiche de l'epic) — **et [#455], [#456]**, rattachées ici le 2026-09-26 : +les quatre portent sur **la même grandeur**, le montant restant dû d'une facture client, et les +quatre sont au jalon « Vague 1 », que l'Epic 25 doit mettre à zéro. + +## Pourquoi découper — § *Règle de splitting préventif* + +Inventaire des sites fait le 2026-09-26 (trois lectures du code, chaque site revérifié par +`grep -nF`, cf. § ci-dessous). Les quatre issues réunies touchent **six modules** : `kesh-db` +(résiduel, avoirs, rapprochement, échéancier), `kesh-report` (balance âgée), +`kesh-reconciliation` (score), `kesh-api` (routes factures, rapprochement, relances, erreurs), +`kesh-i18n`, `frontend` (fiche facture, échéancier, dialogue de règlement). Le seuil est **5** : la +règle impose le découpage **avant** `bmad-create-story`. + +Le découpage suit l'ordre des dépendances, non celui des issues : + +| Story | Issues | Ce qu'elle fait | Pourquoi à ce rang | +|---|---|---|---| +| **25-4-a** — le résiduel juste | [#455], [#456] | la formule canonique soustrait l'avoir **TTC** ; un avoir est refusé sur une facture **réglée, même en partie** ; le test de parité promis et jamais écrit | ⛔ **les deux suivantes réemploient la formule** : la propager avant de la corriger propagerait le défaut | +| **25-4-b** — le résiduel aux agrégats | [#416] | balance âgée, totaux de l'échéancier, **colonne « reste dû »** de l'échéancier et de son CSV, dialogue de règlement pré-rempli, **montant réclamé par les relances** | le cœur de #416 ; première utilisatrice des formes jointes | +| **25-4-c** — le résiduel au rapprochement | [#420] | filtre des candidats, score, re-score à l'acceptation, montant affiché dans la proposition | indépendante de 25-4-b, mais du même socle | + +## Ce que l'inventaire a trouvé, et que les issues ne disaient pas + +1. ⛔ **#420 a DEUX sites, et le premier est avant le score.** `find_unpaid_invoices_for_window` + (`crates/kesh-db/src/repositories/reconciliation.rs:115`) filtre les candidats par + `HAVING total_ttc BETWEEN ? - ? AND ? + ?` : le virement du solde d'une facture partiellement + réglée n'est **même pas candidat**. Corriger `amount_score` seul (`matching.rs:68`) ne changerait + rien. Troisième site : le re-score à l'acceptation, `reconciliation.rs:1196` + (`invoices::total_ttc`). +2. ⛔ **Les relances réclament le TTC complet.** `render_reminder` + (`crates/kesh-api/src/routes/invoice_email.rs:334`) : `total_due = ttc + other_fees + level_fee`. + Une facture de 1 000.— réglée à 900.— est relancée pour 1 000.— plus les frais. Aucune issue ne le + nomme ; c'est **le même symptôme** que #416 — la grandeur agrégée n'est pas le résiduel —, sur un + document **envoyé au client**. Rattaché à 25-4-b. +3. ⚠️ **La relance joint le PDF de la facture**, dont la QR-facture porte le TTC + (`invoice_email.rs:1140-1166`, `invoice_pdf_service::render`). Un client qui paie la relance en + scannant la QR paie **à nouveau le montant entier**. Question posée à Guy (Q1 ci-dessous). +4. ⚠️ **#455 est latent sur les données neuves.** Un avoir est **total** et fait passer la facture à + `cancelled` (`credit_notes.rs:560-563`) ; or les agrégats, le rapprochement et le règlement ne + lisent que des factures `validated`. Le terme « avoir » du résiduel n'est donc **non nul que sur + une facture annulée**, et le reste dû ne s'y affiche que si `amountSettled > 0` — c'est-à-dire + **seulement dans l'état que #456 permet de produire**. Les deux issues se corrigent ensemble. +5. ⚠️ **Les formes jointes existent et ne servent nulle part.** `INVOICE_SETTLED_DERIVED_JOIN_SQL` + et `INVOICE_CREDITED_DERIVED_JOIN_SQL` (`invoice_settlements.rs:40`, `:56`) n'ont aucun appelant, + et le « test de parité » annoncé par le commentaire des lignes 27-29 **n'existe pas**. 25-4-b sera + leur premier appelant : la parité doit être prouvée **avant**. +6. **Pourquoi aucun test n'a vu #455** : toutes les factures des tests de règlement sont à **0 % de + TVA** (`crates/kesh-db/tests/invoice_settlement.rs:32`), où HT = TTC. +7. ⚠️ **Un test encode #456 comme attendu** : la fixture `monter` + (`crates/kesh-db/tests/invoice_settlement.rs:937-990`) règle 40 sur 100 puis crée un avoir avec + `.expect("avoir après règlement partiel — le vrai chemin l'accepte")`. Il **décrit le défaut** ; + il se corrige, il ne se contourne pas. + +## Hors périmètre des trois stories + +- **L'imputation de l'écart** d'un règlement partiel (escompte, frais, perte) — [#384], 25-6. +- **Le côté fournisseur** : une facture fournisseur se règle en une fois, il n'y a pas de résiduel + partiel (#416, § Portée). +- **L'avoir partiel et le remboursement** — v0.2, inchangé. + +## Questions pour Guy + +**Q1 — la QR-facture jointe à une relance.** Elle porte le TTC complet. Sur une facture +partiellement réglée, un client qui paie par la QR paie deux fois la part déjà réglée. Trois +issues : (a) la QR de la relance porte le **reste dû** — le PDF joint n'est alors plus tout à fait +la facture envoyée à l'origine, ce qui touche #387 (l'archivage du PDF) ; (b) la relance d'une +facture partiellement réglée joint un PDF **sans montant dans la QR** (la norme le permet : le +client saisit le montant) ; (c) laisser tel quel et le dire dans le texte de la relance. Ma +recommandation : (b), qui ne ment pas et ne modifie pas la facture. **À trancher avant 25-4-b.** + +## Change Log + +- **2026-09-26** — Découpée avant spécification (§ *Règle de splitting préventif*, six modules), + sur inventaire vérifié. #455 et #456 rattachées (même grandeur, même jalon). Deux sites trouvés + hors des issues : le filtre SQL des candidats du rapprochement (#420), et le montant des relances + (rattaché à 25-4-b). 25-4-a spécifiée dans la foulée. + +[#384]: https://github.com/guycorbaz/kesh/issues/384 +[#387]: https://github.com/guycorbaz/kesh/issues/387 +[#416]: https://github.com/guycorbaz/kesh/issues/416 +[#420]: https://github.com/guycorbaz/kesh/issues/420 +[#455]: https://github.com/guycorbaz/kesh/issues/455 +[#456]: https://github.com/guycorbaz/kesh/issues/456 diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index cfbc52ae..8d962656 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -1,3 +1,4 @@ +# last_updated: 2026-09-26 (5) (25-4 DECOUPEE en a/b/c ; 25-4-a creee, #455/#456 rattachees ; #467 #468 #470 mergees.) # last_updated: 2026-09-26 (4) (**25-3-c DONE** — revue close, gates verts, non poussee.) # last_updated: 2026-09-26 (3) (25-3-c IMPLEMENTEE, en review ; revue a posteriori de la 25-5-a close dans le worktree.) # last_updated: 2026-09-26 (2) (25-3-c : Q1 tranchee — reglement detache.) @@ -352,7 +353,10 @@ development_status: 25-3-c-annuler-facture-fournisseur: done # 2026-09-26 **REVUE CLOSE EN 1 PASSE** (0 > LOW, Sonnet x3) ; non poussee ; PR `closes #454` titre ET corps, sur accord. **IMPLEMENTEE** — gates 2472/2472, 783/783, E2E 220/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 **SPEC VALIDEE en 2 passes** (3 MED -> 0 > LOW). **SPECIFIEE** — `25-3-c-annuler-facture-fournisseur.md`. Geste par le socle (autorite `SupplierPurchase`), queue commune sur l'ecriture d'ACHAT, lot en dernier rang ; payee : reglement DETACHE (colonnes remises a NULL, ecriture libre, lien garde par l'audit) — **Q1 tranchee par Guy le 2026-09-26**. Aucune migration. Prochaine etape : validate. # 2026-09-24 NEE d'un arbitrage de Guy — une facture fournisseur s'annule DANS TOUS LES CAS, sauf si l'exercice de son ecriture d'achat est CLOS (l'exercice, pas le verrou de periode) ; payee, son reglement RESTE au grand livre et REDEVIENT A LETTRER (Epic 15 : un paiement doit correspondre a une facture, au besoin creee pour lui — « comme bexio »). Reecrit `cancel`, donc absorbe [#454]. Derriere la 25-3-a. 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* - 25-4-propager-le-residuel: backlog # [#416] [#420] — la 24-2 a introduit le reglement PARTIEL ; le residuel n'est propage ni a la balance agee ni a l'echeancier, et **le score de rapprochement compare au TTC et non au residuel** : le virement du solde d'une facture partiellement reglee **score 0**. + 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (latent, visible seulement dans l'etat que #456 produit) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE, validation a lancer. + 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 a Guy : QR-facture jointe a la relance porte le TTC. + 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. + 25-4-propager-le-residuel: split # 2026-09-26 DECOUPEE (six modules > 5) en 25-4-a / 25-4-b / 25-4-c ; #455 et #456 rattachees. # [#416] [#420] — la 24-2 a introduit le reglement PARTIEL ; le residuel n'est propage ni a la balance agee ni a l'echeancier, et **le score de rapprochement compare au TTC et non au residuel** : le virement du solde d'une facture partiellement reglee **score 0**. 25-5-a-export-souverainete: done # 2026-09-26 **MERGEE (PR #452, #386 fermee) SANS REVUE DE CODE** ; revue a posteriori CLOSE en 3 passes sur `story/25-5-a-revue-code` (4 HIGH P1, 1 HIGH orchestrateur P2 — colonnes manquantes, garde de colonnes —, P3 ciblee test seul) ; gates 2466/2466, E2E 221/8/19 sans regression ; reports #465, #466. # 2026-09-23 **SPECIFIEE et VALIDEE en 1 passe** (0 CRITICAL, 4 HIGH, 3 MEDIUM, 3 LOW, tous corriges). [#386] — issue du DECOUPAGE de la 25-5 (arbitrage de Guy) : #385 (Balance vs Bilan) et #386 n'ont en commun que le mot « etat », et touchent des modules disjoints. ⛔ **Trois faits qui corrigent l'issue** : 21 tables absentes et non 10 ; le trou s'est CREUSE depuis son ecriture (`invoice_settlements`, Epic 24) ; et SIX repositories sur huit n'ont aucune lecture exhaustive — le vrai cout, absent de l'issue. Arbitrage de Guy : l'export CSV ne porte que les donnees COMPTABLES (11 dedans), les backups portent tout ; 10 exclusions AVEC motif ecrit, dont 3 tables de SECRETS. Les justificatifs (fichiers) partent en story separee sur la SAUVEGARDE. ⛔ La vraie livraison est la GARDE D'EXHAUSTIVITE (patron D4-ter + P7), non les 11 exports : trois epics ont rouvert le trou en silence. ⚠️ Pieges releves par la validation : le nombre 19 code en dur a SEPT endroits dont deux `debug_assert_eq!` qui PANIQUENT ; `audit_log` n'a AUCUN dump exhaustif (ses fonctions sont bornees, la route rejette au-dela de 10 000 lignes) ; `csv_sanitize` existe et n'est pas employee par cet export ; deux sites de manuel DEJA faux (un dossier `reports/` invente, un « 38 » pour 39). 25-5-b-balance-solde-ouverture: backlog # [#385] — la **Balance des comptes ne concorde pas avec le Bilan**, il manque la colonne « solde d'ouverture » (III.4). Detachee de la 25-5 le 2026-09-23 : voir 25-5-a. 25-5-etats-et-export: split # [#385] [#386] — la **Balance des comptes ne concorde pas avec le Bilan**, il manque la colonne « solde d'ouverture » (III.4) ; et **l'export de souverainete omet les achats, les avoirs, les projets et la piste d'audit** (V.6) — *il promet plus qu'il ne tient*. From 0f4b9ec0caf993a11f4b7d86a9c69462326d06d9 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sat, 26 Sep 2026 23:48:27 +0200 Subject: [PATCH 02/34] docs(25-4-a): prompt de validation P1 (refs #455, refs #456) Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-validate-prompt-p1.md | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 _bmad-output/implementation-artifacts/25-4-a-validate-prompt-p1.md diff --git a/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p1.md b/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p1.md new file mode 100644 index 00000000..03a3ca11 --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p1.md @@ -0,0 +1,50 @@ +# Prompt — validation P1, Story 25-4-a (le résiduel juste) + +*Versionné le 2026-09-26. **Une lentille** (Sonnet), contexte frais.* + +Dépôt `/home/gcorbaz/devel/kesh`, branche `story/25-4-propager-le-residuel`. Fiche à valider : +`_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md`. Fiche mère (inventaire, +découpage) : `25-4-propager-le-residuel.md`. Issues : `gh issue view 455`, `gh issue view 456`. +Contexte de la grandeur : `24-2-encaissement-client.md` § D3, D4, AC 5-7. Règles du dépôt : +`CLAUDE.md`. Checklist BMAD : `.claude/skills/bmad-create-story/checklist.md`. + +## Axes — tous obligatoires + +1. **Chaque référence `fichier:ligne`** existe et dit ce que la fiche affirme — relis le code. +2. **La formule** : la sous-requête TTC de l'avoir prescrite (AC 1-2) concorde-t-elle réellement, + centime par centime, avec l'écriture de l'avoir (`generate_credit_note_journal_lines`, + `line_vat_amount` — lis `kesh-core`, sens d'arrondi de `ROUND` MariaDB vs Rust) ? Et avec + `INVOICE_TTC_SUBQUERY_SQL` ? Un écart d'un centime possible est un finding. +3. **La garde** (AC 9-11) : inventorie **tous** les chemins qui créent un règlement ou un avoir + (pars du symptôme : `grep -rn "INSERT INTO invoice_settlements\|create_credit_note\|INSERT INTO credit_notes" crates/`), + et leur sérialisation (verrou pessimiste ou optimiste). La fiche en nomme trois : en manque-t-il ? + Le raisonnement REPEATABLE READ de § Courses est-il juste ? +4. **L'état hérité** : l'import d'une sauvegarde ramène-t-il vraiment l'état (règlement + avoir) ? + Les textes prescrits (AC 17) le disent-ils juste ? D'autres lecteurs de `amount_due` ou des + constantes changent-ils de valeur avec l'AC 1 (inventaire : `grep -rn "INVOICE_CREDITED\|amount_due" crates/ frontend/src`) ? +5. **Les tests** (AC 16) : chacun prouve-t-il ce qu'il annonce, ou peut-il passer à vide ? Les + mutations m1-m6 sont-elles tuables par les tests listés ? Une mutation que rien ne tuerait est + un finding. +6. **Le manuel** : les lignes citées (AC 17) existent-elles et disent-elles ce qu'on veut changer ? + Contrôle aussi le **PDF aplati** (`pdftotext docs/manual/fr/user-manual.pdf - | tr '\n' ' ' | tr -s ' '`, + vers `/tmp/claude-1000/-home-gcorbaz-devel-kesh/ca5ce2e2-67a3-4eeb-817f-c2de35620a1c/scratchpad/`). + Une autre phrase du manuel ou de l'admin-manual, de l'api-external, du README, promet-elle + l'ancien comportement ? +7. **Le découpage** (fiche mère) : quelque chose de 25-4-a appartient-il en fait à b ou c, ou + l'inverse ? La latence de #455 est-elle correctement établie ? + +## Ce que tu rends + +- **Findings** : sévérité (CRITICAL / HIGH / MEDIUM / LOW), l'endroit exact de la fiche, **la + preuve** (commande et résultat, code lu), la correction proposée. +- ⛔ **La liste des axes réellement exercés ET de ceux qui ne l'ont pas été.** Un « 0 finding » + sans elle ne compte pas. + +## Interdits + +⛔ N'écris aucun fichier du dépôt ; n'exécute aucune commande qui écrit dans le dépôt ou dans une +base — `scripts/prepare-release.sh`, `scripts/regen-test-schema.sh`, `scripts/install-hooks.sh`, +`scripts/test-fast.sh`, `scripts/mem-guard.sh`, `make`, `latexmk`, tout `git commit`/`push`/`add`/ +`stash`/`reset`/`rebase`/`checkout`/`switch`/`worktree`, `sqlx migrate`, `cargo test`/`nextest`, +`npm run`, `npx playwright`. Autorisés : lecture, `grep`, `git log`/`show`/`diff`, `gh issue view`, +`pdftotext` vers le scratchpad, `cargo check`. From 73172c8f75ff9dbfc991b29f46799b12a0bb435a Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sat, 26 Sep 2026 23:53:51 +0200 Subject: [PATCH 03/34] =?UTF-8?q?docs(25-4):=20Q1=20tranch=C3=A9e=20?= =?UTF-8?q?=E2=80=94=20QR=20sans=20montant=20sur=20une=20relance=20partiel?= =?UTF-8?q?lement=20r=C3=A9gl=C3=A9e,=20reste=20=C3=A0=20payer=20dit=20au?= =?UTF-8?q?=20client=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-propager-le-residuel.md | 17 +++++++++++++++++ .../implementation-artifacts/sprint-status.yaml | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md index 6da60b67..f651c0ef 100644 --- a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -72,8 +72,25 @@ facture partiellement réglée joint un PDF **sans montant dans la QR** (la norm client saisit le montant) ; (c) laisser tel quel et le dire dans le texte de la relance. Ma recommandation : (b), qui ne ment pas et ne modifie pas la facture. **À trancher avant 25-4-b.** +### ✅ Arbitrage du 2026-09-26 (Q1) — (b), et le reste à payer dit au client + +*« b, mais indiquer au client ce qu'il reste à payer. »* (Guy) + +Pour 25-4-b, donc : + +- la relance d'une facture **partiellement réglée** joint un PDF dont la QR-facture **ne porte pas + de montant** — le client le saisit ; une facture sans règlement garde sa QR à montant ; +- la relance **dit ce qu'il reste à payer** : `{totalDue}` devient **reste dû + frais**, et non plus + TTC + frais (`invoice_email.rs:334`) — c'est le montant que le client doit saisir. + +⚠️ **Ne pas contester l'arbitrage en revue** : en contester la mise en œuvre. Reste à établir en +spécification de 25-4-b : si le reste dû figure **aussi sur le PDF** joint (mention à côté de la +QR sans montant), ou dans le seul texte de la relance — le PDF est aujourd'hui celui de la facture, +et #387 (l'archivage du PDF envoyé) le touche. + ## Change Log +- **2026-09-26** — Q1 tranchée par Guy : QR sans montant pour une facture partiellement réglée, et le reste à payer dit au client (rattaché à 25-4-b). - **2026-09-26** — Découpée avant spécification (§ *Règle de splitting préventif*, six modules), sur inventaire vérifié. #455 et #456 rattachées (même grandeur, même jalon). Deux sites trouvés hors des issues : le filtre SQL des candidats du rapprochement (#420), et le montant des relances diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 8d962656..fdbcda3c 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -354,7 +354,7 @@ development_status: 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (latent, visible seulement dans l'etat que #456 produit) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE, validation a lancer. - 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 a Guy : QR-facture jointe a la relance porte le TTC. + 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-26 : QR sans montant si partiellement reglee, et le reste a payer dit au client. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. 25-4-propager-le-residuel: split # 2026-09-26 DECOUPEE (six modules > 5) en 25-4-a / 25-4-b / 25-4-c ; #455 et #456 rattachees. # [#416] [#420] — la 24-2 a introduit le reglement PARTIEL ; le residuel n'est propage ni a la balance agee ni a l'echeancier, et **le score de rapprochement compare au TTC et non au residuel** : le virement du solde d'une facture partiellement reglee **score 0**. 25-5-a-export-souverainete: done # 2026-09-26 **MERGEE (PR #452, #386 fermee) SANS REVUE DE CODE** ; revue a posteriori CLOSE en 3 passes sur `story/25-5-a-revue-code` (4 HIGH P1, 1 HIGH orchestrateur P2 — colonnes manquantes, garde de colonnes —, P3 ciblee test seul) ; gates 2466/2466, E2E 221/8/19 sans regression ; reports #465, #466. # 2026-09-23 **SPECIFIEE et VALIDEE en 1 passe** (0 CRITICAL, 4 HIGH, 3 MEDIUM, 3 LOW, tous corriges). [#386] — issue du DECOUPAGE de la 25-5 (arbitrage de Guy) : #385 (Balance vs Bilan) et #386 n'ont en commun que le mot « etat », et touchent des modules disjoints. ⛔ **Trois faits qui corrigent l'issue** : 21 tables absentes et non 10 ; le trou s'est CREUSE depuis son ecriture (`invoice_settlements`, Epic 24) ; et SIX repositories sur huit n'ont aucune lecture exhaustive — le vrai cout, absent de l'issue. Arbitrage de Guy : l'export CSV ne porte que les donnees COMPTABLES (11 dedans), les backups portent tout ; 10 exclusions AVEC motif ecrit, dont 3 tables de SECRETS. Les justificatifs (fichiers) partent en story separee sur la SAUVEGARDE. ⛔ La vraie livraison est la GARDE D'EXHAUSTIVITE (patron D4-ter + P7), non les 11 exports : trois epics ont rouvert le trou en silence. ⚠️ Pieges releves par la validation : le nombre 19 code en dur a SEPT endroits dont deux `debug_assert_eq!` qui PANIQUENT ; `audit_log` n'a AUCUN dump exhaustif (ses fonctions sont bornees, la route rejette au-dela de 10 000 lignes) ; `csv_sanitize` existe et n'est pas employee par cet export ; deux sites de manuel DEJA faux (un dossier `reports/` invente, un « 38 » pour 39). From 1ed731350eed377d07537833e1ff38a291065dce Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sat, 26 Sep 2026 23:57:36 +0200 Subject: [PATCH 04/34] =?UTF-8?q?docs(25-4):=20le=20reste=20=C3=A0=20payer?= =?UTF-8?q?=20figure=20aussi=20sur=20le=20PDF=20joint=20=C3=A0=20la=20rela?= =?UTF-8?q?nce=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-propager-le-residuel.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md index f651c0ef..14bb15b1 100644 --- a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -83,13 +83,16 @@ Pour 25-4-b, donc : - la relance **dit ce qu'il reste à payer** : `{totalDue}` devient **reste dû + frais**, et non plus TTC + frais (`invoice_email.rs:334`) — c'est le montant que le client doit saisir. -⚠️ **Ne pas contester l'arbitrage en revue** : en contester la mise en œuvre. Reste à établir en -spécification de 25-4-b : si le reste dû figure **aussi sur le PDF** joint (mention à côté de la -QR sans montant), ou dans le seul texte de la relance — le PDF est aujourd'hui celui de la facture, -et #387 (l'archivage du PDF envoyé) le touche. +- ✅ **Le reste à payer figure AUSSI sur le PDF joint**, à côté de la QR sans montant (Guy, + 2026-09-26) : *« sinon le client ne peut pas savoir ce qu'il reste à payer sans recherche et + calculs »*. Le PDF d'une relance de facture partiellement réglée n'est donc plus exactement la + facture d'origine — à articuler en spécification avec #387 (l'archivage du PDF envoyé). + +⚠️ **Ne pas contester l'arbitrage en revue** : en contester la mise en œuvre. ## Change Log +- **2026-09-26** — Complément de Q1 : le reste à payer figure aussi sur le PDF joint. - **2026-09-26** — Q1 tranchée par Guy : QR sans montant pour une facture partiellement réglée, et le reste à payer dit au client (rattaché à 25-4-b). - **2026-09-26** — Découpée avant spécification (§ *Règle de splitting préventif*, six modules), sur inventaire vérifié. #455 et #456 rattachées (même grandeur, même jalon). Deux sites trouvés From f28811766faecd3b6f243fb535683be39c91c152 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:00:19 +0200 Subject: [PATCH 05/34] =?UTF-8?q?docs(25-4):=20le=20document=20joint=20est?= =?UTF-8?q?=20un=20rappel=20pour=20un=20montant=20partiel,=20pas=20la=20fa?= =?UTF-8?q?cture=20r=C3=A9=C3=A9mise=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../implementation-artifacts/25-4-propager-le-residuel.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md index 14bb15b1..b94e63cd 100644 --- a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -88,10 +88,18 @@ Pour 25-4-b, donc : calculs »*. Le PDF d'une relance de facture partiellement réglée n'est donc plus exactement la facture d'origine — à articuler en spécification avec #387 (l'archivage du PDF envoyé). +- ✅ **Recadrage (Guy, 2026-09-27)** : *« en fait c'est un rappel pour un montant partiel. »* Le + document joint à une relance de facture partiellement réglée n'est pas la facture réémise : c'est + **un rappel pour le montant restant**. L'objection « le PDF n'est plus la facture d'origine » + tombe donc — ce n'est pas la facture —, et #387 ne s'en trouve pas touché. ⚠️ **Point ouvert + posé à Guy** : dès lors, la QR du rappel doit-elle porter le **reste à payer** plutôt que rester + sans montant ? + ⚠️ **Ne pas contester l'arbitrage en revue** : en contester la mise en œuvre. ## Change Log +- **2026-09-27** — Recadrage de Q1 : le document est un rappel pour un montant partiel, pas la facture réémise. - **2026-09-26** — Complément de Q1 : le reste à payer figure aussi sur le PDF joint. - **2026-09-26** — Q1 tranchée par Guy : QR sans montant pour une facture partiellement réglée, et le reste à payer dit au client (rattaché à 25-4-b). - **2026-09-26** — Découpée avant spécification (§ *Règle de splitting préventif*, six modules), From b62db3c6548a0fe5dea578db5ec1e1b75ecb2812 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:03:00 +0200 Subject: [PATCH 06/34] =?UTF-8?q?docs(25-4-a):=20validation=20P1=20?= =?UTF-8?q?=E2=80=94=20#455=20expos=C3=A9=20par=20l'API,=20bouton=20d'avoi?= =?UTF-8?q?r=20align=C3=A9=20sur=20=C2=AB=20D=C3=A9valider=20=C2=BB,=20mon?= =?UTF-8?q?tage=20de=20monter=20(refs=20#455,=20refs=20#456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-residuel-juste.md | 101 +++++++++++++----- .../25-4-propager-le-residuel.md | 4 +- .../sprint-status.yaml | 2 +- 3 files changed, 77 insertions(+), 30 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md index 88396a44..90c7c586 100644 --- a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md +++ b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md @@ -41,11 +41,17 @@ reste dû. La forme jointe `INVOICE_CREDITED_DERIVED_JOIN_SQL` (`:56-58`) porte (`crates/kesh-db/tests/invoice_settlement.rs:32`), où HT = TTC ; et aucun test n'associe un avoir et `amount_due`. -⚠️ **Latent sur les données neuves, pas sur les anciennes.** L'avoir est **total** et fait passer -la facture à `cancelled` (`credit_notes.rs:560-575`) ; le terme « avoir » n'est donc non nul que sur -une facture annulée, où le reste dû ne s'affiche que si `amountSettled > 0` -(`invoices/[id]/+page.svelte:1022-1041`) — c'est-à-dire **exactement l'état que #456 permet de -produire**. Et un `.keshbackup` de la v0.12.0 **peut contenir** cet état : l'import le restaure. +⚠️ **Masqué à l'écran, exposé par l'API.** L'avoir est **total** et fait passer la facture à +`cancelled` (`credit_notes.rs:560-575`) ; le terme « avoir » n'est donc non nul que sur une facture +annulée. **L'écran** n'y affiche le reste dû que si `amountSettled > 0` +(`invoices/[id]/+page.svelte:1022-1041`) — l'état que #456 permet de produire. ⛔ **Mais l'API le +rend toujours** : `get_invoice` (`crates/kesh-api/src/routes/invoices.rs:621-642`) calcule +`amount_due` **sans condition de statut**, et `GET /invoices/{id}` est **ouverte aux clés API en +lecture** (`docs/api-external.md:211`). Un avoir émis sur une facture **jamais réglée** — geste +ordinaire, sans #456 — suffit donc, aujourd'hui, à faire rendre à une intégration tierce +`amountDue` = **la TVA** au lieu de `0`. *(Relevé en validation P1 ; la fiche mère disait « latent » +à tort.)* Et un `.keshbackup` de la v0.12.0 **peut contenir** l'état de #456 : l'import le +restaure. ### #456 — un avoir peut viser une facture partiellement réglée @@ -69,10 +75,10 @@ où `settlement_cancel_blocker` la déclare inannulable (`SettlementCancelBlocke (`crates/kesh-db/tests/invoice_settlement.rs:937-990`) règle 40 sur 100 puis appelle `create_credit_note` avec `.expect("avoir après règlement partiel — le vrai chemin l'accepte")`. -⚠️ **Le frontend affiche le bouton sur une facture partiellement réglée** : -`invoices/[id]/+page.svelte:836` n'exige que `!invoice.paidAt`. Le bouton voisin « Dévalider » a -retiré cette condition pour cette raison même (commentaire `:865`) ; « Créer un avoir » n'a pas été -aligné. +⚠️ **Le frontend masque le bouton sur une facture payée, pas sur une facture partiellement +réglée** : `invoices/[id]/+page.svelte:836` n'exige que `!invoice.paidAt`. Le bouton voisin +« Dévalider » a **retiré** cette condition pour cette raison même (commentaire `:864-871`) et laisse +le serveur refuser avec un motif nommé ; « Créer un avoir » n'a pas été aligné. ## Acceptance Criteria @@ -116,7 +122,8 @@ créance par l'écriture de l'avoir** (`credit_notes.journal_entry_id` → ligne seul HT ou à une valeur recalculée en Rust **ne suffit pas**. **AC 8** — Une facture validée à 8,1 %, **non réglée, créditée** : `amount_due` = **0** (et non la -TVA). Une facture validée à 8,1 %, **réglée de 40 puis créditée** (état **monté en SQL**, cf. +TVA) — **au repository ET à la frontière HTTP** : `GET /api/v1/invoices/{id}` rend `amountDue` = +`"0.00"` (ou sa forme décimale normalisée), `amountSettled` = `0`. Une facture validée à 8,1 %, **réglée de 40 puis créditée** (état **monté en SQL**, cf. AC 13) : `amount_due` = **−40**, le montant encaissé en trop sur une vente annulée — et non `TVA − 40`. ⚠️ **Pas d'écrêtage à zéro** : le doc-comment d'`amount_due` l'interdit déjà. @@ -134,7 +141,7 @@ transaction ; elle est **verrouillante** (`… FOR UPDATE` ou `LOCK IN SHARE MOD motif que `InvoiceNotUnvalidatable` (`errors.rs:119-130`) : le générique ne dit ni ce qui bloque ni quoi faire. Elle porte l'identifiant du premier règlement trouvé (`Option`, `None` si seul `paid_at` est posé) et le numéro de la facture. Code machine **`CREDIT_NOTE_INVOICE_SETTLED`**, -HTTP **409**, ajouté à `DbError::code()` (`errors.rs:640-670`) et à toute garde qui énumère les +HTTP **409**, ajouté à `DbError::error_code()` (`errors.rs:641`) et à toute garde qui énumère les codes. **AC 11** — **Le cas « payée » passe par la même variante** : AC2bis cesse de rendre le générique. @@ -147,17 +154,39 @@ règlement. pouvoir émettre un avoir. » Registre du glossaire (`docs/i18n-glossaire.md` § Registre : de = *Sie*, it = 2ᵉ personne du singulier, en neutre). `details` : `{ "invoiceId", "settlementId" }`. -**AC 13** — La fixture `monter` (`invoice_settlement.rs:937-990`) **cesse de passer par -`create_credit_note`** pour le motif `InvoiceCredited` : elle monte l'état **en SQL** (avoir -`issued` inséré, facture `cancelled`), avec un commentaire qui dit que l'état n'est **plus -atteignable par l'application** depuis 25-4-a, mais **reste possible** par l'import d'une +**AC 13** — La fixture `monter` (`invoice_settlement.rs:937-1019`) cesse d'obtenir l'état +`InvoiceCredited` **par le chemin que 25-4-a ferme**, avec un commentaire qui dit que l'état n'est +**plus atteignable par l'application** depuis 25-4-a, mais **reste possible** par l'import d'une sauvegarde antérieure — ce qui justifie de garder `SettlementCancelBlocker::InvoiceCredited`. ⛔ Ne **pas** supprimer ce motif ni ses tests. -**AC 14** — Frontend : le bouton « Créer un avoir » (`invoices/[id]/+page.svelte:836`) est masqué -quand `invoice.paidAt` est posé **ou** `Number(invoice.amountSettled) > 0` — même condition que le -serveur. Si la course fait arriver le refus malgré tout, le dialogue affiche le message du serveur -(`err.message`, `:98`), déjà traduit : rien à ajouter côté client. +⚠️ **`monter` sert quinze cas, pas un** : `la_precedence_de_l_annulation_lecture_et_ecriture` +(`:1056-1064`) l'appelle avec les cinq motifs seuls **et les dix paires** (`RANGS`, +`assert_eq!(cas.len(), 15)`), dont **quatre combinent `InvoiceCredited`** avec un motif monté +**après** lui dans le même corps (clôture d'exercice, rapprochement bancaire, compte archivé, +aucun exercice ouvert). Le montage doit rester compatible avec ces quatre suites, et **les quinze +cas restent verts**. + +⚠️ **Un avoir `issued` ne s'insère pas en SQL nu** : `chk_credit_notes_issued_has_je` exige +`credit_note_number` **et** `journal_entry_id` non nuls (`20260627000001_credit_notes.sql:53-54`), +donc une écriture de contre-passation valide. **Gabarit recommandé**, qui garde les **deux +écritures produites par les vrais chemins** et ne touche en SQL qu'un rattachement : régler (vrai +chemin) ; **détacher** la ligne de règlement vers une facture auxiliaire validée de la même société +(`UPDATE invoice_settlements SET invoice_id = ?`) ; créer l'avoir (vrai chemin, désormais accepté) ; +**rattacher** la ligne à la facture d'origine. Vérifier les contraintes d'unicité de +`invoice_settlements` avant de retenir ce gabarit ; à défaut, construire l'écriture d'avoir par +`journal_entries::create_in_tx` avec les lignes de `generate_credit_note_journal_lines`, jamais à la +main ligne par ligne. + +**AC 14** — Frontend : le bouton « Créer un avoir » (`invoices/[id]/+page.svelte:836`) **suit le +précédent du bouton « Dévalider »** (`:864-871`) : la condition `!invoice.paidAt` est **retirée**, le +bouton s'affiche pour `canManage`, et **le serveur refuse avec le motif nommé**, que le dialogue +affiche déjà (`creditNoteError = err.message`, `:101`). ⛔ L'écran **ne rejoue pas** la règle métier : +une condition client qui en couvre une moitié laisse l'utilisateur sans rien à lire, et une qui la +couvre entière se désynchronise à la première évolution du serveur. La garde étant désormais +**unique** côté serveur (AC 11), le motif « payée » et le motif « réglée en partie » s'affichent de +la même façon. Le commentaire de `:864-871` est étendu aux deux boutons, ou dupliqué au-dessus du +second. **AC 15** — Rien n'est **réparé** dans les données existantes : pas de migration, pas de rejeu (`CLAUDE.md` : aucune donnée de production à protéger ; précédent 24-2 D7). Les états hérités @@ -172,6 +201,7 @@ règlement — et c'est ce que les textes disent (AC 17). |---|---| | `credited_amount_is_ttc_and_matches_the_ledger` | AC 1, AC 7 — facture multi-taux, avoir émis : sous-requête = crédit créance de l'écriture d'avoir | | `amount_due_of_a_credited_invoice_is_zero` | AC 8 — 8,1 %, non réglée : `0`, pas `8.10` | +| e2e API `get_credited_invoice_reports_zero_amount_due` | AC 8 — la surface exposée aux clés API | | `amount_due_after_settlement_then_legacy_credit_is_minus_settled` | AC 8 — état hérité monté en SQL : `−40` | | `settled_and_credited_forms_are_at_parity` | AC 6 | | `credit_note_refused_on_partially_settled_invoice` | AC 9, AC 10 — **rien écrit** : aucun avoir, facture `validated`, `version` inchangée, solde de créance inchangé, séquence d'avoir non consommée | @@ -179,7 +209,7 @@ règlement — et c'est ce que les textes disent (AC 17). | `credit_note_accepted_after_the_settlement_is_cancelled` | la promesse du message : annuler le règlement rouvre l'avoir | | `credit_note_waits_for_a_concurrent_settlement` | AC 9 — **entrelacement** : une transaction verrouille la facture et insère un règlement ; `create_credit_note` lancé en parallèle attend, puis **refuse** après le commit. Patron : la sonde d'entrelacement de la 25-3-b | | e2e API `credit_note_on_partially_settled_invoice_is_409` | la frontière HTTP : 409, `CREDIT_NOTE_INVOICE_SETTLED`, message fr | -| Vitest fiche facture | AC 14 — bouton absent si `amountSettled > 0`, présent sinon | +| Vitest fiche facture | AC 14 — bouton **présent** sur une facture réglée (en partie **et** en totalité) ; au refus, le dialogue affiche le message du serveur | ⛔ **Chaque test porte au moins une facture à TVA non nulle.** À 0 %, HT = TTC et #455 est invisible — c'est ainsi qu'il est passé. @@ -188,7 +218,7 @@ invisible — c'est ainsi qu'il est passé. `SUM(cn.total_amount)` dans la forme scalaire ; (m2) idem dans la forme jointe seule ; (m3) retirer la lecture des règlements de la garde (ne garder que `paid_at`) ; (m4) retirer `paid_at` (ne garder que la ligne) ; (m5) lire les règlements **avant** le verrou, non verrouillant ; (m6) remettre -`!invoice.paidAt` seul sur le bouton. Chacune doit faire rougir au moins un test. +`!invoice.paidAt` sur le bouton ; (m7) mapper la variante sur le générique `ILLEGAL_STATE_TRANSITION`. Chacune doit faire rougir au moins un test. **AC 17** — Manuels et notes (`docs/manual/fr/user-manual.tex`, PDF régénéré et **contrôlé aplati**) : @@ -198,7 +228,9 @@ aplati**) : « compte de produit archivé » ; - `:963` et `:1455` (« la facture a été créditée par un avoir… ») : précisés comme **cas hérité** — une facture créditée avant la v0.12.1, ou restaurée d'une sauvegarde antérieure ; -- `CHANGELOG.md` `[0.12.1]` *Fixed* : les deux défauts, en langage d'utilisateur. +- `CHANGELOG.md` `[0.12.1]` *Fixed* : les deux défauts, en langage d'utilisateur — et, pour #455, + **nommer l'API** : `GET /invoices/{id}` rendait la TVA en `amountDue` sur une facture créditée ; + une intégration tierce qui le lisait doit le savoir. ⚠️ **Greper le symptôme, pas la phrase** : `grep -rniE "payée|encaissée|réglée" docs/manual/fr/*.tex` restreint aux lignes qui parlent d'avoir, plus le PDF aplati. Les manuels DE/IT/EN sont des stubs. @@ -210,11 +242,11 @@ restreint aux lignes qui parlent d'avoir, plus le PDF aplati. Les manuels DE/IT/ - [ ] **T2 — parité et concordance** (AC 6-8) : nouveau fichier de test sur le patron `invoice_ttc_parity.rs` ; montage de l'état hérité en SQL. - [ ] **T3 — garde de l'avoir** (AC 9-11) : lecture verrouillante des règlements après le verrou, - variante `DbError`, `code()`. + variante `DbError`, `error_code()`. - [ ] **T4 — API et i18n** (AC 12) : mapping, clé ×4 ; e2e 409. -- [ ] **T5 — fixture `monter`** (AC 13) : état monté en SQL, commentaire ; les tests d'annulation de - règlement restent verts. -- [ ] **T6 — frontend** (AC 14) : condition du bouton, Vitest. +- [ ] **T5 — fixture `monter`** (AC 13) : gabarit « détacher, créditer, rattacher », commentaire ; + **les quinze cas** de la précédence restent verts, dont les quatre paires avec `InvoiceCredited`. +- [ ] **T6 — frontend** (AC 14) : condition du bouton retirée, commentaire, Vitest. - [ ] **T7 — tests et mutations** (AC 16) : m1 à m6 exécutées, résultats consignés. - [ ] **T8 — textes** (AC 17) : manuel, PDF aplati, CHANGELOG. - [ ] **T9 — gates** : backend complet (base remise à zéro), frontend complet, E2E complet. @@ -240,14 +272,15 @@ restreint aux lignes qui parlent d'avoir, plus le PDF aplati. Les manuels DE/IT/ | `crates/kesh-db/src/repositories/invoice_settlements.rs:24-58, 122-145` | les quatre constantes, `amount_due` | | `crates/kesh-db/src/repositories/invoices.rs:155-190` | `INVOICE_TTC_SUBQUERY_SQL` / `…_DERIVED_JOIN_SQL`, `total_ttc` | | `crates/kesh-db/src/repositories/credit_notes.rs:187-257, 260-330, 510-575` | écriture de l'avoir, garde AC2bis, `total_amount`, bascule `cancelled` | -| `crates/kesh-db/src/errors.rs:119-175, 300-470, 640-670` | `UnvalidationBlocker`, `DbError`, `code()` | +| `crates/kesh-db/src/errors.rs:119-175, 300-470, 641-670` | `UnvalidationBlocker`, `DbError`, `error_code()` | | `crates/kesh-db/src/repositories/invoices.rs:1430-1448` | la garde de dévalidation, patron de l'AC 9 | | `crates/kesh-api/src/errors.rs:2515-2560, 2741-2748` | mapping `InvoiceNotUnvalidatable`, générique | | `crates/kesh-api/src/routes/credit_notes.rs:183-205` | handler de création | | `crates/kesh-db/tests/invoice_ttc_parity.rs` | patron du test de parité | | `crates/kesh-db/tests/credit_notes_repository.rs:267-299` | `credit_note_refused_on_paid_invoice` | | `crates/kesh-db/tests/invoice_settlement.rs:32, 116-125, 937-990` | helper à 0 %, `solde()`, `monter` | -| `frontend/src/routes/(app)/invoices/[id]/+page.svelte:90-104, 836, 865, 1022-1041` | dialogue d'avoir, bouton, précédent « Dévalider », reste dû | +| `frontend/src/routes/(app)/invoices/[id]/+page.svelte:90-104, 836, 864-871, 1022-1041` | dialogue d'avoir (`err.message` `:101`), bouton, précédent « Dévalider », reste dû | +| `crates/kesh-api/src/routes/invoices.rs:621-642` | `get_invoice` : `amountDue` rendu sans condition de statut | ### Courses @@ -299,6 +332,18 @@ bloque **25-4-b**, pas celle-ci. ## Change Log +- **2026-09-26** — **validation P1** (Sonnet, prompt `25-4-a-validate-prompt-p1.md`) — **1 HIGH, + 3 MEDIUM, 1 LOW**, tous confirmés dans le code avant correction. **HIGH** : le récit « #455 + latent » était faux — `get_invoice` rend `amountDue` sans condition de statut, et + `GET /invoices/{id}` est ouverte aux clés API : une facture créditée à TVA non nulle y rend la + TVA ; récit corrigé ici et dans la fiche mère, AC 8 étendu à la frontière HTTP, e2e ajouté, + CHANGELOG à nommer l'API. **MEDIUM** : `DbError::code()` n'existe pas — c'est `error_code()` ; + AC 14 contredisait le précédent « Dévalider » qu'il citait — il le suit désormais (bouton + visible, le serveur refuse avec le motif nommé, m7 ajoutée) ; AC 13 ignorait que `monter` sert + quinze cas dont quatre paires avec `InvoiceCredited`, et qu'un avoir `issued` exige une écriture + (`chk_credit_notes_issued_has_je`) — gabarit « détacher, créditer, rattacher » prescrit. **LOW** : + `err.message` est à `:101`, non `:98`. Axes tous exercés ; non exercé : la sémantique REPEATABLE + READ n'a pas été mesurée (raisonnée). - **2026-09-26** — Créée (Opus 5.5) après découpage de la 25-4. Inventaire vérifié par `grep -nF` sur chaque site cité. Deux faits hors des issues : #455 est **latent** sur les données neuves et ne se voit que dans l'état que #456 produit — d'où leur réunion ; et la fixture `monter` encode diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md index b94e63cd..6d9372fd 100644 --- a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -39,7 +39,9 @@ Le découpage suit l'ordre des dépendances, non celui des issues : 3. ⚠️ **La relance joint le PDF de la facture**, dont la QR-facture porte le TTC (`invoice_email.rs:1140-1166`, `invoice_pdf_service::render`). Un client qui paie la relance en scannant la QR paie **à nouveau le montant entier**. Question posée à Guy (Q1 ci-dessous). -4. ⚠️ **#455 est latent sur les données neuves.** Un avoir est **total** et fait passer la facture à +4. ⚠️ **#455 est masqué à l'écran — mais pas à l'API.** *(Corrigé en validation P1 de 25-4-a : la + première rédaction disait « latent ». `GET /invoices/{id}`, ouverte aux clés API, rend + `amountDue` sans condition de statut : sur une facture créditée à TVA non nulle, il vaut la TVA.)* Un avoir est **total** et fait passer la facture à `cancelled` (`credit_notes.rs:560-563`) ; or les agrégats, le rapprochement et le règlement ne lisent que des factures `validated`. Le terme « avoir » du résiduel n'est donc **non nul que sur une facture annulée**, et le reste dû ne s'y affiche que si `amountSettled > 0` — c'est-à-dire diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index fdbcda3c..84c42644 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -353,7 +353,7 @@ development_status: 25-3-c-annuler-facture-fournisseur: done # 2026-09-26 **REVUE CLOSE EN 1 PASSE** (0 > LOW, Sonnet x3) ; non poussee ; PR `closes #454` titre ET corps, sur accord. **IMPLEMENTEE** — gates 2472/2472, 783/783, E2E 220/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 **SPEC VALIDEE en 2 passes** (3 MED -> 0 > LOW). **SPECIFIEE** — `25-3-c-annuler-facture-fournisseur.md`. Geste par le socle (autorite `SupplierPurchase`), queue commune sur l'ecriture d'ACHAT, lot en dernier rang ; payee : reglement DETACHE (colonnes remises a NULL, ecriture libre, lien garde par l'audit) — **Q1 tranchee par Guy le 2026-09-26**. Aucune migration. Prochaine etape : validate. # 2026-09-24 NEE d'un arbitrage de Guy — une facture fournisseur s'annule DANS TOUS LES CAS, sauf si l'exercice de son ecriture d'achat est CLOS (l'exercice, pas le verrou de periode) ; payee, son reglement RESTE au grand livre et REDEVIENT A LETTRER (Epic 15 : un paiement doit correspondre a une facture, au besoin creee pour lui — « comme bexio »). Reecrit `cancel`, donc absorbe [#454]. Derriere la 25-3-a. 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* - 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (latent, visible seulement dans l'etat que #456 produit) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE, validation a lancer. + 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation P1 (Sonnet) 1 HIGH / 3 MED / 1 LOW, corriges. 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-26 : QR sans montant si partiellement reglee, et le reste a payer dit au client. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. 25-4-propager-le-residuel: split # 2026-09-26 DECOUPEE (six modules > 5) en 25-4-a / 25-4-b / 25-4-c ; #455 et #456 rattachees. # [#416] [#420] — la 24-2 a introduit le reglement PARTIEL ; le residuel n'est propage ni a la balance agee ni a l'echeancier, et **le score de rapprochement compare au TTC et non au residuel** : le virement du solde d'une facture partiellement reglee **score 0**. From ca66c118b7156bb0e7028036a34357361fb3645c Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:03:17 +0200 Subject: [PATCH 07/34] =?UTF-8?q?docs(25-4-a):=20prompt=20de=20validation?= =?UTF-8?q?=20P2=20;=20plages=20de=20monter=20harmonis=C3=A9es=20(refs=20#?= =?UTF-8?q?455,=20refs=20#456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-residuel-juste.md | 4 +- .../25-4-a-validate-prompt-p2.md | 66 +++++++++++++++++++ .../25-4-propager-le-residuel.md | 2 +- 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 _bmad-output/implementation-artifacts/25-4-a-validate-prompt-p2.md diff --git a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md index 90c7c586..3c1658d5 100644 --- a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md +++ b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md @@ -72,7 +72,7 @@ où `settlement_cancel_blocker` la déclare inannulable (`SettlementCancelBlocke (`UnvalidationBlocker::Settled`, `errors.rs:134-160` ; `invoices.rs:1430-1448`). ⚠️ **Un test encode le défaut comme attendu** : la fixture `monter` -(`crates/kesh-db/tests/invoice_settlement.rs:937-990`) règle 40 sur 100 puis appelle +(`crates/kesh-db/tests/invoice_settlement.rs:937-1019`) règle 40 sur 100 puis appelle `create_credit_note` avec `.expect("avoir après règlement partiel — le vrai chemin l'accepte")`. ⚠️ **Le frontend masque le bouton sur une facture payée, pas sur une facture partiellement @@ -278,7 +278,7 @@ restreint aux lignes qui parlent d'avoir, plus le PDF aplati. Les manuels DE/IT/ | `crates/kesh-api/src/routes/credit_notes.rs:183-205` | handler de création | | `crates/kesh-db/tests/invoice_ttc_parity.rs` | patron du test de parité | | `crates/kesh-db/tests/credit_notes_repository.rs:267-299` | `credit_note_refused_on_paid_invoice` | -| `crates/kesh-db/tests/invoice_settlement.rs:32, 116-125, 937-990` | helper à 0 %, `solde()`, `monter` | +| `crates/kesh-db/tests/invoice_settlement.rs:32, 116-125, 937-1019` | helper à 0 %, `solde()`, `monter` | | `frontend/src/routes/(app)/invoices/[id]/+page.svelte:90-104, 836, 864-871, 1022-1041` | dialogue d'avoir (`err.message` `:101`), bouton, précédent « Dévalider », reste dû | | `crates/kesh-api/src/routes/invoices.rs:621-642` | `get_invoice` : `amountDue` rendu sans condition de statut | diff --git a/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p2.md b/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p2.md new file mode 100644 index 00000000..f6a2662a --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p2.md @@ -0,0 +1,66 @@ +# Prompt — validation P2, Story 25-4-a (le résiduel juste) + +*Versionné le 2026-09-26. **Une lentille** (Haiku), contexte frais.* + +Dépôt `/home/gcorbaz/devel/kesh`, branche `story/25-4-propager-le-residuel`. Fiche à valider : +`_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md`. Fiche mère (inventaire, +découpage) : `25-4-propager-le-residuel.md`. Issues : `gh issue view 455`, `gh issue view 456`. +Contexte de la grandeur : `24-2-encaissement-client.md` § D3, D4, AC 5-7. Règles du dépôt : +`CLAUDE.md`. Checklist BMAD : `.claude/skills/bmad-create-story/checklist.md`. + +## Axes — tous obligatoires + +1. **Chaque référence `fichier:ligne`** existe et dit ce que la fiche affirme — relis le code. +2. **La formule** : la sous-requête TTC de l'avoir prescrite (AC 1-2) concorde-t-elle réellement, + centime par centime, avec l'écriture de l'avoir (`generate_credit_note_journal_lines`, + `line_vat_amount` — lis `kesh-core`, sens d'arrondi de `ROUND` MariaDB vs Rust) ? Et avec + `INVOICE_TTC_SUBQUERY_SQL` ? Un écart d'un centime possible est un finding. +3. **La garde** (AC 9-11) : inventorie **tous** les chemins qui créent un règlement ou un avoir + (pars du symptôme : `grep -rn "INSERT INTO invoice_settlements\|create_credit_note\|INSERT INTO credit_notes" crates/`), + et leur sérialisation (verrou pessimiste ou optimiste). La fiche en nomme trois : en manque-t-il ? + Le raisonnement REPEATABLE READ de § Courses est-il juste ? +4. **L'état hérité** : l'import d'une sauvegarde ramène-t-il vraiment l'état (règlement + avoir) ? + Les textes prescrits (AC 17) le disent-ils juste ? D'autres lecteurs de `amount_due` ou des + constantes changent-ils de valeur avec l'AC 1 (inventaire : `grep -rn "INVOICE_CREDITED\|amount_due" crates/ frontend/src`) ? +5. **Les tests** (AC 16) : chacun prouve-t-il ce qu'il annonce, ou peut-il passer à vide ? Les + mutations m1-m6 sont-elles tuables par les tests listés ? Une mutation que rien ne tuerait est + un finding. +6. **Le manuel** : les lignes citées (AC 17) existent-elles et disent-elles ce qu'on veut changer ? + Contrôle aussi le **PDF aplati** (`pdftotext docs/manual/fr/user-manual.pdf - | tr '\n' ' ' | tr -s ' '`, + vers `/tmp/claude-1000/-home-gcorbaz-devel-kesh/ca5ce2e2-67a3-4eeb-817f-c2de35620a1c/scratchpad/`). + Une autre phrase du manuel ou de l'admin-manual, de l'api-external, du README, promet-elle + l'ancien comportement ? +7. **Le découpage** (fiche mère) : quelque chose de 25-4-a appartient-il en fait à b ou c, ou + l'inverse ? La latence de #455 est-elle correctement établie ? + +## Ce que tu rends + +- **Findings** : sévérité (CRITICAL / HIGH / MEDIUM / LOW), l'endroit exact de la fiche, **la + preuve** (commande et résultat, code lu), la correction proposée. +- ⛔ **La liste des axes réellement exercés ET de ceux qui ne l'ont pas été.** Un « 0 finding » + sans elle ne compte pas. + +## Interdits + +⛔ N'écris aucun fichier du dépôt ; n'exécute aucune commande qui écrit dans le dépôt ou dans une +base — `scripts/prepare-release.sh`, `scripts/regen-test-schema.sh`, `scripts/install-hooks.sh`, +`scripts/test-fast.sh`, `scripts/mem-guard.sh`, `make`, `latexmk`, tout `git commit`/`push`/`add`/ +`stash`/`reset`/`rebase`/`checkout`/`switch`/`worktree`, `sqlx migrate`, `cargo test`/`nextest`, +`npm run`, `npx playwright`. Autorisés : lecture, `grep`, `git log`/`show`/`diff`, `gh issue view`, +`pdftotext` vers le scratchpad, `cargo check`. + +## Contexte P1 — à vérifier, pas à re-signaler + +La passe 1 a trouvé 1 HIGH (récit « #455 latent » faux : `get_invoice` rend `amountDue` aux clés +API), 3 MEDIUM (`error_code()` et non `code()` ; AC 14 aligné sur le précédent « Dévalider » ; +AC 13, `monter` sert quinze cas et un avoir `issued` exige une écriture) et 1 LOW. Les corrections +sont dans `git diff 0f4b9ec0 HEAD -- _bmad-output/implementation-artifacts/25-4-*.md` — **diff unique +aplati, à lire en priorité** : une correction introduit souvent le défaut suivant. + +**Tes priorités** : (1) le **gabarit « détacher, créditer, rattacher »** de l'AC 13 — est-il +réalisable (contraintes d'unicité et FK d'`invoice_settlements`, `company_id`, écriture de +règlement qui pointe l'ancienne facture ?) et compatible avec les quatre paires combinées ? +(2) l'AC 14 révisé — le dialogue d'avoir affiche-t-il vraiment le message du serveur, et que +voit l'utilisateur sur une facture **entièrement** payée, où le bouton était masqué jusqu'ici ? +(3) l'e2e `get_credited_invoice_reports_zero_amount_due` — la forme JSON exacte de `amountDue` +(sérialisation de `Decimal`) pour que l'assertion ne soit pas fragile. diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md index 6d9372fd..4070392c 100644 --- a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -53,7 +53,7 @@ Le découpage suit l'ordre des dépendances, non celui des issues : 6. **Pourquoi aucun test n'a vu #455** : toutes les factures des tests de règlement sont à **0 % de TVA** (`crates/kesh-db/tests/invoice_settlement.rs:32`), où HT = TTC. 7. ⚠️ **Un test encode #456 comme attendu** : la fixture `monter` - (`crates/kesh-db/tests/invoice_settlement.rs:937-990`) règle 40 sur 100 puis crée un avoir avec + (`crates/kesh-db/tests/invoice_settlement.rs:937-1019`) règle 40 sur 100 puis crée un avoir avec `.expect("avoir après règlement partiel — le vrai chemin l'accepte")`. Il **décrit le défaut** ; il se corrige, il ne se contourne pas. From 7503be9b81dff44556711bc0d6b19389d6cdb56c Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:09:34 +0200 Subject: [PATCH 08/34] =?UTF-8?q?docs(25-4):=20la=20QR=20du=20rappel=20por?= =?UTF-8?q?te=20le=20reste=20d=C3=BB,=20une=20r=C3=A8gle=20pour=20les=20de?= =?UTF-8?q?ux=20rappels=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-propager-le-residuel.md | 16 ++++++++++++++++ .../implementation-artifacts/sprint-status.yaml | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md index 4070392c..3f404e51 100644 --- a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -96,11 +96,27 @@ Pour 25-4-b, donc : tombe donc — ce n'est pas la facture —, et #387 ne s'en trouve pas touché. ⚠️ **Point ouvert posé à Guy** : dès lors, la QR du rappel doit-elle porter le **reste à payer** plutôt que rester sans montant ? +- ✅ **Tranché (Guy, 2026-09-27)** : *« si c'est un rappel pour facture impayée, on met le montant + total de la facture, et si c'est un montant partiel, on met le montant à payer : on traite les + deux types de rappel de la même manière. »* Une seule règle : **la QR du rappel porte le reste + dû** — qui vaut le TTC sur une facture sans règlement. L'option (b) « sans montant » est + **abandonnée**. Guy suggère aussi de rappeler **le numéro de la facture d'origine** : la QR le + porte déjà (référence QRR dérivée de la facture, ou message non structuré = numéro, + `invoice_pdf_service.rs:218-250`) et **doit le garder à l'identique** — c'est ce que lit le + rapprochement ; le PDF du rappel le nomme en toutes lettres, avec montant initial, déjà réglé, + reste à payer. +- ⚠️ **Point ouvert pour la spécification de 25-4-b — les frais de rappel.** Le texte réclame + aujourd'hui `TTC + frais` (`invoice_email.rs:334`), mais les frais ne semblent **pas + comptabilisés** (aucune écriture dans `invoice_reminders.rs`, à vérifier). Si la QR portait + « reste dû + frais », le virement du client **dépasserait le reste dû** et serait refusé comme + trop-perçu, au rapprochement comme au règlement manuel (`RECONCILIATION_OVERPAYMENT`). À établir + avant de fixer le montant de la QR. ⚠️ **Ne pas contester l'arbitrage en revue** : en contester la mise en œuvre. ## Change Log +- **2026-09-27** — Q1 retranchée : la QR du rappel porte le reste dû (le TTC s'il n'y a aucun règlement) ; numéro de facture conservé ; point ouvert sur les frais. - **2026-09-27** — Recadrage de Q1 : le document est un rappel pour un montant partiel, pas la facture réémise. - **2026-09-26** — Complément de Q1 : le reste à payer figure aussi sur le PDF joint. - **2026-09-26** — Q1 tranchée par Guy : QR sans montant pour une facture partiellement réglée, et le reste à payer dit au client (rattaché à 25-4-b). diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 84c42644..cd62a835 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -354,7 +354,7 @@ development_status: 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation P1 (Sonnet) 1 HIGH / 3 MED / 1 LOW, corriges. - 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-26 : QR sans montant si partiellement reglee, et le reste a payer dit au client. + 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. 25-4-propager-le-residuel: split # 2026-09-26 DECOUPEE (six modules > 5) en 25-4-a / 25-4-b / 25-4-c ; #455 et #456 rattachees. # [#416] [#420] — la 24-2 a introduit le reglement PARTIEL ; le residuel n'est propage ni a la balance agee ni a l'echeancier, et **le score de rapprochement compare au TTC et non au residuel** : le virement du solde d'une facture partiellement reglee **score 0**. 25-5-a-export-souverainete: done # 2026-09-26 **MERGEE (PR #452, #386 fermee) SANS REVUE DE CODE** ; revue a posteriori CLOSE en 3 passes sur `story/25-5-a-revue-code` (4 HIGH P1, 1 HIGH orchestrateur P2 — colonnes manquantes, garde de colonnes —, P3 ciblee test seul) ; gates 2466/2466, E2E 221/8/19 sans regression ; reports #465, #466. # 2026-09-23 **SPECIFIEE et VALIDEE en 1 passe** (0 CRITICAL, 4 HIGH, 3 MEDIUM, 3 LOW, tous corriges). [#386] — issue du DECOUPAGE de la 25-5 (arbitrage de Guy) : #385 (Balance vs Bilan) et #386 n'ont en commun que le mot « etat », et touchent des modules disjoints. ⛔ **Trois faits qui corrigent l'issue** : 21 tables absentes et non 10 ; le trou s'est CREUSE depuis son ecriture (`invoice_settlements`, Epic 24) ; et SIX repositories sur huit n'ont aucune lecture exhaustive — le vrai cout, absent de l'issue. Arbitrage de Guy : l'export CSV ne porte que les donnees COMPTABLES (11 dedans), les backups portent tout ; 10 exclusions AVEC motif ecrit, dont 3 tables de SECRETS. Les justificatifs (fichiers) partent en story separee sur la SAUVEGARDE. ⛔ La vraie livraison est la GARDE D'EXHAUSTIVITE (patron D4-ter + P7), non les 11 exports : trois epics ont rouvert le trou en silence. ⚠️ Pieges releves par la validation : le nombre 19 code en dur a SEPT endroits dont deux `debug_assert_eq!` qui PANIQUENT ; `audit_log` n'a AUCUN dump exhaustif (ses fonctions sont bornees, la route rejette au-dela de 10 000 lignes) ; `csv_sanitize` existe et n'est pas employee par cet export ; deux sites de manuel DEJA faux (un dossier `reports/` invente, un « 38 » pour 39). From 5c337d679a612c94379e93ffe72687f0204dff34 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:09:55 +0200 Subject: [PATCH 09/34] =?UTF-8?q?docs(25-4):=20frais=20de=20rappel=20confi?= =?UTF-8?q?gurables,=20rien=20d'affich=C3=A9=20=C3=A0=20z=C3=A9ro=20(refs?= =?UTF-8?q?=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../implementation-artifacts/25-4-propager-le-residuel.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md index 3f404e51..471fbefd 100644 --- a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -105,6 +105,10 @@ Pour 25-4-b, donc : `invoice_pdf_service.rs:218-250`) et **doit le garder à l'identique** — c'est ce que lit le rapprochement ; le PDF du rappel le nomme en toutes lettres, avec montant initial, déjà réglé, reste à payer. +- ✅ **Les frais restent configurables, et à zéro rien ne s'affiche** (Guy, 2026-09-27) : *« ils + doivent être configurables : si 0, ne rien afficher. »* Les frais sont déjà réglés par niveau de + rappel (`dunning_levels.fee_amount`) ; le PDF et le texte du rappel n'ont **aucune ligne de + frais** quand le montant est nul — ni « frais : 0.00 », ni total qui les mentionne. - ⚠️ **Point ouvert pour la spécification de 25-4-b — les frais de rappel.** Le texte réclame aujourd'hui `TTC + frais` (`invoice_email.rs:334`), mais les frais ne semblent **pas comptabilisés** (aucune écriture dans `invoice_reminders.rs`, à vérifier). Si la QR portait @@ -116,6 +120,7 @@ Pour 25-4-b, donc : ## Change Log +- **2026-09-27** — Frais de rappel : configurables, aucune ligne affichée à zéro. - **2026-09-27** — Q1 retranchée : la QR du rappel porte le reste dû (le TTC s'il n'y a aucun règlement) ; numéro de facture conservé ; point ouvert sur les frais. - **2026-09-27** — Recadrage de Q1 : le document est un rappel pour un montant partiel, pas la facture réémise. - **2026-09-26** — Complément de Q1 : le reste à payer figure aussi sur le PDF joint. From 451e2cc2b88a3a88ed6fd12076e64f8122c74941 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:10:33 +0200 Subject: [PATCH 10/34] =?UTF-8?q?docs(25-4-a):=20validation=20P2=20?= =?UTF-8?q?=E2=80=94=20facture=20auxiliaire=20du=20gabarit,=20amountDue=20?= =?UTF-8?q?compar=C3=A9=20en=20Decimal,=20bouton=20sur=20facture=20pay?= =?UTF-8?q?=C3=A9e=20(refs=20#455,=20refs=20#456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-residuel-juste.md | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md index 3c1658d5..bfa466fa 100644 --- a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md +++ b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md @@ -122,8 +122,12 @@ créance par l'écriture de l'avoir** (`credit_notes.journal_entry_id` → ligne seul HT ou à une valeur recalculée en Rust **ne suffit pas**. **AC 8** — Une facture validée à 8,1 %, **non réglée, créditée** : `amount_due` = **0** (et non la -TVA) — **au repository ET à la frontière HTTP** : `GET /api/v1/invoices/{id}` rend `amountDue` = -`"0.00"` (ou sa forme décimale normalisée), `amountSettled` = `0`. Une facture validée à 8,1 %, **réglée de 40 puis créditée** (état **monté en SQL**, cf. +TVA) — **au repository ET à la frontière HTTP** : `GET /api/v1/invoices/{id}` rend `amountDue` **non nul +(pas `null` : `null` veut dire « non calculé »,** `routes/invoices.rs:242-248`**)**, sérialisé en +**chaîne** (`rust_decimal` avec `serde-str`, `kesh-api/Cargo.toml:39`), dont la valeur **parsée en +`Decimal`** vaut zéro — ⛔ ni comparaison de chaîne (l'échelle suit le calcul SQL : `"0.0000"` et +`"0.00"` sont également justes), ni `f64` (patron à ne pas suivre : +`invoice_echeancier_e2e.rs:636-641`). Même règle pour `amountSettled`. Une facture validée à 8,1 %, **réglée de 40 puis créditée** (état **monté en SQL**, cf. AC 13) : `amount_due` = **−40**, le montant encaissé en trop sur une vente annulée — et non `TVA − 40`. ⚠️ **Pas d'écrêtage à zéro** : le doc-comment d'`amount_due` l'interdit déjà. @@ -173,8 +177,14 @@ donc une écriture de contre-passation valide. **Gabarit recommandé**, qui gard écritures produites par les vrais chemins** et ne touche en SQL qu'un rattachement : régler (vrai chemin) ; **détacher** la ligne de règlement vers une facture auxiliaire validée de la même société (`UPDATE invoice_settlements SET invoice_id = ?`) ; créer l'avoir (vrai chemin, désormais accepté) ; -**rattacher** la ligne à la facture d'origine. Vérifier les contraintes d'unicité de -`invoice_settlements` avant de retenir ce gabarit ; à défaut, construire l'écriture d'avoir par +**rattacher** la ligne à la facture d'origine. La facture auxiliaire est créée **dans `monter`** +par le helper existant `validated_invoice` (même société, montant quelconque), n'est jamais réglée +ni créditée, et ne sert qu'à porter la ligne le temps de l'avoir ; aucun motif ne la lit. Seule +contrainte d'unicité d'`invoice_settlements` : `uq_invoice_settlements_entry (journal_entry_id)` +(`20260827000001_invoice_settlements.sql:65`) — le détachement ne la touche pas. ⚠️ Pendant le +détachement, l'écriture de règlement reste celle de la facture d'origine : aucun motif de +`settlement_cancel_blocker` ne doit être évalué entre les deux `UPDATE`. Si le gabarit se révèle +impraticable, à défaut, construire l'écriture d'avoir par `journal_entries::create_in_tx` avec les lignes de `generate_credit_note_journal_lines`, jamais à la main ligne par ligne. @@ -188,6 +198,11 @@ couvre entière se désynchronise à la première évolution du serveur. La gard la même façon. Le commentaire de `:864-871` est étendu aux deux boutons, ou dupliqué au-dessus du second. +⚠️ **Changement visible sur une facture entièrement payée** : le bouton, masqué jusqu'ici, apparaît ; +l'utilisateur ouvre le dialogue, confirme, et lit le refus nommé (AC 12) dans le dialogue même +(`creditNoteError`, `:77`, `:101`). C'est le comportement de « Dévalider » depuis le 2026-09-19, et +le CHANGELOG le dit. + **AC 15** — Rien n'est **réparé** dans les données existantes : pas de migration, pas de rejeu (`CLAUDE.md` : aucune donnée de production à protéger ; précédent 24-2 D7). Les états hérités restent **visibles** — reste dû négatif sur la fiche, motif `InvoiceCredited` à l'annulation du @@ -332,6 +347,18 @@ bloque **25-4-b**, pas celle-ci. ## Change Log +- **2026-09-27** — **validation P2** (Haiku, diff aplati, prompt `25-4-a-validate-prompt-p2.md`) — + rapporte 3 MEDIUM et 1 LOW ; **un seul MEDIUM retenu** : l'AC 13 ne disait pas d'où vient la facture + auxiliaire du gabarit — précisé (helper `validated_invoice`, contrainte d'unicité vérifiée, aucun + motif évalué pendant le détachement). **Écartés** : deux « MEDIUM » reprochent au **code** de ne pas + encore faire ce que la fiche prescrit (bouton `:836`, test e2e absent) — ce n'est pas un défaut de + spec ; le LOW (deux formulations du manuel) est déjà couvert par l'AC 17. ⚠️ **Repris par + l'orchestrateur**, la priorité 3 que la passe n'a pas tranchée : `amountDue` est une **chaîne** à + l'échelle du calcul SQL — l'AC 8 impose désormais `null` interdit et une comparaison **en + `Decimal`**, ni chaîne ni `f64` ; et la priorité 2 : ce que voit l'utilisateur sur une facture + entièrement payée (bouton désormais visible, refus nommé dans le dialogue) est écrit à l'AC 14. + La passe affirmait aussi que `accept_one_invoice` ne crée pas de règlement — faux + (`invoice_settlements::create_in_tx`, `reconciliation.rs:1416-1428`) ; sans conséquence sur la fiche. - **2026-09-26** — **validation P1** (Sonnet, prompt `25-4-a-validate-prompt-p1.md`) — **1 HIGH, 3 MEDIUM, 1 LOW**, tous confirmés dans le code avant correction. **HIGH** : le récit « #455 latent » était faux — `get_invoice` rend `amountDue` sans condition de statut, et From 1344b3a2945e183eb1e94123ae507c3c2bddb09b Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:10:59 +0200 Subject: [PATCH 11/34] =?UTF-8?q?docs(25-4-a):=20prompt=20de=20validation?= =?UTF-8?q?=20P3=20cibl=C3=A9e=20(refs=20#455,=20refs=20#456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-validate-prompt-p3.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 _bmad-output/implementation-artifacts/25-4-a-validate-prompt-p3.md diff --git a/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p3.md b/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p3.md new file mode 100644 index 00000000..d26bf42e --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p3.md @@ -0,0 +1,49 @@ +# Prompt — validation P3 CIBLÉE, Story 25-4-a (le résiduel juste) + +*Versionné le 2026-09-27. **Une lentille** (Opus), contexte frais. Passe **ciblée** (CLAUDE.md +§ « La passe ciblée ») : la P2 n'a trouvé qu'un défaut **né de la correction P1** ; ce qu'il reste à +relire est la remédiation, pas la story entière.* + +Dépôt `/home/gcorbaz/devel/kesh`, branche `story/25-4-propager-le-residuel`. Fiche : +`_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md`. **Périmètre** : le diff aplati +des remédiations P1 et P2 — +`git diff 0f4b9ec0 451e2cc2 -- _bmad-output/implementation-artifacts/25-4-a-residuel-juste.md _bmad-output/implementation-artifacts/25-4-propager-le-residuel.md` +(ignore les paragraphes de la fiche mère qui consignent les arbitrages de Guy sur 25-4-b : **ne pas +les contester**). + +## La lentille : chasseur de régressions de remédiation + +Pour chaque passage réécrit en P1 ou P2 : + +1. **Est-il vrai contre le code ?** Relis les sites qu'il cite : `get_invoice` + (`crates/kesh-api/src/routes/invoices.rs`), `docs/api-external.md`, le bouton et le dialogue + d'avoir (`frontend/src/routes/(app)/invoices/[id]/+page.svelte`), la fixture `monter` et + `la_precedence_de_l_annulation_lecture_et_ecriture` (`crates/kesh-db/tests/invoice_settlement.rs`), + la migration `20260827000001_invoice_settlements.sql`, `settlement_cancel_blocker` + (`invoice_settlements_write.rs`), `kesh-api/Cargo.toml` (`serde-str`). +2. **Le gabarit « détacher, créditer, rattacher » (AC 13)** : exécute-le **mentalement** pas à pas + contre `create_credit_note` et les quatre paires combinées de `monter`. Une FK, une contrainte, + un audit, un contrôle de `company_id`, une lecture de `invoice_settlements` par + `create_credit_note` ou par les motifs montés **après** peut-il le casser ? L'écriture de + règlement porte-t-elle quelque part l'identifiant de la facture d'origine (description, audit, + `invoice_settlements` seul) ? +3. **AC 14 révisé** : cohérent avec l'AC 11, l'AC 12, les tests de l'AC 16 et la mutation m6/m7 ? + Un test de l'AC 16 porte-t-il encore l'ancienne logique (« bouton absent ») ? +4. **AC 8 révisé** : l'assertion prescrite peut-elle passer à vide (`null`, champ absent) ? +5. **Propagation** : chaque correction a-t-elle été reportée partout où le symptôme était écrit + (tâches T1-T9, tableau « Où regarder », Change Log, fiche mère, `sprint-status.yaml`) ? Greppe la + **valeur** (`code()`, `:98`, `937-990`, `latent`, `masqué`), pas la formulation. + +## Ce que tu rends + +- **Findings** : sévérité, endroit exact, **preuve** (commande et résultat, code lu), correction. +- ⛔ **La liste des axes réellement exercés ET de ceux qui ne l'ont pas été.** + +## Interdits + +⛔ N'écris aucun fichier du dépôt ; aucune commande qui écrit dans le dépôt ou dans une base — +`scripts/prepare-release.sh`, `scripts/regen-test-schema.sh`, `scripts/install-hooks.sh`, +`scripts/test-fast.sh`, `scripts/mem-guard.sh`, `make`, `latexmk`, tout `git commit`/`push`/`add`/ +`stash`/`reset`/`rebase`/`checkout`/`switch`/`worktree`, `sqlx migrate`, `cargo test`/`nextest`, +`npm run`, `npx playwright`. Autorisés : lecture, `grep`, `git log`/`show`/`diff`, `gh issue view`, +`cargo check`. From 9df357e7c22e07f47478a7d950818e19cadd72b5 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:15:42 +0200 Subject: [PATCH 12/34] =?UTF-8?q?docs(25-4-a):=20validation=20P3=20cibl?= =?UTF-8?q?=C3=A9e=20=E2=80=94=20propagation=20des=20corrections=20P1=20(r?= =?UTF-8?q?efs=20#455,=20refs=20#456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-residuel-juste.md | 44 ++++++++++++++----- .../25-4-propager-le-residuel.md | 12 ++--- .../sprint-status.yaml | 2 +- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md index bfa466fa..8e1d17a4 100644 --- a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md +++ b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md @@ -122,9 +122,10 @@ créance par l'écriture de l'avoir** (`credit_notes.journal_entry_id` → ligne seul HT ou à une valeur recalculée en Rust **ne suffit pas**. **AC 8** — Une facture validée à 8,1 %, **non réglée, créditée** : `amount_due` = **0** (et non la -TVA) — **au repository ET à la frontière HTTP** : `GET /api/v1/invoices/{id}` rend `amountDue` **non nul -(pas `null` : `null` veut dire « non calculé »,** `routes/invoices.rs:242-248`**)**, sérialisé en -**chaîne** (`rust_decimal` avec `serde-str`, `kesh-api/Cargo.toml:39`), dont la valeur **parsée en +TVA) — **au repository ET à la frontière HTTP** : `GET /api/v1/invoices/{id}` rend `amountDue` **présent et non `null`** +(`null` veut dire « non calculé », `routes/invoices.rs:242-248` ; ⛔ aucun `unwrap_or`, aucune +valeur par défaut dans l'assertion), sérialisé en +**chaîne** (sérialisation par défaut de `rust_decimal`, `kesh-api/Cargo.toml:39`), dont la valeur **parsée en `Decimal`** vaut zéro — ⛔ ni comparaison de chaîne (l'échelle suit le calcul SQL : `"0.0000"` et `"0.00"` sont également justes), ni `f64` (patron à ne pas suivre : `invoice_echeancier_e2e.rs:636-641`). Même règle pour `amountSettled`. Une facture validée à 8,1 %, **réglée de 40 puis créditée** (état **monté en SQL**, cf. @@ -142,7 +143,7 @@ transaction ; elle est **verrouillante** (`… FOR UPDATE` ou `LOCK IN SHARE MOD **avant** le verrou. **AC 10** — Le refus est une **variante dédiée** de `DbError`, pas `IllegalStateTransition` — même -motif que `InvoiceNotUnvalidatable` (`errors.rs:119-130`) : le générique ne dit ni ce qui bloque ni +motif que `InvoiceNotUnvalidatable` (`errors.rs:453`, raison écrite `:119-130`) : le générique ne dit ni ce qui bloque ni quoi faire. Elle porte l'identifiant du premier règlement trouvé (`Option`, `None` si seul `paid_at` est posé) et le numéro de la facture. Code machine **`CREDIT_NOTE_INVOICE_SETTLED`**, HTTP **409**, ajouté à `DbError::error_code()` (`errors.rs:641`) et à toute garde qui énumère les @@ -162,7 +163,10 @@ pouvoir émettre un avoir. » Registre du glossaire (`docs/i18n-glossaire.md` § `InvoiceCredited` **par le chemin que 25-4-a ferme**, avec un commentaire qui dit que l'état n'est **plus atteignable par l'application** depuis 25-4-a, mais **reste possible** par l'import d'une sauvegarde antérieure — ce qui justifie de garder `SettlementCancelBlocker::InvoiceCredited`. -⛔ Ne **pas** supprimer ce motif ni ses tests. +⛔ Ne **pas** supprimer ce motif ni ses tests. Ses doc-comments, qui le présentent comme un chemin +normal (« créditée par un avoir après ce règlement … pas une anomalie », `errors.rs:214-217` ; +« Hors `validated`, c'est donc un avoir », `invoice_settlements_write.rs:326-329`), disent +désormais que l'état n'est plus atteignable **que par l'import d'une sauvegarde antérieure**. ⚠️ **`monter` sert quinze cas, pas un** : `la_precedence_de_l_annulation_lecture_et_ecriture` (`:1056-1064`) l'appelle avec les cinq motifs seuls **et les dix paires** (`RANGS`, @@ -184,9 +188,11 @@ contrainte d'unicité d'`invoice_settlements` : `uq_invoice_settlements_entry (j (`20260827000001_invoice_settlements.sql:65`) — le détachement ne la touche pas. ⚠️ Pendant le détachement, l'écriture de règlement reste celle de la facture d'origine : aucun motif de `settlement_cancel_blocker` ne doit être évalué entre les deux `UPDATE`. Si le gabarit se révèle -impraticable, à défaut, construire l'écriture d'avoir par -`journal_entries::create_in_tx` avec les lignes de `generate_credit_note_journal_lines`, jamais à la -main ligne par ligne. +impraticable, **arrêter et demander** : le repli naturel — construire l'écriture d'avoir avec les +lignes de `generate_credit_note_journal_lines` — est **impossible depuis un test**, la fonction +étant privée (`credit_notes.rs:187`), et la rendre publique pour un test n'est pas un choix du +développeur. La facture auxiliaire est datée **dans l'exercice seedé** (`d − 20` suffit) : c'est ce +qui garde valide le cas « aucun exercice ouvert ». **AC 14** — Frontend : le bouton « Créer un avoir » (`invoices/[id]/+page.svelte:836`) **suit le précédent du bouton « Dévalider »** (`:864-871`) : la condition `!invoice.paidAt` est **retirée**, le @@ -196,7 +202,9 @@ une condition client qui en couvre une moitié laisse l'utilisateur sans rien à couvre entière se désynchronise à la première évolution du serveur. La garde étant désormais **unique** côté serveur (AC 11), le motif « payée » et le motif « réglée en partie » s'affichent de la même façon. Le commentaire de `:864-871` est étendu aux deux boutons, ou dupliqué au-dessus du -second. +second. ⚠️ Le commentaire du bloc Suspendre/Reprendre (`:842-844`) affirme que ces boutons sont +« masqués, comme les boutons voisins « Créer un avoir » / « Supprimer » » : il devient faux et se +corrige dans le même patch. ⚠️ **Changement visible sur une facture entièrement payée** : le bouton, masqué jusqu'ici, apparaît ; l'utilisateur ouvre le dialogue, confirme, et lit le refus nommé (AC 12) dans le dialogue même @@ -255,14 +263,17 @@ restreint aux lignes qui parlent d'avoir, plus le PDF aplati. Les manuels DE/IT/ - [ ] **T1 — formule de l'avoir TTC** (AC 1-5) : factoriser la formule de ligne, réécrire les deux constantes d'avoir, doc-comments. - [ ] **T2 — parité et concordance** (AC 6-8) : nouveau fichier de test sur le patron - `invoice_ttc_parity.rs` ; montage de l'état hérité en SQL. + `invoice_ttc_parity.rs`. ⚠️ Le helper `validated_invoice` (`invoice_settlement.rs:32`) est + **à 0 % en dur** et passe une écriture de vente **au HT** : les factures à 8,1 % se montent + par le **vrai chemin de validation**, sur le patron de `create_and_validate` + (`credit_notes_repository.rs`) ; l'état hérité, par le gabarit de l'AC 13. - [ ] **T3 — garde de l'avoir** (AC 9-11) : lecture verrouillante des règlements après le verrou, variante `DbError`, `error_code()`. - [ ] **T4 — API et i18n** (AC 12) : mapping, clé ×4 ; e2e 409. - [ ] **T5 — fixture `monter`** (AC 13) : gabarit « détacher, créditer, rattacher », commentaire ; **les quinze cas** de la précédence restent verts, dont les quatre paires avec `InvoiceCredited`. - [ ] **T6 — frontend** (AC 14) : condition du bouton retirée, commentaire, Vitest. -- [ ] **T7 — tests et mutations** (AC 16) : m1 à m6 exécutées, résultats consignés. +- [ ] **T7 — tests et mutations** (AC 16) : m1 à **m7** exécutées, résultats consignés. - [ ] **T8 — textes** (AC 17) : manuel, PDF aplati, CHANGELOG. - [ ] **T9 — gates** : backend complet (base remise à zéro), frontend complet, E2E complet. @@ -347,6 +358,17 @@ bloque **25-4-b**, pas celle-ci. ## Change Log +- **2026-09-27** — **validation P3 ciblée** (Opus, diff aplati des remédiations P1-P2, prompt + `25-4-a-validate-prompt-p3.md`) — **2 MEDIUM, 5 LOW**, tous de **propagation** des corrections P1, + tous confirmés par grep. MEDIUM : T7 disait encore « m1 à m6 » (m7 ajoutée) ; l'AC 14 rendait faux + le commentaire Suspendre/Reprendre (`:842-844`) et l'AC 13 laissait deux doc-comments décrire + l'avoir après règlement comme un chemin normal. LOW : « non nul » pour « non `null` » (et + `unwrap_or` interdit) ; repli de l'AC 13 impossible (fonction privée) — remplacé par « arrêter et + demander », date de la facture auxiliaire précisée ; cause de la sérialisation en chaîne ; + fiche mère et `sprint-status` en retard d'une passe ; T2 ne disait pas comment monter une facture + à 8,1 %. Le gabarit « détacher, créditer, rattacher » a été **exécuté mentalement** contre + `create_credit_note`, le rang 1 et les quatre paires : il tient. Référence de l'AC 10 corrigée + (`errors.rs:453`). Aucun code de production touché par ces corrections. - **2026-09-27** — **validation P2** (Haiku, diff aplati, prompt `25-4-a-validate-prompt-p2.md`) — rapporte 3 MEDIUM et 1 LOW ; **un seul MEDIUM retenu** : l'AC 13 ne disait pas d'où vient la facture auxiliaire du gabarit — précisé (helper `validated_invoice`, contrainte d'unicité vérifiée, aucun diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md index 471fbefd..b836637b 100644 --- a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -40,12 +40,12 @@ Le découpage suit l'ordre des dépendances, non celui des issues : (`invoice_email.rs:1140-1166`, `invoice_pdf_service::render`). Un client qui paie la relance en scannant la QR paie **à nouveau le montant entier**. Question posée à Guy (Q1 ci-dessous). 4. ⚠️ **#455 est masqué à l'écran — mais pas à l'API.** *(Corrigé en validation P1 de 25-4-a : la - première rédaction disait « latent ». `GET /invoices/{id}`, ouverte aux clés API, rend - `amountDue` sans condition de statut : sur une facture créditée à TVA non nulle, il vaut la TVA.)* Un avoir est **total** et fait passer la facture à - `cancelled` (`credit_notes.rs:560-563`) ; or les agrégats, le rapprochement et le règlement ne - lisent que des factures `validated`. Le terme « avoir » du résiduel n'est donc **non nul que sur - une facture annulée**, et le reste dû ne s'y affiche que si `amountSettled > 0` — c'est-à-dire - **seulement dans l'état que #456 permet de produire**. Les deux issues se corrigent ensemble. + première rédaction disait « latent ».)* L'avoir est **total** et fait passer la facture à + `cancelled` (`credit_notes.rs:560-575`) ; le terme « avoir » n'est donc non nul que sur une + facture annulée. L'**écran** n'y affiche le reste dû que si `amountSettled > 0` — l'état que #456 + permet de produire. Mais `GET /invoices/{id}`, ouverte aux clés API, rend `amountDue` **sans + condition de statut** : sur toute facture créditée à TVA non nulle, il vaut la TVA. Les deux + issues se corrigent ensemble parce qu'elles portent sur la même grandeur et le même état. 5. ⚠️ **Les formes jointes existent et ne servent nulle part.** `INVOICE_SETTLED_DERIVED_JOIN_SQL` et `INVOICE_CREDITED_DERIVED_JOIN_SQL` (`invoice_settlements.rs:40`, `:56`) n'ont aucun appelant, et le « test de parité » annoncé par le commentaire des lignes 27-29 **n'existe pas**. 25-4-b sera diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index cd62a835..04f53ab6 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -353,7 +353,7 @@ development_status: 25-3-c-annuler-facture-fournisseur: done # 2026-09-26 **REVUE CLOSE EN 1 PASSE** (0 > LOW, Sonnet x3) ; non poussee ; PR `closes #454` titre ET corps, sur accord. **IMPLEMENTEE** — gates 2472/2472, 783/783, E2E 220/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 **SPEC VALIDEE en 2 passes** (3 MED -> 0 > LOW). **SPECIFIEE** — `25-3-c-annuler-facture-fournisseur.md`. Geste par le socle (autorite `SupplierPurchase`), queue commune sur l'ecriture d'ACHAT, lot en dernier rang ; payee : reglement DETACHE (colonnes remises a NULL, ecriture libre, lien garde par l'audit) — **Q1 tranchee par Guy le 2026-09-26**. Aucune migration. Prochaine etape : validate. # 2026-09-24 NEE d'un arbitrage de Guy — une facture fournisseur s'annule DANS TOUS LES CAS, sauf si l'exercice de son ecriture d'achat est CLOS (l'exercice, pas le verrou de periode) ; payee, son reglement RESTE au grand livre et REDEVIENT A LETTRER (Epic 15 : un paiement doit correspondre a une facture, au besoin creee pour lui — « comme bexio »). Reecrit `cancel`, donc absorbe [#454]. Derriere la 25-3-a. 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* - 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation P1 (Sonnet) 1 HIGH / 3 MED / 1 LOW, corriges. + 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L (propagation), tous corriges. EN ATTENTE de l'arbitrage de Guy sur l'avoir (volet 2). 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. 25-4-propager-le-residuel: split # 2026-09-26 DECOUPEE (six modules > 5) en 25-4-a / 25-4-b / 25-4-c ; #455 et #456 rattachees. # [#416] [#420] — la 24-2 a introduit le reglement PARTIEL ; le residuel n'est propage ni a la balance agee ni a l'echeancier, et **le score de rapprochement compare au TTC et non au residuel** : le virement du solde d'une facture partiellement reglee **score 0**. From b18f7cd2ad1d5958e97005dd6a2893b963dadf4f Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:18:50 +0200 Subject: [PATCH 13/34] =?UTF-8?q?docs(25-4):=20avoir=20sur=20facture=20r?= =?UTF-8?q?=C3=A9gl=C3=A9e=20refus=C3=A9=20en=2025-4-a=20(lev=C3=A9e=20:?= =?UTF-8?q?=20#471)=20;=20#384=20ramen=C3=A9e=20en=2025-4-d=20(refs=20#384?= =?UTF-8?q?,=20refs=20#456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-residuel-juste.md | 13 ++++++++++++ .../25-4-propager-le-residuel.md | 21 +++++++++++++++---- .../sprint-status.yaml | 6 ++++-- .../epic-25-vague1-suite.md | 8 +++++-- 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md index 8e1d17a4..ff0af122 100644 --- a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md +++ b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md @@ -80,6 +80,17 @@ réglée** : `invoices/[id]/+page.svelte:836` n'exige que `!invoice.paidAt`. Le « Dévalider » a **retiré** cette condition pour cette raison même (commentaire `:864-871`) et laisse le serveur refuser avec un motif nommé ; « Créer un avoir » n'a pas été aligné. +## ✅ Arbitrage du 2026-09-27 — le refus reste, sa levée a son issue + +Guy : *« si on peut créer un avoir pour une facture payée, il faudrait aussi pouvoir le faire pour +une facture partiellement payée »* — les deux cas sont traités **de la même façon**, et, pour +l'instant, **dans le sens du refus** : un avoir sur facture encaissée laisse au client un crédit que +Kesh ne sait ni montrer, ni rembourser, ni imputer. La levée du refus est tracée par **[#471]**. Le +message de refus (AC 12) n'a donc pas à promettre une fonction à venir ; le manuel (AC 17) renvoie à +la limite existante « avoir sur facture déjà encaissée ». + +⚠️ **Ne pas contester l'arbitrage en revue** : en contester la mise en œuvre. + ## Acceptance Criteria ### Volet 1 — l'avoir compté TTC (#455) @@ -358,6 +369,7 @@ bloque **25-4-b**, pas celle-ci. ## Change Log +- **2026-09-27** — Arbitrage de Guy : le refus de l'avoir sur facture réglée (en tout ou en partie) reste ; sa levée est tracée par #471. - **2026-09-27** — **validation P3 ciblée** (Opus, diff aplati des remédiations P1-P2, prompt `25-4-a-validate-prompt-p3.md`) — **2 MEDIUM, 5 LOW**, tous de **propagation** des corrections P1, tous confirmés par grep. MEDIUM : T7 disait encore « m1 à m6 » (m7 ajoutée) ; l'AC 14 rendait faux @@ -398,5 +410,6 @@ bloque **25-4-b**, pas celle-ci. ne se voit que dans l'état que #456 produit — d'où leur réunion ; et la fixture `monter` encode #456 comme attendu. +[#471]: https://github.com/guycorbaz/kesh/issues/471 [#455]: https://github.com/guycorbaz/kesh/issues/455 [#456]: https://github.com/guycorbaz/kesh/issues/456 diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md index b836637b..65f7e7dc 100644 --- a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -1,4 +1,4 @@ -# Story 25.4 : Propager le résiduel — découpée en 25-4-a / 25-4-b / 25-4-c +# Story 25.4 : Propager le résiduel — découpée en 25-4-a / 25-4-b / 25-4-c / 25-4-d Status: split @@ -15,13 +15,14 @@ Inventaire des sites fait le 2026-09-26 (trois lectures du code, chaque site rev `kesh-i18n`, `frontend` (fiche facture, échéancier, dialogue de règlement). Le seuil est **5** : la règle impose le découpage **avant** `bmad-create-story`. -Le découpage suit l'ordre des dépendances, non celui des issues : +Le découpage (quatre stories depuis le 2026-09-27) suit l'ordre des dépendances, non celui des issues : | Story | Issues | Ce qu'elle fait | Pourquoi à ce rang | |---|---|---|---| | **25-4-a** — le résiduel juste | [#455], [#456] | la formule canonique soustrait l'avoir **TTC** ; un avoir est refusé sur une facture **réglée, même en partie** ; le test de parité promis et jamais écrit | ⛔ **les deux suivantes réemploient la formule** : la propager avant de la corriger propagerait le défaut | | **25-4-b** — le résiduel aux agrégats | [#416] | balance âgée, totaux de l'échéancier, **colonne « reste dû »** de l'échéancier et de son CSV, dialogue de règlement pré-rempli, **montant réclamé par les relances** | le cœur de #416 ; première utilisatrice des formes jointes | | **25-4-c** — le résiduel au rapprochement | [#420] | filtre des candidats, score, re-score à l'acceptation, montant affiché dans la proposition | indépendante de 25-4-b, mais du même socle | +| **25-4-d** — solder le reste | [#384] | imputer l'écart d'un règlement partiel — **perte sur débiteur**, escompte, frais bancaires — pour clore une facture partiellement réglée ; la part de **TVA** réduit la TVA due, seule la part HT va au compte de perte | ramenée de la 25-6 le 2026-09-27 (Guy) : elle repose entièrement sur le résiduel juste de 25-4-a | ## Ce que l'inventaire a trouvé, et que les issues ne disaient pas @@ -57,9 +58,19 @@ Le découpage suit l'ordre des dépendances, non celui des issues : `.expect("avoir après règlement partiel — le vrai chemin l'accepte")`. Il **décrit le défaut** ; il se corrige, il ne se contourne pas. -## Hors périmètre des trois stories +## ✅ Arbitrages du 2026-09-27 — l'avoir sur facture encaissée, et la perte + +- **L'avoir reste refusé sur une facture réglée, en tout ou en partie** (25-4-a). Un avoir sur + facture encaissée est un geste normal, mais il laisse au client un **crédit** que Kesh ne sait ni + montrer, ni rembourser, ni imputer : l'autoriser seul reproduirait la dette muette de #456. Sa + levée est tracée par **[#471]** (crédit visible, remboursement ou imputation). +- **#384 est ramenée dans la 25-4, en 25-4-d**, pour clore une facture partiellement réglée dont le + solde ne sera pas payé (perte sur débiteur), ou réglée sous déduction d'un escompte ou de frais. + ⚠️ La part de TVA du montant passé en perte **réduit la TVA due** (correction de l'impôt sur + créance irrécouvrable) ; seule la part HT va au compte de perte. + +## Hors périmètre des quatre stories -- **L'imputation de l'écart** d'un règlement partiel (escompte, frais, perte) — [#384], 25-6. - **Le côté fournisseur** : une facture fournisseur se règle en une fois, il n'y a pas de résiduel partiel (#416, § Portée). - **L'avoir partiel et le remboursement** — v0.2, inchangé. @@ -120,6 +131,7 @@ Pour 25-4-b, donc : ## Change Log +- **2026-09-27** — Arbitrages : avoir sur facture réglée refusé en 25-4-a, levée tracée par #471 ; #384 ramenée en 25-4-d. - **2026-09-27** — Frais de rappel : configurables, aucune ligne affichée à zéro. - **2026-09-27** — Q1 retranchée : la QR du rappel porte le reste dû (le TTC s'il n'y a aucun règlement) ; numéro de facture conservé ; point ouvert sur les frais. - **2026-09-27** — Recadrage de Q1 : le document est un rappel pour un montant partiel, pas la facture réémise. @@ -131,6 +143,7 @@ Pour 25-4-b, donc : (rattaché à 25-4-b). 25-4-a spécifiée dans la foulée. [#384]: https://github.com/guycorbaz/kesh/issues/384 +[#471]: https://github.com/guycorbaz/kesh/issues/471 [#387]: https://github.com/guycorbaz/kesh/issues/387 [#416]: https://github.com/guycorbaz/kesh/issues/416 [#420]: https://github.com/guycorbaz/kesh/issues/420 diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 04f53ab6..dbcd648d 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -1,3 +1,4 @@ +# last_updated: 2026-09-27 (1) (25-4-d creee depuis #384 ; #471 ouverte ; 25-4-a validee P3.) # last_updated: 2026-09-26 (5) (25-4 DECOUPEE en a/b/c ; 25-4-a creee, #455/#456 rattachees ; #467 #468 #470 mergees.) # last_updated: 2026-09-26 (4) (**25-3-c DONE** — revue close, gates verts, non poussee.) # last_updated: 2026-09-26 (3) (25-3-c IMPLEMENTEE, en review ; revue a posteriori de la 25-5-a close dans le worktree.) @@ -353,14 +354,15 @@ development_status: 25-3-c-annuler-facture-fournisseur: done # 2026-09-26 **REVUE CLOSE EN 1 PASSE** (0 > LOW, Sonnet x3) ; non poussee ; PR `closes #454` titre ET corps, sur accord. **IMPLEMENTEE** — gates 2472/2472, 783/783, E2E 220/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 **SPEC VALIDEE en 2 passes** (3 MED -> 0 > LOW). **SPECIFIEE** — `25-3-c-annuler-facture-fournisseur.md`. Geste par le socle (autorite `SupplierPurchase`), queue commune sur l'ecriture d'ACHAT, lot en dernier rang ; payee : reglement DETACHE (colonnes remises a NULL, ecriture libre, lien garde par l'audit) — **Q1 tranchee par Guy le 2026-09-26**. Aucune migration. Prochaine etape : validate. # 2026-09-24 NEE d'un arbitrage de Guy — une facture fournisseur s'annule DANS TOUS LES CAS, sauf si l'exercice de son ecriture d'achat est CLOS (l'exercice, pas le verrou de periode) ; payee, son reglement RESTE au grand livre et REDEVIENT A LETTRER (Epic 15 : un paiement doit correspondre a une facture, au besoin creee pour lui — « comme bexio »). Reecrit `cancel`, donc absorbe [#454]. Derriere la 25-3-a. 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* - 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L (propagation), tous corriges. EN ATTENTE de l'arbitrage de Guy sur l'avoir (volet 2). + 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L (propagation), tous corriges. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. + 25-4-d-solder-le-reste: backlog # [#384] ramenee de la 25-6 le 2026-09-27 (Guy) — perte sur debiteur, escompte, frais : clore une facture partiellement reglee ; la part TVA reduit la TVA due. 25-4-propager-le-residuel: split # 2026-09-26 DECOUPEE (six modules > 5) en 25-4-a / 25-4-b / 25-4-c ; #455 et #456 rattachees. # [#416] [#420] — la 24-2 a introduit le reglement PARTIEL ; le residuel n'est propage ni a la balance agee ni a l'echeancier, et **le score de rapprochement compare au TTC et non au residuel** : le virement du solde d'une facture partiellement reglee **score 0**. 25-5-a-export-souverainete: done # 2026-09-26 **MERGEE (PR #452, #386 fermee) SANS REVUE DE CODE** ; revue a posteriori CLOSE en 3 passes sur `story/25-5-a-revue-code` (4 HIGH P1, 1 HIGH orchestrateur P2 — colonnes manquantes, garde de colonnes —, P3 ciblee test seul) ; gates 2466/2466, E2E 221/8/19 sans regression ; reports #465, #466. # 2026-09-23 **SPECIFIEE et VALIDEE en 1 passe** (0 CRITICAL, 4 HIGH, 3 MEDIUM, 3 LOW, tous corriges). [#386] — issue du DECOUPAGE de la 25-5 (arbitrage de Guy) : #385 (Balance vs Bilan) et #386 n'ont en commun que le mot « etat », et touchent des modules disjoints. ⛔ **Trois faits qui corrigent l'issue** : 21 tables absentes et non 10 ; le trou s'est CREUSE depuis son ecriture (`invoice_settlements`, Epic 24) ; et SIX repositories sur huit n'ont aucune lecture exhaustive — le vrai cout, absent de l'issue. Arbitrage de Guy : l'export CSV ne porte que les donnees COMPTABLES (11 dedans), les backups portent tout ; 10 exclusions AVEC motif ecrit, dont 3 tables de SECRETS. Les justificatifs (fichiers) partent en story separee sur la SAUVEGARDE. ⛔ La vraie livraison est la GARDE D'EXHAUSTIVITE (patron D4-ter + P7), non les 11 exports : trois epics ont rouvert le trou en silence. ⚠️ Pieges releves par la validation : le nombre 19 code en dur a SEPT endroits dont deux `debug_assert_eq!` qui PANIQUENT ; `audit_log` n'a AUCUN dump exhaustif (ses fonctions sont bornees, la route rejette au-dela de 10 000 lignes) ; `csv_sanitize` existe et n'est pas employee par cet export ; deux sites de manuel DEJA faux (un dossier `reports/` invente, un « 38 » pour 39). 25-5-b-balance-solde-ouverture: backlog # [#385] — la **Balance des comptes ne concorde pas avec le Bilan**, il manque la colonne « solde d'ouverture » (III.4). Detachee de la 25-5 le 2026-09-23 : voir 25-5-a. 25-5-etats-et-export: split # [#385] [#386] — la **Balance des comptes ne concorde pas avec le Bilan**, il manque la colonne « solde d'ouverture » (III.4) ; et **l'export de souverainete omet les achats, les avoirs, les projets et la piste d'audit** (V.6) — *il promet plus qu'il ne tient*. - 25-6-tableau-de-bord-et-dettes: backlog # [#388] [#389] [#384] [#387] — la tuile « Dernieres ecritures » **n'appelle rien** et affiche toujours « Aucune ecriture » (*une tuile qui est un decor*) ; le « solde bancaire » est le solde COMPTABLE, donc faux ; imputer l'ECART d'un reglement partiel (escompte, frais bancaires, perte sur debiteur) ; et **le PDF d'une facture n'est jamais archive**, il est regenere a la volee (IV.9) — *un document reemis n'est pas le document envoye*. + 25-6-tableau-de-bord-et-dettes: backlog # [#388] [#389] [#387] (#384 ramenee en 25-4-d le 2026-09-27) — la tuile « Dernieres ecritures » **n'appelle rien** et affiche toujours « Aucune ecriture » (*une tuile qui est un decor*) ; le « solde bancaire » est le solde COMPTABLE, donc faux ; imputer l'ECART d'un reglement partiel (escompte, frais bancaires, perte sur debiteur) ; et **le PDF d'une facture n'est jamais archive**, il est regenere a la volee (IV.9) — *un document reemis n'est pas le document envoye*. epic-16: done # 2026-08-11 (nuit) MERGÉ — dernière story 16-3b via PR #296 (squash `593bb845`). #152, #144 et #151 fermées. v0.9.0 prête à taguer. # 2026-07-26 KICKOFF (rétro Epic 14, décision D-D ; GO Guy après publication de v0.8.0). Branche `story/16-1-compte-produit-par-ligne` créée sur main `ef6cdf52`. Séquence : 16-1 (#152 socle par ligne) → 16-2 (#144 défaut catalogue) → 16-3 (#151 reliquat PDF). # ÉLU PROCHAIN EPIC — kickoff dès le merge de la PR groupée Epic 14. Reste #144 (compte produit au catalogue) + #152 (compte produit par ligne) ; #151 (récap TVA facture) déjà mergé hors-bande via PR #267 (bb3c8d64, partiel : coord. émetteur / n° client restants). Capte aussi les CR utilisateurs #265/#266/#268. # Facturation avancée (#144/#152/#151) — Epic dédié acté 2026-06-04 # 2026-07-26 ORDRE INVERSÉ au kickoff (#152 avant #144). Motif ground-truth : `invoice_lines` ne # porte AUCUNE colonne de compte ET ne référence même pas `products` (pas de `product_id` — cf. diff --git a/_bmad-output/planning-artifacts/epic-25-vague1-suite.md b/_bmad-output/planning-artifacts/epic-25-vague1-suite.md index 95bded48..1802055b 100644 --- a/_bmad-output/planning-artifacts/epic-25-vague1-suite.md +++ b/_bmad-output/planning-artifacts/epic-25-vague1-suite.md @@ -211,6 +211,11 @@ propagé aux rapports agrégés — balance âgée et échéancier ([#416]) — rapprochement compare au TTC et non au résiduel**, si bien que le virement du solde d'une facture partiellement réglée **score 0** ([#420]). +⚠️ **Découpée le 2026-09-26 en 25-4-a / b / c, puis d** : [#455] et [#456] (même grandeur, même +jalon) y sont rattachées, et **[#384] y est ramenée de la 25-6 le 2026-09-27** (arbitrage de Guy) +en 25-4-d — solder le reste d'une facture partiellement réglée repose sur le résiduel juste. Détail : +`25-4-propager-le-residuel.md`. + ### 25-5 — Les états et l'export **Issues : [#385], [#386].** La **Balance des comptes ne concorde pas avec le Bilan** — il @@ -225,8 +230,7 @@ tient*. - La tuile **« Dernières écritures » n'appelle rien** et affiche toujours « Aucune écriture » ([#388]) — *une tuile qui est un décor*. - Le **« solde bancaire » est le solde comptable**, donc faux ([#389]). -- **Imputer l'écart d'un règlement partiel** — escompte, frais bancaires, perte sur débiteur - ([#384]). +- ~~**Imputer l'écart d'un règlement partiel**~~ — [#384] **ramenée en 25-4-d** le 2026-09-27. - **Le PDF d'une facture n'est jamais archivé**, il est régénéré à la volée ([#387], IV.9) — un document réémis n'est pas le document envoyé. From 5ed4a704b7bdf349c70ecb1a882428862c18c64c Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:19:05 +0200 Subject: [PATCH 14/34] =?UTF-8?q?docs(25-4-a):=20prompt=20de=20validation?= =?UTF-8?q?=20P4=20cibl=C3=A9e=20(refs=20#455,=20refs=20#456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-validate-prompt-p4.md | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 _bmad-output/implementation-artifacts/25-4-a-validate-prompt-p4.md diff --git a/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p4.md b/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p4.md new file mode 100644 index 00000000..047bdafb --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-a-validate-prompt-p4.md @@ -0,0 +1,58 @@ +# Prompt — validation P4 CIBLÉE, Story 25-4-a (le résiduel juste) + +*Versionné le 2026-09-27. **Une lentille** (Sonnet), contexte frais. Passe **ciblée** (CLAUDE.md +§ « La passe ciblée ») : la P3 n'a trouvé que des défauts **de propagation des corrections P1** ; ce qu'il reste à +relire est la remédiation, pas la story entière.* + +Dépôt `/home/gcorbaz/devel/kesh`, branche `story/25-4-propager-le-residuel`. Fiche : +`_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md`. **Périmètre** : le diff aplati +des remédiations P3 et de l'arbitrage du 2026-09-27 — +`git diff 1344b3a2 b18f7cd2 -- _bmad-output/implementation-artifacts/25-4-a-residuel-juste.md _bmad-output/implementation-artifacts/25-4-propager-le-residuel.md` +(ignore les paragraphes de la fiche mère qui consignent les arbitrages de Guy sur 25-4-b : **ne pas +les contester**). + +## La lentille : chasseur de régressions de remédiation + +Pour chaque passage réécrit en P1 ou P2 : + +1. **Est-il vrai contre le code ?** Relis les sites qu'il cite : `get_invoice` + (`crates/kesh-api/src/routes/invoices.rs`), `docs/api-external.md`, le bouton et le dialogue + d'avoir (`frontend/src/routes/(app)/invoices/[id]/+page.svelte`), la fixture `monter` et + `la_precedence_de_l_annulation_lecture_et_ecriture` (`crates/kesh-db/tests/invoice_settlement.rs`), + la migration `20260827000001_invoice_settlements.sql`, `settlement_cancel_blocker` + (`invoice_settlements_write.rs`), `kesh-api/Cargo.toml` (`serde-str`). +2. **Le gabarit « détacher, créditer, rattacher » (AC 13)** : exécute-le **mentalement** pas à pas + contre `create_credit_note` et les quatre paires combinées de `monter`. Une FK, une contrainte, + un audit, un contrôle de `company_id`, une lecture de `invoice_settlements` par + `create_credit_note` ou par les motifs montés **après** peut-il le casser ? L'écriture de + règlement porte-t-elle quelque part l'identifiant de la facture d'origine (description, audit, + `invoice_settlements` seul) ? +3. **AC 14 révisé** : cohérent avec l'AC 11, l'AC 12, les tests de l'AC 16 et la mutation m6/m7 ? + Un test de l'AC 16 porte-t-il encore l'ancienne logique (« bouton absent ») ? +4. **AC 8 révisé** : l'assertion prescrite peut-elle passer à vide (`null`, champ absent) ? +5. **Propagation** : chaque correction a-t-elle été reportée partout où le symptôme était écrit + (tâches T1-T9, tableau « Où regarder », Change Log, fiche mère, `sprint-status.yaml`) ? Greppe la + **valeur** (`code()`, `:98`, `937-990`, `latent`, `masqué`), pas la formulation. + +## Ce que tu rends + +- **Findings** : sévérité, endroit exact, **preuve** (commande et résultat, code lu), correction. +- ⛔ **La liste des axes réellement exercés ET de ceux qui ne l'ont pas été.** + +## Interdits + +⛔ N'écris aucun fichier du dépôt ; aucune commande qui écrit dans le dépôt ou dans une base — +`scripts/prepare-release.sh`, `scripts/regen-test-schema.sh`, `scripts/install-hooks.sh`, +`scripts/test-fast.sh`, `scripts/mem-guard.sh`, `make`, `latexmk`, tout `git commit`/`push`/`add`/ +`stash`/`reset`/`rebase`/`checkout`/`switch`/`worktree`, `sqlx migrate`, `cargo test`/`nextest`, +`npm run`, `npx playwright`. Autorisés : lecture, `grep`, `git log`/`show`/`diff`, `gh issue view`, +`cargo check`. + +## Complément P4 + +Le diff contient aussi l'**arbitrage du 2026-09-27** (refus de l'avoir maintenu, levée tracée par +#471 ; #384 ramenée en 25-4-d) : **ne pas le contester**, mais vérifier que la fiche 25-4-a reste +cohérente avec lui (AC 12, AC 17, hors périmètre de la fiche mère), et que la fiche de l'epic et +`sprint-status.yaml` disent la même chose. Vérifie aussi que les corrections P3 (T7 « m1 à m7 », +commentaire `:842-844`, doc-comments `errors.rs:214-217` et `invoice_settlements_write.rs:326-329`, +repli « arrêter et demander », T2 et le montage à 8,1 %) sont justes et ne contredisent rien. From ab9b9448ae0320602aec32a7f3cceeb930e29106 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 00:31:45 +0200 Subject: [PATCH 15/34] =?UTF-8?q?docs(25-4-a):=20validation=20P4=20?= =?UTF-8?q?=E2=80=94=20close=20en=204=20passes,=20pr=C3=AAte=20pour=20le?= =?UTF-8?q?=20dev=20(refs=20#455,=20refs=20#456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-residuel-juste.md | 16 ++++++++++++++-- .../implementation-artifacts/sprint-status.yaml | 4 ++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md index ff0af122..5cacd437 100644 --- a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md +++ b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md @@ -176,7 +176,7 @@ pouvoir émettre un avoir. » Registre du glossaire (`docs/i18n-glossaire.md` § sauvegarde antérieure — ce qui justifie de garder `SettlementCancelBlocker::InvoiceCredited`. ⛔ Ne **pas** supprimer ce motif ni ses tests. Ses doc-comments, qui le présentent comme un chemin normal (« créditée par un avoir après ce règlement … pas une anomalie », `errors.rs:214-217` ; -« Hors `validated`, c'est donc un avoir », `invoice_settlements_write.rs:326-329`), disent +« Hors `validated`, c'est donc un avoir », `invoice_settlements_write.rs:327-330`), disent désormais que l'état n'est plus atteignable **que par l'import d'une sauvegarde antérieure**. ⚠️ **`monter` sert quinze cas, pas un** : `la_precedence_de_l_annulation_lecture_et_ecriture` @@ -309,7 +309,8 @@ restreint aux lignes qui parlent d'avoir, plus le PDF aplati. Les manuels DE/IT/ | `crates/kesh-db/src/repositories/invoice_settlements.rs:24-58, 122-145` | les quatre constantes, `amount_due` | | `crates/kesh-db/src/repositories/invoices.rs:155-190` | `INVOICE_TTC_SUBQUERY_SQL` / `…_DERIVED_JOIN_SQL`, `total_ttc` | | `crates/kesh-db/src/repositories/credit_notes.rs:187-257, 260-330, 510-575` | écriture de l'avoir, garde AC2bis, `total_amount`, bascule `cancelled` | -| `crates/kesh-db/src/errors.rs:119-175, 300-470, 641-670` | `UnvalidationBlocker`, `DbError`, `error_code()` | +| `crates/kesh-db/src/errors.rs:119-175, 205-260, 300-470, 641-670` | `UnvalidationBlocker`, `SettlementCancelBlocker` (doc d'`InvoiceCredited` `:214-217`), `DbError`, `error_code()` | +| `crates/kesh-db/src/repositories/invoice_settlements_write.rs:60-72, 320-333` | verrou de `settle_invoice` ; rang 1 de `settlement_cancel_blocker` (commentaire `:327-330`, AC 13) | | `crates/kesh-db/src/repositories/invoices.rs:1430-1448` | la garde de dévalidation, patron de l'AC 9 | | `crates/kesh-api/src/errors.rs:2515-2560, 2741-2748` | mapping `InvoiceNotUnvalidatable`, générique | | `crates/kesh-api/src/routes/credit_notes.rs:183-205` | handler de création | @@ -369,6 +370,17 @@ bloque **25-4-b**, pas celle-ci. ## Change Log +- **2026-09-27** — **validation P4 ciblée** (Sonnet, diff `1344b3a2..b18f7cd2`, prompt + `25-4-a-validate-prompt-p4.md`) — rapporte **1 MEDIUM, 3 LOW**, tous confirmés et corrigés. + **MEDIUM reclassé LOW** par l'orchestrateur : `invoice_settlements_write.rs` manquait au tableau + « Où regarder », mais l'AC 13 qui prescrit son édition le **nomme avec sa ligne** — une omission de + navigation, sans risque qu'une prescription soit perdue. LOW : citation `:326-329` → `:327-330` ; + plage d'`errors.rs` étendue à `SettlementCancelBlocker` ; commentaire de la ligne mère de + `sprint-status` resté à trois stories. Le gabarit de l'AC 13, l'AC 8 et l'AC 14 revérifiés sans + défaut ; la sérialisation en chaîne confirmée dans la source de `rust_decimal` 1.42.1. + ⛔ **Boucle de validation CLOSE en 4 passes** : `1H/3M/1L → 1M → 2M/5L → 0 > LOW` (après + reclassement), rotation Sonnet → Haiku → Opus → Sonnet, deux passes ciblées ; toutes les + corrections ont porté sur la spec, aucune sur du code. **Prête pour le dev.** - **2026-09-27** — Arbitrage de Guy : le refus de l'avoir sur facture réglée (en tout ou en partie) reste ; sa levée est tracée par #471. - **2026-09-27** — **validation P3 ciblée** (Opus, diff aplati des remédiations P1-P2, prompt `25-4-a-validate-prompt-p3.md`) — **2 MEDIUM, 5 LOW**, tous de **propagation** des corrections P1, diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index dbcd648d..daf27cdd 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -354,11 +354,11 @@ development_status: 25-3-c-annuler-facture-fournisseur: done # 2026-09-26 **REVUE CLOSE EN 1 PASSE** (0 > LOW, Sonnet x3) ; non poussee ; PR `closes #454` titre ET corps, sur accord. **IMPLEMENTEE** — gates 2472/2472, 783/783, E2E 220/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 **SPEC VALIDEE en 2 passes** (3 MED -> 0 > LOW). **SPECIFIEE** — `25-3-c-annuler-facture-fournisseur.md`. Geste par le socle (autorite `SupplierPurchase`), queue commune sur l'ecriture d'ACHAT, lot en dernier rang ; payee : reglement DETACHE (colonnes remises a NULL, ecriture libre, lien garde par l'audit) — **Q1 tranchee par Guy le 2026-09-26**. Aucune migration. Prochaine etape : validate. # 2026-09-24 NEE d'un arbitrage de Guy — une facture fournisseur s'annule DANS TOUS LES CAS, sauf si l'exercice de son ecriture d'achat est CLOS (l'exercice, pas le verrou de periode) ; payee, son reglement RESTE au grand livre et REDEVIENT A LETTRER (Epic 15 : un paiement doit correspondre a une facture, au besoin creee pour lui — « comme bexio »). Reecrit `cancel`, donc absorbe [#454]. Derriere la 25-3-a. 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* - 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L (propagation), tous corriges. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. + 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. 25-4-d-solder-le-reste: backlog # [#384] ramenee de la 25-6 le 2026-09-27 (Guy) — perte sur debiteur, escompte, frais : clore une facture partiellement reglee ; la part TVA reduit la TVA due. - 25-4-propager-le-residuel: split # 2026-09-26 DECOUPEE (six modules > 5) en 25-4-a / 25-4-b / 25-4-c ; #455 et #456 rattachees. # [#416] [#420] — la 24-2 a introduit le reglement PARTIEL ; le residuel n'est propage ni a la balance agee ni a l'echeancier, et **le score de rapprochement compare au TTC et non au residuel** : le virement du solde d'une facture partiellement reglee **score 0**. + 25-4-propager-le-residuel: split # 2026-09-26 DECOUPEE (six modules > 5) en 25-4-a / 25-4-b / 25-4-c, puis 25-4-d (#384, 2026-09-27) ; #455 et #456 rattachees. # [#416] [#420] — la 24-2 a introduit le reglement PARTIEL ; le residuel n'est propage ni a la balance agee ni a l'echeancier, et **le score de rapprochement compare au TTC et non au residuel** : le virement du solde d'une facture partiellement reglee **score 0**. 25-5-a-export-souverainete: done # 2026-09-26 **MERGEE (PR #452, #386 fermee) SANS REVUE DE CODE** ; revue a posteriori CLOSE en 3 passes sur `story/25-5-a-revue-code` (4 HIGH P1, 1 HIGH orchestrateur P2 — colonnes manquantes, garde de colonnes —, P3 ciblee test seul) ; gates 2466/2466, E2E 221/8/19 sans regression ; reports #465, #466. # 2026-09-23 **SPECIFIEE et VALIDEE en 1 passe** (0 CRITICAL, 4 HIGH, 3 MEDIUM, 3 LOW, tous corriges). [#386] — issue du DECOUPAGE de la 25-5 (arbitrage de Guy) : #385 (Balance vs Bilan) et #386 n'ont en commun que le mot « etat », et touchent des modules disjoints. ⛔ **Trois faits qui corrigent l'issue** : 21 tables absentes et non 10 ; le trou s'est CREUSE depuis son ecriture (`invoice_settlements`, Epic 24) ; et SIX repositories sur huit n'ont aucune lecture exhaustive — le vrai cout, absent de l'issue. Arbitrage de Guy : l'export CSV ne porte que les donnees COMPTABLES (11 dedans), les backups portent tout ; 10 exclusions AVEC motif ecrit, dont 3 tables de SECRETS. Les justificatifs (fichiers) partent en story separee sur la SAUVEGARDE. ⛔ La vraie livraison est la GARDE D'EXHAUSTIVITE (patron D4-ter + P7), non les 11 exports : trois epics ont rouvert le trou en silence. ⚠️ Pieges releves par la validation : le nombre 19 code en dur a SEPT endroits dont deux `debug_assert_eq!` qui PANIQUENT ; `audit_log` n'a AUCUN dump exhaustif (ses fonctions sont bornees, la route rejette au-dela de 10 000 lignes) ; `csv_sanitize` existe et n'est pas employee par cet export ; deux sites de manuel DEJA faux (un dossier `reports/` invente, un « 38 » pour 39). 25-5-b-balance-solde-ouverture: backlog # [#385] — la **Balance des comptes ne concorde pas avec le Bilan**, il manque la colonne « solde d'ouverture » (III.4). Detachee de la 25-5 le 2026-09-23 : voir 25-5-a. 25-5-etats-et-export: split # [#385] [#386] — la **Balance des comptes ne concorde pas avec le Bilan**, il manque la colonne « solde d'ouverture » (III.4) ; et **l'export de souverainete omet les achats, les avoirs, les projets et la piste d'audit** (V.6) — *il promet plus qu'il ne tient*. From 9bf2bb2b5c992e107e70d39849daa76408358ef9 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:02:05 +0200 Subject: [PATCH 16/34] =?UTF-8?q?feat(25-4-a):=20le=20reste=20d=C3=BB=20co?= =?UTF-8?q?mpte=20l'avoir=20TTC,=20et=20l'avoir=20est=20refus=C3=A9=20sur?= =?UTF-8?q?=20une=20facture=20r=C3=A9gl=C3=A9e=20(refs=20#455,=20refs=20#4?= =?UTF-8?q?56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 + .../25-4-a-residuel-juste.md | 111 ++++++- .../sprint-status.yaml | 2 +- crates/kesh-api/src/errors.rs | 30 ++ .../kesh-api/tests/invoice_echeancier_e2e.rs | 113 +++++++ crates/kesh-db/src/errors.rs | 34 +- .../kesh-db/src/repositories/credit_notes.rs | 37 ++- .../src/repositories/invoice_settlements.rs | 47 ++- .../repositories/invoice_settlements_write.rs | 3 + crates/kesh-db/src/repositories/invoices.rs | 43 ++- .../kesh-db/tests/credit_notes_repository.rs | 239 +++++++++++++- .../tests/invoice_amount_due_parity.rs | 311 ++++++++++++++++++ crates/kesh-db/tests/invoice_settlement.rs | 27 +- crates/kesh-i18n/locales/de-CH/messages.ftl | 1 + crates/kesh-i18n/locales/en-CH/messages.ftl | 1 + crates/kesh-i18n/locales/fr-CH/messages.ftl | 1 + crates/kesh-i18n/locales/it-CH/messages.ftl | 1 + docs/manual/fr/user-manual.pdf | Bin 290281 -> 292120 bytes docs/manual/fr/user-manual.tex | 21 +- .../routes/(app)/invoices/[id]/+page.svelte | 30 +- .../[id]/invoice-settlements-page.test.ts | 38 ++- 21 files changed, 1042 insertions(+), 54 deletions(-) create mode 100644 crates/kesh-db/tests/invoice_amount_due_parity.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ca07fcb0..1d6e160a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,14 @@ Le contenu est rédigé en français à destination des **fiduciaires, PME, ind - **Sur la fiche d'une facture fournisseur, le bouton *Annuler la facture* s'affichait aussi pour un utilisateur en consultation**, qui ne pouvait que se heurter à un refus ; et un refus effaçait toute la fiche au profit d'un message d'erreur. Le bouton est désormais réservé aux comptables et administrateurs, et un refus s'affiche sous le bouton, la fiche restant visible. +- **Une facture créditée gardait sa TVA en « reste dû » ([#455](https://github.com/guycorbaz/kesh/issues/455)).** Le reste dû retranchait l'avoir **hors taxes** d'un total **TTC** : une facture de 100.— HT à 8,1 %, entièrement créditée, restait due de 8.10, alors que le grand livre, lui, avait soldé la créance. L'écran ne l'affichait que dans un cas rare, mais ⚠️ **l'API le rendait toujours** : `GET /api/v1/invoices/{id}`, ouverte aux clés API en lecture, renvoyait la TVA dans `amountDue` pour toute facture créditée. **Si une intégration lisait ce champ, ses chiffres étaient faux.** Le reste dû compte désormais l'avoir TTC, arrondi ligne par ligne comme son écriture. + +- **Un avoir pouvait viser une facture déjà réglée en partie ([#456](https://github.com/guycorbaz/kesh/issues/456)), et le compte du client devenait créditeur sans que rien ne le signale.** L'avoir était refusé sur une facture **payée**, mais pas sur une facture réglée **en partie** — qui, depuis le règlement partiel, n'est « payée » qu'une fois soldée. Comme l'avoir annule tout le montant de la facture, le client se retrouvait avec un crédit du montant déjà encaissé, que Kesh ne sait ni montrer, ni rembourser. L'avoir est désormais refusé dès qu'un règlement existe, avec un message qui dit quoi faire : annuler d'abord le règlement. *(Autoriser l'avoir sur une facture encaissée, avec le remboursement ou l'imputation qu'il appelle, est suivi par [#471](https://github.com/guycorbaz/kesh/issues/471).)* + ### Changed +- **Le bouton *Créer un avoir* s'affiche aussi sur une facture payée**, et c'est voulu. Il était masqué sur une facture payée mais pas sur une facture réglée en partie : l'écran ne couvrait que la moitié de la règle. Il reste désormais visible, et Kesh explique le refus dans le dialogue — comme le bouton *Dévalider*. + - **Supprimer une facture validée n'est plus possible — il faut la « dévalider » d'abord.** Jusqu'ici, le bouton *Supprimer* d'une facture validée effaçait la facture **et son écriture comptable** d'un seul geste. La destruction d'une écriture était donc un **effet de bord du mot « supprimer »** : rien, dans le vocabulaire de l'écran, ne disait qu'on touchait aux livres. Le geste existe toujours, mais il porte désormais son nom. Le bouton **« Dévalider »** repasse la facture en **brouillon**, supprime son écriture, et **conserve son numéro** — en la revalidant, elle reprend *le même*, sans consommer le compteur. Deux sorties s'ouvrent alors : corriger puis revalider, ou supprimer le brouillon. diff --git a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md index 5cacd437..82b5e340 100644 --- a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md +++ b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md @@ -1,6 +1,6 @@ # Story 25.4-a : Le résiduel juste — l'avoir compté TTC, et refusé sur une facture réglée -Status: ready-for-dev +Status: review **Issues : [#455], [#456]**, qu'elle **ferme** : `closes #455, closes #456` dans le **titre ET le corps** de la PR (squash ; un `refs` partout laisserait les issues ouvertes sans signal). @@ -271,22 +271,22 @@ restreint aux lignes qui parlent d'avoir, plus le PDF aplati. Les manuels DE/IT/ ## Tasks / Subtasks -- [ ] **T1 — formule de l'avoir TTC** (AC 1-5) : factoriser la formule de ligne, réécrire les deux +- [x] **T1 — formule de l'avoir TTC** (AC 1-5) : factoriser la formule de ligne, réécrire les deux constantes d'avoir, doc-comments. -- [ ] **T2 — parité et concordance** (AC 6-8) : nouveau fichier de test sur le patron +- [x] **T2 — parité et concordance** (AC 6-8) : nouveau fichier de test sur le patron `invoice_ttc_parity.rs`. ⚠️ Le helper `validated_invoice` (`invoice_settlement.rs:32`) est **à 0 % en dur** et passe une écriture de vente **au HT** : les factures à 8,1 % se montent par le **vrai chemin de validation**, sur le patron de `create_and_validate` (`credit_notes_repository.rs`) ; l'état hérité, par le gabarit de l'AC 13. -- [ ] **T3 — garde de l'avoir** (AC 9-11) : lecture verrouillante des règlements après le verrou, +- [x] **T3 — garde de l'avoir** (AC 9-11) : lecture verrouillante des règlements après le verrou, variante `DbError`, `error_code()`. -- [ ] **T4 — API et i18n** (AC 12) : mapping, clé ×4 ; e2e 409. -- [ ] **T5 — fixture `monter`** (AC 13) : gabarit « détacher, créditer, rattacher », commentaire ; +- [x] **T4 — API et i18n** (AC 12) : mapping, clé ×4 ; e2e 409. +- [x] **T5 — fixture `monter`** (AC 13) : gabarit « détacher, créditer, rattacher », commentaire ; **les quinze cas** de la précédence restent verts, dont les quatre paires avec `InvoiceCredited`. -- [ ] **T6 — frontend** (AC 14) : condition du bouton retirée, commentaire, Vitest. -- [ ] **T7 — tests et mutations** (AC 16) : m1 à **m7** exécutées, résultats consignés. -- [ ] **T8 — textes** (AC 17) : manuel, PDF aplati, CHANGELOG. -- [ ] **T9 — gates** : backend complet (base remise à zéro), frontend complet, E2E complet. +- [x] **T6 — frontend** (AC 14) : condition du bouton retirée, commentaire, Vitest. +- [x] **T7 — tests et mutations** (AC 16) : m1 à **m7** exécutées, résultats consignés. +- [x] **T8 — textes** (AC 17) : manuel, PDF aplati, CHANGELOG. +- [x] **T9 — gates** : backend complet (base remise à zéro), frontend complet, E2E complet. ## Dev Notes @@ -362,14 +362,105 @@ bloque **25-4-b**, pas celle-ci. ### Agent Model Used +Claude Opus 5.5. + ### Debug Log References +- `settled_and_credited_forms_are_at_parity` attendait **huit** factures ; il y en a **sept** (six du jeu + + la facture auxiliaire du gabarit). Erreur de décompte dans le test, pas dans le code — corrigée, + et l'assertion anti-vacuité garde des valeurs **non nulles** des deux côtés. +- `SUM(next_number)` rend un `DECIMAL` sous MariaDB : `CAST(… AS SIGNED)` dans l'empreinte du test de + refus. +- Un doc-comment égaré (« Paramètres de recherche, tri et pagination. ») précédait + `INVOICE_TTC_SUBQUERY_SQL` : remis au-dessus d'`InvoiceListQuery`, qu'il décrit. +- ⚠️ **Relevé hors périmètre, non corrigé** : la clé voisine `error-invoice-unvalidate-blocked-settled` + (it-CH) dit « annullatelo » — forme *voi*, contraire au registre du glossaire (2ᵉ personne du + singulier). La nouvelle clé suit le glossaire (« annullalo »). + ### Completion Notes List +- **Formule du TTC d'une ligne, source unique** (AC 5) : macro `line_ttc_sql!` (`invoices.rs`), + rendant un littéral via `concat!` ; les **quatre** constantes en dérivent (TTC facture scalaire et + jointe, avoir scalaire et joint), qui restent des `&'static str`. +- **Avoir compté TTC** (AC 1-4) : les deux constantes d'avoir somment `credit_note_lines` (jointes à + `credit_notes`, `status = 'issued'`), arrondi ligne par ligne ; doc-comments sur l'unité et sur + `total_amount` HT. Aucune migration. +- **Parité et concordance** (AC 6-8) : `tests/invoice_amount_due_parity.rs`, 4 tests, **tous à TVA + non nulle** ; factures par le vrai chemin de validation ; l'état hérité par le gabarit « détacher, + créditer, rattacher ». Le crédit de l'avoir égale le crédit de la créance **dans l'écriture** + (1207.71 sur trois lignes à 8,1 % / 2,6 % / sous-centime), et le test s'assure que `total_amount` + (HT) en diffère — sans quoi il ne distinguerait rien. +- **Garde de l'avoir** (AC 9-11) : lecture **verrouillante** des règlements, **après** le verrou de la + facture ; une ligne **ou** `paid_at` ; variante `DbError::CreditNoteBlockedBySettlement` + (`CREDIT_NOTE_INVOICE_SETTLED`), qui remplace aussi le générique du cas « payée ». +- **API et i18n** (AC 12) : 409, message suffixé du numéro de facture, `details { invoiceId, + settlementId }` ; clé `error-credit-note-blocked-settled` ×4. +- **Fixture `monter`** (AC 13) : gabarit « détacher, créditer, rattacher » ; les **quinze cas** de la + précédence restent verts. Doc-comments de `InvoiceCredited` et du rang 1 : état hérité. +- **Frontend** (AC 14) : condition `!invoice.paidAt` retirée du bouton, commentaires Suspendre/Reprendre + corrigé ; `data-testid` sur le bouton, la confirmation et l'erreur du dialogue. +- **Textes** (AC 17) : manuel (refus nommé en § Avoirs, deux limites reformulées « réglée, même en + partie », deux motifs d'annulation de règlement précisés comme **cas hérité**), PDF régénéré et + contrôlé aplati (0 `??`, six phrases de contrôle présentes, « marquée payée » absente) ; CHANGELOG + *Fixed* ×2 (dont **l'API** pour #455) et *Changed* (le bouton). Grep large du symptôme « avoir sur + facture payée » : aucun autre site. + +**Tests ajoutés** (recomptés depuis la source, `main` → arbre de travail) : `invoice_amount_due_parity` +0 → 4, `credit_notes_repository` 6 → 9 (dont `credit_note_refused_on_paid_invoice` réécrit), +`invoice_echeancier_e2e` 11 → 13, Vitest `invoice-settlements-page.test.ts` 2 → 5. + +**Mutations** (résultat **observé**, chaque fichier restauré après) : + +| Mutation | Tests rouges | +|---|---| +| m1 — avoir scalaire en `SUM(cn.total_amount)` | 4 / 4 de `invoice_amount_due_parity` | +| m2 — avoir joint seul en `SUM(total_amount)` | `settled_and_credited_forms_are_at_parity` | +| m3 — garde sur `paid_at` seul | `…refused_on_partially_settled_invoice`, `…accepted_after_the_settlement_is_cancelled`, `…waits_for_a_concurrent_settlement` | +| m4 — garde sur la ligne seule | `credit_note_refused_on_paid_invoice` | +| m5 — lecture avant le verrou, non verrouillante | `credit_note_waits_for_a_concurrent_settlement` | +| m6 — `!invoice.paidAt` remis sur le bouton | 2 Vitest (bouton sur facture payée ; refus dans le dialogue) | +| m7 — code mappé sur `ILLEGAL_STATE_TRANSITION` | `credit_note_on_partially_settled_invoice_is_409` | + +### Gates + +- Backend : base remise à zéro (tmpfs + migrations + seed), `scripts/test-fast.sh` (fmt + clippy + + nextest) **2484 / 2484** (2475 + 9). +- Frontend : `check` 0 erreur (27 avertissements), `lint-i18n-ownership` PASS, `test:unit` + **826 / 826** (823 + 3), build OK. +- E2E : `kesh_e2e` reconstruite, montage complet (`smtpConfigured:true`, répertoires inbox et documents), + frontend buildé après le dernier patch, run à **22:52 UTC** : **222 passés / 10 échoués / 19 + ignorés**. Les 7 de KF-029 ; les 3 autres — `product-revenue-account:133`, `sidebar-navigation:75` + (pollution documentée) et ⚠️ `accounts.spec.ts:185` (**non documenté**) — passent **rejoués seuls, + 18 / 18**, et ne touchent aucun écran de la story. Trois pollutions dans un même run, là où la + baseline en compte une : à surveiller, sans régression de la story. + ### File List +| Fichier | Nature | +|---|---| +| `crates/kesh-db/src/repositories/invoices.rs` | macro `line_ttc_sql!`, constantes TTC recomposées, doc-comment replacé | +| `crates/kesh-db/src/repositories/invoice_settlements.rs` | avoir TTC (scalaire, joint), doc d'`amount_due` | +| `crates/kesh-db/src/repositories/credit_notes.rs` | garde : lecture verrouillante des règlements | +| `crates/kesh-db/src/errors.rs` | `CreditNoteBlockedBySettlement`, `error_code()`, doc d'`InvoiceCredited` | +| `crates/kesh-db/src/repositories/invoice_settlements_write.rs` | commentaire du rang 1 | +| `crates/kesh-api/src/errors.rs` | mapping 409 | +| `crates/kesh-i18n/locales/{fr,de,it,en}-CH/messages.ftl` | `error-credit-note-blocked-settled` | +| `crates/kesh-db/tests/invoice_amount_due_parity.rs` | **nouveau** — parité, concordance, zéro, hérité | +| `crates/kesh-db/tests/credit_notes_repository.rs` | refus payée réécrit ; partiel, réouverture, entrelacement | +| `crates/kesh-db/tests/invoice_settlement.rs` | `monter` : gabarit « détacher, créditer, rattacher » | +| `crates/kesh-api/tests/invoice_echeancier_e2e.rs` | e2e : `amountDue` nul, 409 | +| `frontend/src/routes/(app)/invoices/[id]/+page.svelte` | bouton, commentaires, `data-testid` | +| `frontend/src/routes/(app)/invoices/[id]/invoice-settlements-page.test.ts` | 3 Vitest | +| `docs/manual/fr/user-manual.tex` / `.pdf` | avoirs, limites, cas hérité | +| `CHANGELOG.md` | *Fixed* ×2, *Changed* | + ## Change Log +- **2026-09-27** — **dev** (Opus 5.5) : formule du TTC de ligne factorisée (`line_ttc_sql!`), avoir + compté TTC, garde verrouillante de l'avoir sur facture réglée, variante `CREDIT_NOTE_INVOICE_SETTLED` + ×4 locales, fixture `monter` par le gabarit, bouton aligné sur « Dévalider », manuel et CHANGELOG. + +9 tests Rust, +3 Vitest ; **7 / 7 mutations tuées**. Gates : backend 2484/2484, frontend 826/826, + E2E 222/10/19 sans régression (7 KF-029 + 3 pollutions, rejouées seules vertes). - **2026-09-27** — **validation P4 ciblée** (Sonnet, diff `1344b3a2..b18f7cd2`, prompt `25-4-a-validate-prompt-p4.md`) — rapporte **1 MEDIUM, 3 LOW**, tous confirmés et corrigés. **MEDIUM reclassé LOW** par l'orchestrateur : `invoice_settlements_write.rs` manquait au tableau diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index daf27cdd..7502347b 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -354,7 +354,7 @@ development_status: 25-3-c-annuler-facture-fournisseur: done # 2026-09-26 **REVUE CLOSE EN 1 PASSE** (0 > LOW, Sonnet x3) ; non poussee ; PR `closes #454` titre ET corps, sur accord. **IMPLEMENTEE** — gates 2472/2472, 783/783, E2E 220/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 **SPEC VALIDEE en 2 passes** (3 MED -> 0 > LOW). **SPECIFIEE** — `25-3-c-annuler-facture-fournisseur.md`. Geste par le socle (autorite `SupplierPurchase`), queue commune sur l'ecriture d'ACHAT, lot en dernier rang ; payee : reglement DETACHE (colonnes remises a NULL, ecriture libre, lien garde par l'audit) — **Q1 tranchee par Guy le 2026-09-26**. Aucune migration. Prochaine etape : validate. # 2026-09-24 NEE d'un arbitrage de Guy — une facture fournisseur s'annule DANS TOUS LES CAS, sauf si l'exercice de son ecriture d'achat est CLOS (l'exercice, pas le verrou de periode) ; payee, son reglement RESTE au grand livre et REDEVIENT A LETTRER (Epic 15 : un paiement doit correspondre a une facture, au besoin creee pour lui — « comme bexio »). Reecrit `cancel`, donc absorbe [#454]. Derriere la 25-3-a. 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* - 25-4-a-residuel-juste: ready-for-dev # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. + 25-4-a-residuel-juste: review # 2026-09-27 IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. 25-4-d-solder-le-reste: backlog # [#384] ramenee de la 25-6 le 2026-09-27 (Guy) — perte sur debiteur, escompte, frais : clore une facture partiellement reglee ; la part TVA reduit la TVA due. diff --git a/crates/kesh-api/src/errors.rs b/crates/kesh-api/src/errors.rs index 61ecab28..a93bc1f6 100644 --- a/crates/kesh-api/src/errors.rs +++ b/crates/kesh-api/src/errors.rs @@ -2563,6 +2563,36 @@ impl IntoResponse for AppError { }); (StatusCode::CONFLICT, Json(body)).into_response() } + // Story 25-4-a (#456) — l'avoir refusé sur une facture réglée, + // même en partie. ⛔ Un code et un message propres : l'ancienne + // garde rendait `ILLEGAL_STATE_TRANSITION`, dont le texte n'était + // que journalisé. Le numéro de la facture est suffixé, comme + // pour la dévalidation. + DbError::CreditNoteBlockedBySettlement { + invoice_id, + settlement_id, + invoice_number, + } => { + let base = t( + "error-credit-note-blocked-settled", + "Cette facture porte un règlement, même partiel : annulez-le d'abord pour pouvoir émettre un avoir.", + ); + let message = match invoice_number.as_deref() { + Some(numero) => format!("{base} ({numero})"), + None => base, + }; + let body = serde_json::json!({ + "error": { + "code": "CREDIT_NOTE_INVOICE_SETTLED", + "message": message, + "details": { + "invoiceId": invoice_id, + "settlementId": settlement_id, + }, + } + }); + (StatusCode::CONFLICT, Json(body)).into_response() + } // Story 25-3-a-1 (#414) — l'annulation d'un règlement refusée. // // ⚠️ Seuls les rangs que le GESTE refuse lui-même arrivent ici diff --git a/crates/kesh-api/tests/invoice_echeancier_e2e.rs b/crates/kesh-api/tests/invoice_echeancier_e2e.rs index ab43533a..99cffc3f 100644 --- a/crates/kesh-api/tests/invoice_echeancier_e2e.rs +++ b/crates/kesh-api/tests/invoice_echeancier_e2e.rs @@ -728,3 +728,116 @@ async fn a_closed_fiscal_year_is_announced_and_refused(pool: MySqlPool) { let body: serde_json::Value = resp.json().await.unwrap(); assert_eq!(body["error"]["code"], "FISCAL_YEAR_CLOSED", "got {body:?}"); } + +// --- Story 25-4-a (#455, #456) — le résiduel juste, à la frontière HTTP -------- + +async fn post_credit_note(app: &TestApp, token: &str, invoice_id: i64) -> reqwest::Response { + app.client + .post(app.url("/api/v1/credit-notes")) + .header("Authorization", format!("Bearer {token}")) + .json(&json!({ "invoiceId": invoice_id, "date": "2026-05-10" })) + .send() + .await + .unwrap() +} + +/// Lit un montant rendu par l'API : **présent, non `null`, chaîne**, parsé en +/// `Decimal` — ni comparaison de chaîne (l'échelle suit le calcul SQL), ni +/// `f64`. `null` voudrait dire « non calculé » : ce serait un échec. +fn montant(v: &serde_json::Value, champ: &str) -> rust_decimal::Decimal { + let s = v[champ] + .as_str() + .unwrap_or_else(|| panic!("`{champ}` doit être une chaîne présente, reçu {v:?}")); + s.parse::() + .unwrap_or_else(|e| panic!("`{champ}` = {s:?} n'est pas un décimal : {e}")) +} + +/// #455 — une facture à 8,1 % créditée, jamais réglée : `GET /invoices/{id}` +/// — ouverte aux clés API en lecture — rend un reste dû **nul**, et non la TVA. +#[sqlx::test(migrations = "../kesh-db/test-schema")] +async fn get_credited_invoice_reports_zero_amount_due(pool: MySqlPool) { + let (company_id, admin_id) = seed_base(&pool).await; + let contact_id = seed_contact(&pool, company_id, admin_id).await; + let (id, _v) = create_validated_invoice( + &pool, + company_id, + contact_id, + admin_id, + NaiveDate::from_ymd_opt(2026, 4, 1).unwrap(), + NaiveDate::from_ymd_opt(2026, 4, 30).unwrap(), + dec!(100.00), + ) + .await; + + let app = spawn_app(pool.clone()).await; + let token = login(&app).await; + assert_eq!(post_credit_note(&app, &token, id).await.status(), 201); + + let resp = app + .client + .get(app.url(&format!("/api/v1/invoices/{id}"))) + .header("Authorization", format!("Bearer {token}")) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + let v: serde_json::Value = resp.json().await.unwrap(); + assert_eq!(v["status"], "cancelled", "anti-vacuité : l'avoir est passé"); + assert_eq!( + montant(&v, "amountDue"), + rust_decimal::Decimal::ZERO, + "et non 8.10 — got {v:?}" + ); + assert_eq!(montant(&v, "amountSettled"), rust_decimal::Decimal::ZERO); +} + +/// #456 — un avoir sur une facture réglée en partie est refusé en **409**, avec +/// son code et un message qui dit quoi faire. +#[sqlx::test(migrations = "../kesh-db/test-schema")] +async fn credit_note_on_partially_settled_invoice_is_409(pool: MySqlPool) { + let (company_id, admin_id) = seed_base(&pool).await; + let contact_id = seed_contact(&pool, company_id, admin_id).await; + let (id, _v) = create_validated_invoice( + &pool, + company_id, + contact_id, + admin_id, + NaiveDate::from_ymd_opt(2026, 4, 1).unwrap(), + NaiveDate::from_ymd_opt(2026, 4, 30).unwrap(), + dec!(100.00), + ) + .await; + let caisse = caisse_id(&pool, company_id).await; + + let app = spawn_app(pool.clone()).await; + let token = login(&app).await; + let settle = app + .client + .post(app.url(&format!("/api/v1/invoices/{id}/settlements"))) + .header("Authorization", format!("Bearer {token}")) + .json(&json!({ + "settlementType": "internal_account", + "accountId": caisse, + "amount": "40.00", + "settledOn": "2026-04-15" + })) + .send() + .await + .unwrap(); + assert_eq!(settle.status(), 200); + + let resp = post_credit_note(&app, &token, id).await; + assert_eq!(resp.status(), 409); + let v: serde_json::Value = resp.json().await.unwrap(); + assert_eq!( + v["error"]["code"], "CREDIT_NOTE_INVOICE_SETTLED", + "got {v:?}" + ); + let message = v["error"]["message"].as_str().unwrap(); + assert!( + message.starts_with("Cette facture porte un règlement, même partiel"), + "message fr attendu, reçu {message:?}" + ); + assert_eq!(v["error"]["details"]["invoiceId"], id); + assert!(v["error"]["details"]["settlementId"].as_i64().is_some()); +} diff --git a/crates/kesh-db/src/errors.rs b/crates/kesh-db/src/errors.rs index 48326cf1..402d0b39 100644 --- a/crates/kesh-db/src/errors.rs +++ b/crates/kesh-db/src/errors.rs @@ -212,9 +212,13 @@ pub enum SettlementCancelBlocker { /// rapprochement à annuler. Coupe court : aucun rang suivant ne s'évalue. BankTransactionNotReconciled, /// La facture a été **créditée** par un avoir après ce règlement. Le - /// règlement est alors un paiement **à lettrer** (Epic 15), pas une - /// anomalie — il ne s'annule pas. ⚠️ Le statut `cancelled` d'une facture - /// ne naît en production que de l'avoir (`credit_notes.rs`). + /// règlement est alors un paiement **à lettrer** (Epic 15) — il ne + /// s'annule pas. ⚠️ Le statut `cancelled` d'une facture ne naît en + /// production que de l'avoir (`credit_notes.rs`). + /// + /// ⚠️ **État hérité depuis la Story 25-4-a (#456)** : un avoir est refusé + /// sur une facture réglée, même en partie. L'état n'est plus atteignable + /// que par l'**import d'une sauvegarde antérieure** — d'où ce motif, gardé. InvoiceCredited, /// Tête **fournisseur** (Story 25-3-a-2) : la facture n'est pas `paid` — il /// n'y a pas de règlement à annuler. ⚠️ Coupe court par construction : une @@ -460,6 +464,29 @@ pub enum DbError { document_label: Option, }, + /// Un avoir ne peut pas viser cette facture : elle porte un **règlement**, + /// même partiel (Story 25-4-a, #456). + /// + /// ⛔ **Les deux conditions, jamais l'une seule** : une ligne + /// `invoice_settlements` **ou** `paid_at` posé — comme + /// [`UnvalidationBlocker::Settled`]. Depuis la 24-2, `paid_at` n'est posé + /// qu'au solde : la garde qui ne lisait que lui laissait passer une facture + /// réglée en partie, et l'avoir, qui contre-passe tout le TTC, rendait la + /// créance **créditrice** du montant encaissé. + /// + /// Conflit d'état → HTTP **409** `CREDIT_NOTE_INVOICE_SETTLED`. Remplace le + /// générique `IllegalStateTransition` de l'ancienne garde AC2bis, qui ne + /// disait ni ce qui bloquait ni quoi faire. + #[error("Avoir refusé : la facture porte un règlement")] + CreditNoteBlockedBySettlement { + invoice_id: i64, + /// Le premier règlement trouvé ; `None` quand seul `paid_at` est posé + /// (facture soldée avant la 24-2, sans ligne de règlement). + settlement_id: Option, + /// Le **numéro** de la facture, que l'utilisateur connaît. + invoice_number: Option, + }, + /// Le règlement ne peut pas être annulé (Story 25-3-a-1, #414). /// /// Conflit d'état → HTTP **409**, avec le code canonique du @@ -655,6 +682,7 @@ impl DbError { Self::AccountRoleInvalidForType { .. } => "ACCOUNT_ROLE_INVALID_FOR_TYPE", Self::InvalidRevenueAccounts(_) => "INVOICE_LINE_REVENUE_ACCOUNT_INVALID", Self::CreditNoteRevenueAccountsArchived(_) => "CREDIT_NOTE_REVENUE_ACCOUNT_ARCHIVED", + Self::CreditNoteBlockedBySettlement { .. } => "CREDIT_NOTE_INVOICE_SETTLED", // ⚠️ Le code EXPOSÉ est celui du `ReversalBlocker` (huit valeurs) ; // celui-ci n'est que le repli générique du mapping structuré. Self::EntryNotReversable { .. } => "ENTRY_NOT_REVERSABLE", diff --git a/crates/kesh-db/src/repositories/credit_notes.rs b/crates/kesh-db/src/repositories/credit_notes.rs index a0b164a7..cf79168e 100644 --- a/crates/kesh-db/src/repositories/credit_notes.rs +++ b/crates/kesh-db/src/repositories/credit_notes.rs @@ -297,16 +297,39 @@ pub async fn create_credit_note( inv.status ))); } - // AC2bis : pas d'avoir sur une facture déjà encaissée (v0.2, pas de - // flux de remboursement — éviterait un solde débiteur négatif). - Some(inv) if inv.paid_at.is_some() => { - return Err(DbError::IllegalStateTransition( - "impossible de créer un avoir sur une facture déjà payée".into(), - )); - } Some(inv) => inv, }; + // AC2bis, refondue par la Story 25-4-a (#456) : pas d'avoir sur une + // facture ENCAISSÉE, même en partie. L'avoir contre-passe tout le TTC ; + // sur une facture réglée, la créance deviendrait créditrice du montant + // encaissé, et Kesh n'a ni remboursement ni imputation pour le dénouer + // (#471 porte la levée de ce refus). + // + // ⛔ **Une ligne de règlement OU `paid_at`** — jamais `paid_at` seul : + // depuis la 24-2, il n'est posé qu'au solde. + // + // ⛔ **Lecture VERROUILLANTE, et après le verrou de la facture** (leçon + // de la 25-3-b) : sous REPEATABLE READ, une lecture simple fige + // l'instantané. Un règlement commité pendant que nous attendions le + // verrou de la facture doit être vu ici. + let settlement: Option = sqlx::query_scalar( + "SELECT id FROM invoice_settlements \ + WHERE invoice_id = ? AND company_id = ? ORDER BY id LIMIT 1 FOR UPDATE", + ) + .bind(invoice_id) + .bind(company_id) + .fetch_optional(&mut *tx) + .await + .map_err(map_db_error)?; + if settlement.is_some() || invoice.paid_at.is_some() { + return Err(DbError::CreditNoteBlockedBySettlement { + invoice_id, + settlement_id: settlement, + invoice_number: invoice.invoice_number.clone(), + }); + } + // AC3 : une seule note de crédit par facture (UNIQUE(invoice_id) en DB ; // check explicite pour une erreur métier claire plutôt qu'une FK 1062). let existing: Option = diff --git a/crates/kesh-db/src/repositories/invoice_settlements.rs b/crates/kesh-db/src/repositories/invoice_settlements.rs index d2c85887..0261a9b0 100644 --- a/crates/kesh-db/src/repositories/invoice_settlements.rs +++ b/crates/kesh-db/src/repositories/invoice_settlements.rs @@ -20,13 +20,14 @@ const COLUMNS: &str = "id, company_id, invoice_id, journal_entry_id, amount, set use crate::entities::{InvoiceSettlement, NewInvoiceSettlement}; use crate::errors::{DbError, map_db_error}; +use crate::repositories::invoices::line_ttc_sql; /// Forme **scalaire par facture** du total réglé — sous-requête corrélée. /// **Prérequis : alias `i` sur `invoices`.** /// /// Miroir exact de [`INVOICE_SETTLED_DERIVED_JOIN_SQL`], qui en est la forme /// agrégée. Les deux doivent rester d'accord : c'est ce que vérifie le test de -/// parité. +/// parité (`tests/invoice_amount_due_parity.rs`). /// /// ⚠️ Même discipline que `INVOICE_TTC_SUBQUERY_SQL` (`invoices.rs`) : la forme /// corrélée sert **une** facture, jamais une liste — elle y serait ré-évaluée @@ -40,22 +41,44 @@ pub const INVOICE_SETTLED_SUBQUERY_SQL: &str = pub const INVOICE_SETTLED_DERIVED_JOIN_SQL: &str = "LEFT JOIN (SELECT invoice_id, SUM(amount) AS settled \ FROM invoice_settlements GROUP BY invoice_id) st ON st.invoice_id = i.id"; -/// Forme **scalaire par facture** de l'avoir émis — `0` s'il n'y en a pas. -/// **Prérequis : alias `i` sur `invoices`.** +/// Forme **scalaire par facture** de l'avoir émis, **TTC** — `0` s'il n'y en +/// a pas. **Prérequis : alias `i` sur `invoices`.** +/// +/// ⛔ **TTC, arrondi ligne par ligne** — la grandeur que l'écriture de l'avoir +/// porte au crédit de la créance (`credit_notes::generate_credit_note_journal_lines` : +/// `total_ht + total_vat`, TVA par [`kesh_core::accounting::vat::line_vat_amount`]). +/// Le résiduel soustrait ce terme d'un TTC : il doit être dans la même unité. +/// **Jamais `credit_notes.total_amount`**, qui est **HT** (miroir de +/// `invoices.total_amount`) : le lire ici laissait la TVA d'une facture +/// créditée en reste dû (#455, Story 25-4-a). /// /// ⚠️ **Seul un avoir `issued` compte.** Les statuts sont /// `draft / issued / cancelled` (`chk_credit_notes_status`) : un brouillon n'a /// pas d'écriture et n'éteint donc rien. `credit_notes.invoice_id` est /// `NOT NULL UNIQUE` — au plus un avoir par facture, la somme est donc une /// commodité de forme, pas un cumul réel. -pub const INVOICE_CREDITED_SUBQUERY_SQL: &str = "(SELECT COALESCE(SUM(cn.total_amount), 0) FROM credit_notes cn \ - WHERE cn.invoice_id = i.id AND cn.status = 'issued')"; +pub const INVOICE_CREDITED_SUBQUERY_SQL: &str = concat!( + "(SELECT COALESCE(SUM(", + line_ttc_sql!("cl."), + "), 0) FROM credit_note_lines cl \ + INNER JOIN credit_notes cn ON cn.id = cl.credit_note_id \ + WHERE cn.invoice_id = i.id AND cn.status = 'issued')" +); -/// Forme **agrégat multi-factures** de l'avoir émis — alias `cnt`. -/// **Prérequis : alias `i` sur `invoices`.** -pub const INVOICE_CREDITED_DERIVED_JOIN_SQL: &str = "LEFT JOIN (SELECT invoice_id, SUM(total_amount) AS credited \ - FROM credit_notes WHERE status = 'issued' GROUP BY invoice_id) cnt \ - ON cnt.invoice_id = i.id"; +/// Forme **agrégat multi-factures** de l'avoir émis, **TTC** — table dérivée +/// à joindre (alias `cnt`), puis `COALESCE(cnt.credited, 0)` côté requête +/// externe. **Prérequis : alias `i` sur `invoices`.** +/// +/// Miroir exact de [`INVOICE_CREDITED_SUBQUERY_SQL`] — même unité, même +/// arrondi ; les deux sont tenues d'accord par le test de parité +/// (`tests/invoice_amount_due_parity.rs`). +pub const INVOICE_CREDITED_DERIVED_JOIN_SQL: &str = concat!( + "LEFT JOIN (SELECT cn.invoice_id, SUM(", + line_ttc_sql!("cl."), + ") AS credited FROM credit_note_lines cl \ + INNER JOIN credit_notes cn ON cn.id = cl.credit_note_id \ + WHERE cn.status = 'issued' GROUP BY cn.invoice_id) cnt ON cnt.invoice_id = i.id" +); /// Enregistre un règlement dans la transaction courante. /// @@ -121,6 +144,10 @@ where /// Ce qui reste dû sur une facture : `TTC − avoir émis − Σ règlements`. /// +/// ⛔ **Trois termes TTC.** Le TTC de la facture et celui de l'avoir sont +/// arrondis ligne par ligne par la même formule (`line_ttc_sql!`) ; les +/// règlements sont des montants encaissés, donc TTC par nature. +/// /// ⚠️ **Calculé, jamais stocké.** C'est la seule source de vérité du « combien /// reste-t-il ? », et elle est adossée aux mêmes données que le grand livre. /// diff --git a/crates/kesh-db/src/repositories/invoice_settlements_write.rs b/crates/kesh-db/src/repositories/invoice_settlements_write.rs index 6b7d692f..415b747c 100644 --- a/crates/kesh-db/src/repositories/invoice_settlements_write.rs +++ b/crates/kesh-db/src/repositories/invoice_settlements_write.rs @@ -328,6 +328,9 @@ pub async fn settlement_cancel_blocker_unlinking( // `validated` ou `cancelled` : l'encaissement exige `validated`, la // dévalidation refuse une facture réglée, et `cancelled` ne naît en // production que de l'avoir. Hors `validated`, c'est donc un avoir. + // ⚠️ Depuis la Story 25-4-a (#456), l'avoir est refusé sur une facture + // réglée : ce rang ne se déclenche plus que sur un état HÉRITÉ, ramené par + // l'import d'une sauvegarde antérieure. if status != "validated" { return Ok(Some((SettlementCancelBlocker::InvoiceCredited, None, None))); } diff --git a/crates/kesh-db/src/repositories/invoices.rs b/crates/kesh-db/src/repositories/invoices.rs index b62c4e68..526bbc5a 100644 --- a/crates/kesh-db/src/repositories/invoices.rs +++ b/crates/kesh-db/src/repositories/invoices.rs @@ -147,7 +147,32 @@ pub enum PausedFilter { NotPaused, } -/// Paramètres de recherche, tri et pagination. +/// **Source unique** de la formule SQL du TTC d'UNE ligne (facture ou avoir) — +/// Story 25-4-a (#455). Rend un **littéral** (`concat!`), pour que les +/// constantes qui la composent restent des `&'static str`. +/// +/// `$p` est le préfixe de colonne : `"l."`, `"cl."`, ou `""` dans une table +/// dérivée sans alias. Arrondi **par ligne**, half-away-from-zero : miroir de +/// `kesh_core::accounting::vat::line_vat_amount`, que l'écriture de vente +/// comme celle de l'avoir emploient. +/// +/// ⛔ Ne pas recopier la formule à la main : la TVA de l'avoir a été soustraite +/// au HT pendant des mois (#455) parce que la grandeur « avoir » n'empruntait +/// pas cette formule-ci. +macro_rules! line_ttc_sql { + ($p:literal) => { + concat!( + $p, + "line_total + ROUND(", + $p, + "line_total * ", + $p, + "vat_rate / 100, 2)" + ) + }; +} +pub(crate) use line_ttc_sql; + /// Forme **scalaire par facture** du TTC canonique (#246, Story 21-2a) — /// sous-requête corrélée. **Prérequis : alias `i` sur `invoices`.** /// @@ -158,8 +183,11 @@ pub enum PausedFilter { /// /// `pub` (PAS `pub(crate)`) : consommée par `repositories::reconciliation` /// (21-2b) et par `kesh-report` (balance âgée 21-7) — source de vérité unique. -pub const INVOICE_TTC_SUBQUERY_SQL: &str = "(SELECT COALESCE(SUM(l.line_total + ROUND(l.line_total * l.vat_rate / 100, 2)), 0) \ - FROM invoice_lines l WHERE l.invoice_id = i.id)"; +pub const INVOICE_TTC_SUBQUERY_SQL: &str = concat!( + "(SELECT COALESCE(SUM(", + line_ttc_sql!("l."), + "), 0) FROM invoice_lines l WHERE l.invoice_id = i.id)" +); /// Forme **agrégat multi-factures** du TTC canonique (#246) — table dérivée à /// joindre (alias `lt`), puis `SUM(COALESCE(lt.ttc, 0))` côté requête externe. @@ -168,9 +196,11 @@ pub const INVOICE_TTC_SUBQUERY_SQL: &str = "(SELECT COALESCE(SUM(l.line_total + /// Choix de la table dérivée plutôt que la corrélée dans le `SUM()` externe : /// raison de **performance** (la corrélée serait ré-évaluée par ligne et par /// `CASE`), pas un interdit SQL (MariaDB accepte les deux — vérifié). -pub const INVOICE_TTC_DERIVED_JOIN_SQL: &str = "LEFT JOIN (SELECT invoice_id, \ - SUM(line_total + ROUND(line_total * vat_rate / 100, 2)) AS ttc \ - FROM invoice_lines GROUP BY invoice_id) lt ON lt.invoice_id = i.id"; +pub const INVOICE_TTC_DERIVED_JOIN_SQL: &str = concat!( + "LEFT JOIN (SELECT invoice_id, SUM(", + line_ttc_sql!(""), + ") AS ttc FROM invoice_lines GROUP BY invoice_id) lt ON lt.invoice_id = i.id" +); /// TTC canonique d'UNE facture (#246) via la forme scalaire — pour les /// call-sites qui n'ont pas les lignes chargées (ex. re-score de la @@ -189,6 +219,7 @@ where .map_err(map_db_error) } +/// Paramètres de recherche, tri et pagination. #[derive(Debug, Clone, Default)] pub struct InvoiceListQuery { pub search: Option, diff --git a/crates/kesh-db/tests/credit_notes_repository.rs b/crates/kesh-db/tests/credit_notes_repository.rs index f5f97df7..0c16ade8 100644 --- a/crates/kesh-db/tests/credit_notes_repository.rs +++ b/crates/kesh-db/tests/credit_notes_repository.rs @@ -10,9 +10,9 @@ use chrono::NaiveDate; use kesh_db::entities::contact::{ContactType, NewContact}; -use kesh_db::entities::{NewCreditNote, NewInvoice, NewInvoiceLine}; +use kesh_db::entities::{NewCreditNote, NewInvoice, NewInvoiceLine, SettlementChoice}; use kesh_db::errors::DbError; -use kesh_db::repositories::{contacts, credit_notes, invoices}; +use kesh_db::repositories::{contacts, credit_notes, invoice_settlements_write, invoices}; use kesh_db::test_fixtures::{SeededCompany, seed_accounting_company}; use rust_decimal::Decimal; use rust_decimal_macros::dec; @@ -290,9 +290,18 @@ async fn credit_note_refused_on_paid_invoice(pool: MySqlPool) { ) .await .unwrap_err(); + // Story 25-4-a (#456) : la garde AC2bis rend sa variante dédiée, plus le + // générique — le motif « payée » et le motif « réglée en partie » sont un + // seul et même refus. `settlement_id` est `None` : seul `paid_at` est posé. assert!( - matches!(err, DbError::IllegalStateTransition(_)), - "refus facture payée" + matches!( + err, + DbError::CreditNoteBlockedBySettlement { + settlement_id: None, + .. + } + ), + "refus facture payée — reçu {err:?}" ); } @@ -423,3 +432,225 @@ async fn credit_note_inherits_project_and_nets_to_zero(pool: MySqlPool) { .unwrap(); assert_eq!(debit, credit, "net par projet doit être 0 après l'avoir"); } + +// ─── Story 25-4-a (#456) — pas d'avoir sur une facture réglée, même en partie ─── + +async fn settle(pool: &MySqlPool, seeded: &SeededCompany, invoice_id: i64, amount: Decimal) -> i64 { + let out = invoice_settlements_write::settle_invoice( + pool, + seeded.admin_user_id, + seeded.company_id, + invoice_id, + SettlementChoice::InternalAccount { + account_id: seeded.accounts["1000"], + }, + amount, + d(2026, 6, 20), + ) + .await + .expect("règlement"); + sqlx::query_scalar("SELECT id FROM invoice_settlements WHERE journal_entry_id = ?") + .bind(out.journal_entry_id) + .fetch_one(pool) + .await + .expect("ligne de règlement") +} + +async fn try_credit( + pool: &MySqlPool, + seeded: &SeededCompany, + invoice_id: i64, +) -> Result { + credit_notes::create_credit_note( + pool, + NewCreditNote { + company_id: seeded.company_id, + invoice_id, + date: d(2026, 7, 1), + }, + seeded.admin_user_id, + ) + .await +} + +/// Ce qu'un refus ne doit pas avoir touché : avoirs, statut et version de la +/// facture, solde de la créance, séquence de numéros d'avoir. +async fn empreinte( + pool: &MySqlPool, + seeded: &SeededCompany, + invoice_id: i64, +) -> (i64, String, i32, Decimal, i64) { + let avoirs: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM credit_notes WHERE company_id = ?") + .bind(seeded.company_id) + .fetch_one(pool) + .await + .unwrap(); + let (status, version): (String, i32) = + sqlx::query_as("SELECT status, version FROM invoices WHERE id = ?") + .bind(invoice_id) + .fetch_one(pool) + .await + .unwrap(); + let creance: Decimal = sqlx::query_scalar( + "SELECT COALESCE(SUM(debit) - SUM(credit), 0) FROM journal_entry_lines WHERE account_id = ?", + ) + .bind(seeded.accounts["1100"]) + .fetch_one(pool) + .await + .unwrap(); + let sequence: i64 = sqlx::query_scalar( + "SELECT CAST(COALESCE(SUM(next_number), 0) AS SIGNED) FROM credit_note_number_sequences WHERE company_id = ?", + ) + .bind(seeded.company_id) + .fetch_one(pool) + .await + .unwrap(); + (avoirs, status, version, creance, sequence) +} + +/// AC 9, AC 10 — une facture réglée en partie (`paid_at` NULL) est refusée, et +/// **rien n'est écrit**. C'est le chemin que l'ancienne garde laissait passer. +#[sqlx::test(migrations = "./test-schema")] +async fn credit_note_refused_on_partially_settled_invoice(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let contact = make_contact(&pool, seeded.company_id, seeded.admin_user_id).await; + let invoice_id = + create_and_validate(&pool, &seeded, contact, &[(dec!(8.10), dec!(100.00))]).await; + let sid = settle(&pool, &seeded, invoice_id, dec!(40.00)).await; + + let paid_at: Option = + sqlx::query_scalar("SELECT paid_at FROM invoices WHERE id = ?") + .bind(invoice_id) + .fetch_one(&pool) + .await + .unwrap(); + assert!( + paid_at.is_none(), + "anti-vacuité : la facture n'est PAS soldée" + ); + + let avant = empreinte(&pool, &seeded, invoice_id).await; + let err = try_credit(&pool, &seeded, invoice_id).await.unwrap_err(); + match &err { + DbError::CreditNoteBlockedBySettlement { + invoice_id: iid, + settlement_id, + invoice_number, + } => { + assert_eq!(*iid, invoice_id); + assert_eq!(*settlement_id, Some(sid)); + assert!(invoice_number.is_some(), "le numéro est rendu"); + } + other => panic!("attendu CreditNoteBlockedBySettlement, reçu {other:?}"), + } + assert_eq!(err.error_code(), "CREDIT_NOTE_INVOICE_SETTLED"); + assert_eq!( + empreinte(&pool, &seeded, invoice_id).await, + avant, + "rien n'est écrit" + ); +} + +/// La promesse du message : annuler le règlement rouvre l'avoir. +#[sqlx::test(migrations = "./test-schema")] +async fn credit_note_accepted_after_the_settlement_is_cancelled(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let contact = make_contact(&pool, seeded.company_id, seeded.admin_user_id).await; + let invoice_id = + create_and_validate(&pool, &seeded, contact, &[(dec!(8.10), dec!(100.00))]).await; + let sid = settle(&pool, &seeded, invoice_id, dec!(40.00)).await; + assert!(try_credit(&pool, &seeded, invoice_id).await.is_err()); + + invoice_settlements_write::cancel_settlement( + &pool, + seeded.admin_user_id, + seeded.company_id, + invoice_id, + sid, + ) + .await + .expect("annulation du règlement"); + + try_credit(&pool, &seeded, invoice_id) + .await + .expect("l'avoir passe une fois le règlement annulé"); +} + +/// AC 9 — ⛔ **entrelacement** : un règlement commité PENDANT que l'avoir +/// attend le verrou de la facture doit être vu. +/// +/// 1. une transaction verrouille la facture et y rattache un règlement, sans +/// valider ; +/// 2. l'avoir démarre et attend au `FOR UPDATE` de la facture ; +/// 3. la transaction est validée. +/// +/// La garde lit les règlements **après** le verrou, par une lecture +/// verrouillante : elle voit la ligne et refuse. Lue **avant** le verrou, en +/// lecture simple, elle aurait vu « aucun règlement » et laissé passer. +/// +/// Le règlement vient du vrai chemin, sur une facture auxiliaire : seul son +/// rattachement se fait dans la transaction concurrente. +#[sqlx::test(migrations = "./test-schema")] +async fn credit_note_waits_for_a_concurrent_settlement(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let contact = make_contact(&pool, seeded.company_id, seeded.admin_user_id).await; + let invoice_id = + create_and_validate(&pool, &seeded, contact, &[(dec!(8.10), dec!(100.00))]).await; + let parking = create_and_validate(&pool, &seeded, contact, &[(dec!(8.10), dec!(100.00))]).await; + let sid = settle(&pool, &seeded, parking, dec!(40.00)).await; + + // (1) Le règlement concurrent, non validé, qui tient la facture. + let mut concurrent = pool.begin().await.unwrap(); + sqlx::query("SELECT id FROM invoices WHERE id = ? FOR UPDATE") + .bind(invoice_id) + .execute(&mut *concurrent) + .await + .unwrap(); + sqlx::query("UPDATE invoice_settlements SET invoice_id = ? WHERE id = ?") + .bind(invoice_id) + .bind(sid) + .execute(&mut *concurrent) + .await + .unwrap(); + + // (2) L'avoir démarre et attend. + let p = pool.clone(); + let (company_id, user_id) = (seeded.company_id, seeded.admin_user_id); + let avoir = tokio::spawn(async move { + credit_notes::create_credit_note( + &p, + NewCreditNote { + company_id, + invoice_id, + date: d(2026, 7, 1), + }, + user_id, + ) + .await + }); + let vue = kesh_db::test_fixtures::attendre_une_requete_en_cours( + &pool, + &["FROM invoices", "FOR UPDATE"], + || avoir.is_finished(), + ) + .await; + if !vue { + panic!("l'avoir a fini sans attendre le verrou : {:?}", avoir.await); + } + + // (3) Le règlement concurrent est validé. + concurrent.commit().await.unwrap(); + + let result = avoir.await.expect("tâche d'avoir"); + assert!( + matches!( + result, + Err(DbError::CreditNoteBlockedBySettlement { + settlement_id: Some(s), + .. + }) if s == sid + ), + "l'avoir devait attendre le règlement puis refuser — reçu {:?}", + result.map(|c| c.credit_note.id) + ); +} diff --git a/crates/kesh-db/tests/invoice_amount_due_parity.rs b/crates/kesh-db/tests/invoice_amount_due_parity.rs new file mode 100644 index 00000000..79523b62 --- /dev/null +++ b/crates/kesh-db/tests/invoice_amount_due_parity.rs @@ -0,0 +1,311 @@ +//! Le résiduel juste — Story 25-4-a (#455, #456). +//! +//! `amount_due` = `TTC − avoir émis − Σ règlements`. Trois garanties : +//! +//! 1. **L'avoir est compté TTC** (#455) : le terme « avoir » vaut le crédit que +//! l'écriture de l'avoir porte à la créance — pas `credit_notes.total_amount`, +//! qui est HT. C'est l'assertion de **concordance avec le grand livre** qui le +//! prouve, pas une valeur recalculée à côté. +//! 2. **Les formes scalaire et jointe sont d'accord**, facture par facture, pour le +//! réglé comme pour l'avoir — le test de parité que le doc-comment +//! d'`INVOICE_SETTLED_SUBQUERY_SQL` annonçait sans qu'il existe. Les formes +//! jointes n'avaient aucun appelant ; 25-4-b en sera le premier. +//! 3. **Chaque facture porte une TVA non nulle.** À 0 %, HT = TTC et #455 est +//! invisible — c'est ainsi qu'il a passé tous les tests de la 24-2. +//! +//! Pré-requis : MariaDB démarré (`sqlx::test` crée une DB éphémère par test). + +use chrono::NaiveDate; +use kesh_db::entities::contact::{ContactType, NewContact}; +use kesh_db::entities::{NewCreditNote, NewInvoice, NewInvoiceLine, SettlementChoice}; +use kesh_db::repositories::invoice_settlements::{ + INVOICE_CREDITED_DERIVED_JOIN_SQL, INVOICE_CREDITED_SUBQUERY_SQL, + INVOICE_SETTLED_DERIVED_JOIN_SQL, INVOICE_SETTLED_SUBQUERY_SQL, amount_due, +}; +use kesh_db::repositories::{contacts, credit_notes, invoice_settlements_write, invoices}; +use kesh_db::test_fixtures::{SeededCompany, seed_accounting_company}; +use rust_decimal::Decimal; +use rust_decimal_macros::dec; +use sqlx::MySqlPool; + +fn d(y: i32, m: u32, day: u32) -> NaiveDate { + NaiveDate::from_ymd_opt(y, m, day).unwrap() +} + +async fn make_contact(pool: &MySqlPool, seeded: &SeededCompany) -> i64 { + contacts::create( + pool, + seeded.admin_user_id, + NewContact { + company_id: seeded.company_id, + contact_type: ContactType::Entreprise, + name: "Client Résiduel".into(), + first_name: None, + last_name: None, + is_client: true, + is_supplier: false, + address: Some("Rue 1\n1000 Lausanne".into()), + address_street: None, + address_building: None, + address_postal_code: None, + address_city: None, + address_country: None, + email: None, + phone: None, + ide_number: None, + client_number: None, + default_payment_terms: Some("30".into()), + default_payment_terms_days: None, + language: None, + salutation: kesh_db::entities::contact::Salutation::Neutre, + }, + ) + .await + .expect("contact") + .id +} + +/// Une facture validée par le **vrai chemin** (écriture de vente TVA comprise) — +/// jamais par le helper à 0 % de `invoice_settlement.rs`. +async fn validated( + pool: &MySqlPool, + seeded: &SeededCompany, + contact_id: i64, + lines: &[(Decimal, Decimal)], +) -> i64 { + let new = NewInvoice { + company_id: seeded.company_id, + contact_id, + date: d(2026, 6, 15), + due_date: None, + payment_terms: None, + lines: lines + .iter() + .map(|(rate, price)| NewInvoiceLine { + revenue_account_id: None, + description: "Ligne".into(), + quantity: dec!(1), + unit_price: *price, + vat_rate: *rate, + }) + .collect(), + project_id: None, + }; + let (inv, _) = invoices::create(pool, seeded.admin_user_id, new) + .await + .expect("create invoice"); + invoices::validate_invoice(pool, seeded.company_id, inv.id, seeded.admin_user_id) + .await + .expect("validate invoice"); + inv.id +} + +async fn settle(pool: &MySqlPool, seeded: &SeededCompany, invoice_id: i64, amount: Decimal) -> i64 { + invoice_settlements_write::settle_invoice( + pool, + seeded.admin_user_id, + seeded.company_id, + invoice_id, + SettlementChoice::InternalAccount { + account_id: seeded.accounts["1000"], + }, + amount, + d(2026, 6, 20), + ) + .await + .expect("règlement") + .journal_entry_id +} + +async fn credit( + pool: &MySqlPool, + seeded: &SeededCompany, + invoice_id: i64, +) -> credit_notes::IssuedCreditNote { + credit_notes::create_credit_note( + pool, + NewCreditNote { + company_id: seeded.company_id, + invoice_id, + date: d(2026, 7, 1), + }, + seeded.admin_user_id, + ) + .await + .expect("avoir") +} + +/// L'état hérité **règlement puis avoir**, que 25-4-a rend inatteignable par +/// l'application mais qu'un `.keshbackup` v0.12.0 peut ramener. +/// +/// Gabarit « détacher, créditer, rattacher » (AC 13) : les deux écritures +/// viennent des **vrais chemins** ; seul le rattachement de la ligne de +/// règlement passe en SQL, le temps de l'avoir. +async fn settle_then_legacy_credit( + pool: &MySqlPool, + seeded: &SeededCompany, + contact_id: i64, + invoice_id: i64, + amount: Decimal, +) { + settle(pool, seeded, invoice_id, amount).await; + let parking = validated(pool, seeded, contact_id, &[(dec!(8.10), dec!(10.00))]).await; + sqlx::query("UPDATE invoice_settlements SET invoice_id = ? WHERE invoice_id = ?") + .bind(parking) + .bind(invoice_id) + .execute(pool) + .await + .expect("détacher"); + credit(pool, seeded, invoice_id).await; + sqlx::query("UPDATE invoice_settlements SET invoice_id = ? WHERE invoice_id = ?") + .bind(invoice_id) + .bind(parking) + .execute(pool) + .await + .expect("rattacher"); +} + +async fn credited_scalar(pool: &MySqlPool, invoice_id: i64) -> Decimal { + sqlx::query_scalar(&format!( + "SELECT {INVOICE_CREDITED_SUBQUERY_SQL} FROM invoices i WHERE i.id = ?" + )) + .bind(invoice_id) + .fetch_one(pool) + .await + .expect("avoir scalaire") +} + +/// AC 1, AC 7 — ⛔ le terme « avoir » du résiduel égale le crédit que +/// l'écriture de l'avoir porte à la créance. Multi-taux, arrondis limites. +#[sqlx::test(migrations = "./test-schema")] +async fn credited_amount_is_ttc_and_matches_the_ledger(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let contact = make_contact(&pool, &seeded).await; + let invoice_id = validated( + &pool, + &seeded, + contact, + &[ + (dec!(8.10), dec!(1000.00)), + (dec!(2.60), dec!(123.45)), + (dec!(8.10), dec!(0.05)), + ], + ) + .await; + + let issued = credit(&pool, &seeded, invoice_id).await; + let receivable = seeded.accounts["1100"]; + let ledger_credit: Decimal = issued + .journal_entry + .lines + .iter() + .filter(|l| l.account_id == receivable) + .map(|l| l.credit) + .sum(); + + // 1000 + 81.00 ; 123.45 + 3.21 ; 0.05 + 0.00 (sous-centime) = 1207.71 + assert_eq!(ledger_credit, dec!(1207.71), "crédit créance de l'avoir"); + assert_eq!(credited_scalar(&pool, invoice_id).await, ledger_credit); + assert_ne!( + issued.credit_note.total_amount, ledger_credit, + "le montant stocké est HT : s'il égalait le crédit, le test ne distinguerait rien" + ); +} + +/// AC 8 — facture créditée, jamais réglée : il ne reste rien à payer. +#[sqlx::test(migrations = "./test-schema")] +async fn amount_due_of_a_credited_invoice_is_zero(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let contact = make_contact(&pool, &seeded).await; + let invoice_id = validated(&pool, &seeded, contact, &[(dec!(8.10), dec!(100.00))]).await; + credit(&pool, &seeded, invoice_id).await; + + assert_eq!( + amount_due(&pool, invoice_id).await.unwrap(), + Decimal::ZERO, + "et non 8.10, la TVA (#455)" + ); +} + +/// AC 8 — l'état hérité : 40 encaissés sur une vente annulée. Le reste dû vaut +/// −40, un trop-perçu visible — pas `TVA − 40`, et pas écrêté à zéro. +#[sqlx::test(migrations = "./test-schema")] +async fn amount_due_after_settlement_then_legacy_credit_is_minus_settled(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let contact = make_contact(&pool, &seeded).await; + let invoice_id = validated(&pool, &seeded, contact, &[(dec!(8.10), dec!(100.00))]).await; + settle_then_legacy_credit(&pool, &seeded, contact, invoice_id, dec!(40.00)).await; + + assert_eq!(amount_due(&pool, invoice_id).await.unwrap(), dec!(-40.00)); +} + +/// AC 6 — la forme scalaire et la forme jointe rendent la même valeur, facture +/// par facture, pour le réglé et pour l'avoir. Jeu : sans règlement, un +/// règlement, deux règlements, avoir émis, avoir brouillon, état hérité. +#[sqlx::test(migrations = "./test-schema")] +async fn settled_and_credited_forms_are_at_parity(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let contact = make_contact(&pool, &seeded).await; + let lines = [(dec!(8.10), dec!(100.00)), (dec!(2.60), dec!(33.33))]; + + let _plain = validated(&pool, &seeded, contact, &lines).await; + let one = validated(&pool, &seeded, contact, &lines).await; + settle(&pool, &seeded, one, dec!(40.00)).await; + let two = validated(&pool, &seeded, contact, &lines).await; + settle(&pool, &seeded, two, dec!(40.00)).await; + settle(&pool, &seeded, two, dec!(12.34)).await; + let credited = validated(&pool, &seeded, contact, &lines).await; + credit(&pool, &seeded, credited).await; + let legacy = validated(&pool, &seeded, contact, &lines).await; + settle_then_legacy_credit(&pool, &seeded, contact, legacy, dec!(25.00)).await; + + // Avoir BROUILLON : sans écriture, il n'éteint rien — monté en SQL, aucun + // chemin applicatif ne produisant de brouillon (single-step, DC5). + let draft_target = validated(&pool, &seeded, contact, &lines).await; + let cn = sqlx::query( + "INSERT INTO credit_notes (company_id, invoice_id, contact_id, date, status, total_amount) \ + VALUES (?, ?, ?, ?, 'draft', 133.33)", + ) + .bind(seeded.company_id) + .bind(draft_target) + .bind(contact) + .bind(d(2026, 7, 1)) + .execute(&pool) + .await + .expect("avoir brouillon"); + sqlx::query( + "INSERT INTO credit_note_lines (credit_note_id, position, description, quantity, unit_price, vat_rate, line_total) \ + VALUES (?, 1, 'Ligne', 1, 100.00, 8.10, 100.00)", + ) + .bind(cn.last_insert_id()) + .execute(&pool) + .await + .expect("ligne d'avoir brouillon"); + + let rows: Vec<(i64, Decimal, Decimal, Decimal, Decimal)> = sqlx::query_as(&format!( + "SELECT i.id, {INVOICE_SETTLED_SUBQUERY_SQL}, COALESCE(st.settled, 0), \ + {INVOICE_CREDITED_SUBQUERY_SQL}, COALESCE(cnt.credited, 0) \ + FROM invoices i {INVOICE_SETTLED_DERIVED_JOIN_SQL} {INVOICE_CREDITED_DERIVED_JOIN_SQL} \ + WHERE i.company_id = ? ORDER BY i.id" + )) + .bind(seeded.company_id) + .fetch_all(&pool) + .await + .expect("parité"); + + // Six factures du jeu, plus la facture auxiliaire du gabarit. + assert_eq!(rows.len(), 7, "le jeu entier est lu : {rows:?}"); + for (id, settled_s, settled_j, credited_s, credited_j) in &rows { + assert_eq!(settled_s, settled_j, "réglé, facture {id}"); + assert_eq!(credited_s, credited_j, "avoir, facture {id}"); + } + // Anti-vacuité : la parité porte sur des valeurs NON nulles des deux côtés. + let by_id = |x: i64| rows.iter().find(|r| r.0 == x).unwrap(); + assert_eq!(by_id(two).1, dec!(52.34)); + assert_eq!(by_id(credited).3, dec!(142.30)); // 100 + 8.10 ; 33.33 + 0.87 + assert_eq!( + by_id(draft_target).3, + Decimal::ZERO, + "un brouillon n'éteint rien" + ); +} diff --git a/crates/kesh-db/tests/invoice_settlement.rs b/crates/kesh-db/tests/invoice_settlement.rs index 0f01479f..f14d1f21 100644 --- a/crates/kesh-db/tests/invoice_settlement.rs +++ b/crates/kesh-db/tests/invoice_settlement.rs @@ -974,6 +974,25 @@ async fn monter(pool: &MySqlPool, motifs: &[SettlementCancelBlocker]) -> (Seeded .await; if motifs.contains(&SettlementCancelBlocker::InvoiceCredited) { + // ⚠️ **État HÉRITÉ, plus atteignable par l'application** depuis la + // Story 25-4-a (#456) : un avoir est désormais refusé sur une facture + // réglée, même en partie. Il reste possible par l'**import d'une + // sauvegarde antérieure** — c'est ce qui justifie de garder le motif + // `InvoiceCredited` et ce montage. + // + // Gabarit « détacher, créditer, rattacher » : les deux écritures + // (règlement, avoir) viennent des VRAIS chemins ; seul le rattachement + // de la ligne de règlement passe en SQL, le temps de l'avoir. La + // facture auxiliaire est datée dans l'exercice seedé, que le cas + // « aucun exercice ouvert » garde valide. + let parking = + validated_invoice(pool, &seeded, dec!(10.00), d - chrono::Duration::days(20)).await; + sqlx::query("UPDATE invoice_settlements SET invoice_id = ? WHERE id = ?") + .bind(parking) + .bind(sid) + .execute(pool) + .await + .expect("détacher le règlement"); credit_notes::create_credit_note( pool, kesh_db::entities::NewCreditNote { @@ -984,7 +1003,13 @@ async fn monter(pool: &MySqlPool, motifs: &[SettlementCancelBlocker]) -> (Seeded seeded.admin_user_id, ) .await - .expect("avoir après règlement partiel — le vrai chemin l'accepte"); + .expect("avoir sur la facture détachée de son règlement"); + sqlx::query("UPDATE invoice_settlements SET invoice_id = ? WHERE id = ?") + .bind(inv_id) + .bind(sid) + .execute(pool) + .await + .expect("rattacher le règlement"); } if motifs.contains(&SettlementCancelBlocker::MatchedBankTransaction) { match_to_bank( diff --git a/crates/kesh-i18n/locales/de-CH/messages.ftl b/crates/kesh-i18n/locales/de-CH/messages.ftl index 3a86241f..6c151ed9 100644 --- a/crates/kesh-i18n/locales/de-CH/messages.ftl +++ b/crates/kesh-i18n/locales/de-CH/messages.ftl @@ -24,6 +24,7 @@ error-journal-entry-linked-to-invoice = Diese Buchung stammt aus einer freigegeb error-check-constraint = Ungültiger Wert error-illegal-state = Unzulässiger Statusübergang error-invoice-unvalidate-blocked-settled = Diese Rechnung weist eine — auch nur teilweise — Zahlung auf: stornieren Sie diese zuerst. +error-credit-note-blocked-settled = Diese Rechnung weist eine — auch nur teilweise — Zahlung auf: Stornieren Sie diese zuerst, um eine Gutschrift ausstellen zu können. error-invoice-unvalidate-blocked-credited = Diese Rechnung wurde durch eine Gutschrift korrigiert, die bereits die Korrektur darstellt. error-invoice-unvalidate-blocked-reminders = Diese Rechnung hat eine Mahnhistorie: deren Löschung würde den Nachweis des Inkassos vernichten. error-invoice-unvalidate-blocked-emailed = Diese Rechnung wurde dem Kunden zugestellt: korrigieren Sie sie mit einer Gutschrift. diff --git a/crates/kesh-i18n/locales/en-CH/messages.ftl b/crates/kesh-i18n/locales/en-CH/messages.ftl index fe0684f0..92ffd928 100644 --- a/crates/kesh-i18n/locales/en-CH/messages.ftl +++ b/crates/kesh-i18n/locales/en-CH/messages.ftl @@ -24,6 +24,7 @@ error-journal-entry-linked-to-invoice = This journal entry was generated by a va error-check-constraint = Invalid value error-illegal-state = Illegal state transition error-invoice-unvalidate-blocked-settled = This invoice carries a payment, even a partial one: cancel it first. +error-credit-note-blocked-settled = This invoice carries a payment, even a partial one: cancel it first to be able to issue a credit note. error-invoice-unvalidate-blocked-credited = This invoice is credited by a credit note, which is already the correction. error-invoice-unvalidate-blocked-reminders = This invoice has a reminder history: removing it would erase the proof of collection. error-invoice-unvalidate-blocked-emailed = This invoice was sent to the client: correct it with a credit note. diff --git a/crates/kesh-i18n/locales/fr-CH/messages.ftl b/crates/kesh-i18n/locales/fr-CH/messages.ftl index 8aaa3a6a..9e47a49e 100644 --- a/crates/kesh-i18n/locales/fr-CH/messages.ftl +++ b/crates/kesh-i18n/locales/fr-CH/messages.ftl @@ -24,6 +24,7 @@ error-journal-entry-linked-to-invoice = Cette écriture comptable a été géné error-check-constraint = Valeur invalide error-illegal-state = Transition d'état interdite error-invoice-unvalidate-blocked-settled = Cette facture porte un règlement, même partiel : annulez-le d'abord. +error-credit-note-blocked-settled = Cette facture porte un règlement, même partiel : annulez-le d'abord pour pouvoir émettre un avoir. error-invoice-unvalidate-blocked-credited = Cette facture est créditée par un avoir, qui en est déjà la correction. error-invoice-unvalidate-blocked-reminders = Cette facture a un historique de rappels : le dévalider effacerait la preuve du recouvrement. error-invoice-unvalidate-blocked-emailed = Cette facture a été envoyée au client : corrigez-la par un avoir. diff --git a/crates/kesh-i18n/locales/it-CH/messages.ftl b/crates/kesh-i18n/locales/it-CH/messages.ftl index 5cb50f27..06f35274 100644 --- a/crates/kesh-i18n/locales/it-CH/messages.ftl +++ b/crates/kesh-i18n/locales/it-CH/messages.ftl @@ -24,6 +24,7 @@ error-journal-entry-linked-to-invoice = Questa registrazione contabile è stata error-check-constraint = Valore non valido error-illegal-state = Transizione di stato non consentita error-invoice-unvalidate-blocked-settled = Questa fattura presenta un pagamento, anche parziale: annullatelo prima. +error-credit-note-blocked-settled = Questa fattura presenta un pagamento, anche parziale: annullalo prima per poter emettere una nota di credito. error-invoice-unvalidate-blocked-credited = Questa fattura è stornata da una nota di credito, che ne è già la correzione. error-invoice-unvalidate-blocked-reminders = Questa fattura ha uno storico di solleciti: eliminarlo cancellerebbe la prova del recupero. error-invoice-unvalidate-blocked-emailed = Questa fattura è stata inviata al cliente: correggetela con una nota di credito. diff --git a/docs/manual/fr/user-manual.pdf b/docs/manual/fr/user-manual.pdf index f402efacd064c0d629e774329a4ebf85ccc8cddd..139a7469d9ca487daeca37fabb66e16230e00dcb 100644 GIT binary patch delta 127804 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�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

@lfr;_q5P-uG6MwJ0xI7Uu~vT0>mc46S0rA!T3 zoh7~BcH}ABN`_3zwDdRR@I4h7fOG*;%8P|%ybRZ_6YPTLmgLMty0tU;e&zT|avH!s zarlPC0cQwt;1=D$fm{zDRm@MiH!&Tw1*KpLZnr#mhvl6O7b>4sR8A*;hmksI8~OxY zVZa0-M9~>AVJK6eWv1#xO%ioSLI<)+y>D>JDzWOMoMhG12ne8U&hHZq?Y8+ds>oij z3_0Z`x2+r9;DHLms~8y8 z02?oX4Mo4r6@Kp!Xm2y)wkl+Z-$u*PzqjdOcvq=U#nGfwWlGP@ng91r(#3N^pHX)d9^d-48`k1U*N|T1 zSmi91D}+N_*_QFyFGorx0Z)h=Yw>NA&DPSmZlR5SC)HfM2F5&n3*FTihrD&&Vh&&T zh^FgK-fS}+C}F-Kq%aSJRT-g)LBI7|n!kzI?tt^@)gd?Cm+PXi^nf^FJvH;BSZ;56 z!cQr*5TH2AP_HqczVzU@#(JHPu18I8Ok1F(Xf#+X7@6|u>V6Ofnm~-an;9Vth`)kKQ3lDTvwXqx8{>6ULl+zIt z1Fz51WarcSke~n_pZ52NL>yJ0Z7a^Co-7d7{fgXJC>?yKzpWYh#f42rWSoWi zw^;Sq(%Uhs+V1STH%_s4@^nNwnB^7N^@G^8|K(PkBZ^AM2`J2PMx_c6JZc6psF_Kj zWqH@WF~1u{^9?_*58EFg0ng-nt+9J_m*w{%v%4#t_g=g?7k-xFYVvUf*e+WJctFC- zqKVY~7TbM2m%RPK>2F&CJmYvRLw4R?a`S&=bgOjwLzpwaX3QB?J+FVi1y>c}u@{~p zW-4z;MpjaS+Z+4+l2qMMqQTy*BNg8EKM4$oaEBhqCN9R&H+|il_uxEl0~nrz^Oc@naEzgBN_Bk53{s-JGT2j|M1ZS z6~`unj7kDFK1p^`*B%H1%xN-cbs#(vxy(yeY}10_#xkVNdxf(45Ez13zvpS=ezt{(00eK{nclKDga{$y}jcAgGbB)b)FMQ~A(Eq*3XDxeUbh^gXLW#^Ws>4mC9F}F50 zk)L)Y52UO%$U4hQC!%3s&lyeUiB2IOx5(>WxvG-mYQ{rEsp;^+CC_&--c>lX_9Fme zE<`G0SI@&J=hI{{%aO?aUS--w6yTbrIk5jw;>A(Dc+P{v!AGTlJrECB&zKfGzkH`Akd;3_RDR}$iLc-byk~Yq5+IDwCVCDNf z%#ei{;d`Z}wXzI8-oN9f40ih+cZTtLP2iE{wm#m=V9u`j8| z;0EpHCrd?U(acI7567!|Eu~5T3zN${1);!Fn0bb%hKL@T>uKYa6Z11XmPfy&2qr z(2&h=6PhXr)=`WvT*>$GP_E^}R0BojhI^i^E-AzQm<~&! zFt%4pK((7(Fz-KbVdVcLu&+UBC{-vDC){OoP>iFKNsaH|6e9!1OOcj4T~Y)v)%Yi9 zCwlAI0=ptr>dwr-o8oq#EQto+TI*HB4A8;#E7aG^3}doSGiwJ})Q%ZJb@GIXY(NO? zl)dw;%r;@=P(RWkG)=1`D7e#lg(xXKrlVOu#EODZzRkeMpRG3Q1Ao+-AdmsT^b znmm)vCm!ywHc&?Khk-nr47Ts4O|)7Ss6Nk1WDnXMCvD7{+V5(CZO_l>RbfD-`OJBj z>2op;q?ff<|GGxlaNaA=1NR9%P8Q*b;1b&_V{sM@Q3}8#C<<%c%RH*Odd4%R;kT<5 z#X>*SU$=uN|AhvhT!F-0(wmew4c-W^R+%!<^=71AhasZTL54qVj*^fAc0v5q+oa|a zGA&FFXFKymayi90(!nePlS48_%`-SuPdOdjSvn!g#BEeGFpbm59Otd;g{941t}>`c zp1<4Zd!7Nms;I+llVi#A?`n+mq_umyhe*#7j996o5QR&SW>m2v6iyq?`3px4pK3E) zN*ydJj>{_FadDh}O0jRgP^&V}w(zHXCZf#dtQyBzqKSbKPaO7`nt26P za^K=Fq?_`?dkcU<;R|sSjY*Vnc_;?OJqWV{`fCBkYSy3qTJL0n#020pBK!}7sJ}&S zd$(GRz~aAj7R5|RM|W-*pU^uggBBI`bzGGofonzwS{iejo8NwFy%p3AGK-!JPI{m@it z>i+`l-Wapd^oi|TTAJpiEAk@KP2?nqn7iHXC*4pj^NcD;#K1FV_c$6COaEKT!CUbw z&f32JQhqMYv7y&>gOQ$R)kZu40txh)CBCdY)L^saNP4;4QUXWSwJ U3j{X`l4$a zQmq&;wsu+@P-oMY+?Hb?7;w4~Y$xMm6QlslEyHDHr4oi`A1^ZJjd;%ZSu|8`-}X2n zHO6=M!ZSmB{hjg8fq(b|9JC|VnSi9U#remKElY5gcll^me^$c=iNuHDpysPLftzT~ z%)eN$rM`n+YHX6-D}S`{SDToG zR}lfuv>&^(()O6`u>4gs=)0|ZCNVl7)J0cX6SKJ0zgRZ$NM@{B{n?eBSX*<|^95bU zRQn1`Sg=mY-Ks9vY!(*F?^Z=aximJJ10;u8tV@EEBC3wh*0wLAGB_Z-bAtBz&onuH z_edn<&6cHmSuzITZ|xYxwTd)^f_;pp@~>;l4BL1g4KNi zlO#}VrC}iw^e~Legf(s|M^KsLj8iejLybJ&CCD&Y z7o$#?Dz16vGBTNh5H|}iI!6~kOnHr}VhZ#|M`KX|@_-Zu%y0p-BA!5%4?^Y0O|>lL zam9D$G-YQ-nd+_D)G+pEmEE73MK1Eo)*=IrDuSh1kxt2f7?&g6-!kUlJ~1f|25GiV zjS~}Y$1@E7=B(*I8dcPD$88?RSv)~n{LIB3E1uih_a{--?@sUM7H^(_AP+!5UK~pL z=sH^mc(y1CJN@etJa+NpS{!8$&EJ%nZ>HB@xaKoi4j=We0`Sn)Iz`Y?3RNvHEmVpT zdwAukZ0+SB-Y(dxd><}T9)G{y_UAr>A)I}`FcQRq5gfV={0k)FYA{#Vk5#-@fkq*~ z+Q^Cy4rVTD;&l8P;IF0t2iXQ~bfin-0{fPlOX%0D;yF>5=t)s_z)ZuOrs2Gkmwt)C z(P}y%3HFwID1!SQ_e>7aJA)D*R&%^)JO2cRzLuikD`$5k7vU@_ARb=AD)ARHHd3q1 z#8guvZnyP0<=}pT^2WdR`-0k|ZHVY^!3n1LvE~J{0*CvmbdARV33S6mKz~h6AfDtM zr&>p!{uiWr6^p4F@wp#KF z!L2YQciO2D1u5RA%T#eFWk7Vq+-*WdL3-sV7)Lu*^f!GyUWk3Yj}ZTS!C$JsPpd!< zjFDhUm1@}0ji(R-a1f*xSD5-bkAAhIS8ejXp`Q(P0wqy@d*+i6A{^9`eAN!z)&$qv z!D{ofp4o{e#zT@pdqv+{&BKnug)tLKPKW@N--K~!^N-!5EW)dPf4A5I(SQVDeK!xa zoRq1Wx5mdPF2*9O9`2o51yx9N4Lj4_{~9PRorDO>B!M-R>9OM{SJwaRoYYQtZoMX3+$ z0@Vi^J7HHcaPr+-n^gh5Uu+|uA5YUJC_R@d5^UB91@hstQAxi37_sgM*pe;3R^pAJeJ}B{o<}Q(RZA-3=i(e+O^FVmoCT-LEsWa(z zz5mQt6XML7N~|i*Dw^0VH-|cH<~*f#K3ej+#`4Po;CTnp$33Hi^9-1iW@L5uU2_yQ zW`h7G>(hHRpgQ)1>*65}nh;;%V6D!YW9v)YdR+CzP40!>Zv|$Zw~jPoW4vu)>|Ik% zl^r>4g9j&@6UF*g%4??Cq(iZIgFy%u+T69=t*e5)??_*&#tkv^?&qtK6Z5ai%ib5N z^np(Rj{1+w~Rtti+vWO%7g^R&1&tK?@mh{VOoe$ z3&x5!fRa+EmWkO|SVaoD7<_G(Ce@+{zW?t2%ar zzHAFear&w^8J^W19AmR2^(Q}Kj7aJsIJ0{}Xl&d)Q1+IN+hJLbZE5{vnm|6ifch74 zS$yfy+H{ku&QF2&it})Jj|h40bW0h^G@9E0PVrt?o;N_l1iY zsoNvA%3&v0B8C+jWDeu0ds?hY`C6h^ZOU zgtn^iXQp4zY3_LKFC+5}cqCD?E!G}nZ;dbf}%~hd0n;{eb$C;CUEtlLuYkYP)V8u z@hJW7xSKh91pO1kMWWx~$ieP5jk=lDLh;A`^0f}X>#w{ zy%Q2lrI4}P7R)NzP2>zc?A*r)tN@n{pZ#6&4MFVSX(92$`un1*^U8OHKuR|Aa4Um@ zMGeAtB!I;6^N({B6X60?%$pG#*;Sn+c$s1s-dOU>%AsWjBmRb%e0YW5DiT^C_p#LoH zXXjo=h7=7hfd@L`srZHgd3N6dXVhPJ*Vb<@B?IVK%ZK!?8x-;5oj}~X1 z2zg`otOee0I4VJXRHestw;tSm{Pcjg_U=y?&B}@m!0GYf(1C1&{XIBD-PBlDoU(i*U%pH4uE0Di6Ss2;z+7* z8Isiem2>AQ#cG};oL%uTPa3COcIv013?y@n{RLM-(Iol!D)(uLR{pjp zHQK5~2v?-4Ab}zF!b`%*nxK2{68%=ym!jVcpgF3Gsj`(GXH!POf3R|Nvt6aYO9LBc z#@oo-;bI*B)WzAgUSrIT;HjEs?_!_dof7o$D7J8tU?4x4^NXS&PWnoftuF2n0V9F7 zq%)Lli2Tq3?F@Wvl*^5PKs1oCj9 z4(|=*C})>12Bv)NXLK#ywfUWV*yu|DP*cH&&#kn3fp)YNb4f2=TE@TSYOrwAT>S@U zZ7;M3H+f8G&FR=ug>>B}K13C~%^DOsRXDL}2#129ZGq7;qU1JJvqENGu!8<)Xpz_!md#{hCxq@lCuH6129Fjnj4S+cCR>L z%fuQ^RK68O|8`|WzDtfkDcUx3k^?#&jGHhl8gO9oaNX*>Q5`=X7 z%Tz78ry0!}xplyJfK!G#k;gCEnIJ010F@sJ{fR^8wG(H+l~ov=@itvF?i@MN26C_{r0#G$RsS$gu0*-Bu#%!$bY zl$ozFHLJT;E?7b^tbzi79owdFcEz-mM0%P6XqV$cjbwA^GDGIQwE8exNj}5+#S0Q* ztZHN3s?6qlm6hKQAA6PeNGLvn3-Mk($zhrdZh}DtEY>OY72ylgkVVnVqQWEK=wdsF z@y_Bl&vMV|woy%YxS)b0=Ld9zqJjryn_QjtB+W=+D(czM)YLTq*AX2Zj_&LhJSw#8 zW#yEXiYTt0Nz5^Nj;{D)>$PFid?NASfynh!-5f4_@0fCyxaVwLZkrnOl?&xIANACx z2FwQ<5|pNJ#_5ri8Dc4;SQ#bK4Hpxmvxg81d|2h^8(Jn*7QVpfu7wh<)VStcRmhn7 zA@rrt#At@|vl3x|r&gY~OqgHH$WdEADuI8Gp$hv~rHwN&8^6*kn0Re0M%ZIZ@T_v?ul=q%-Qr%@K*QwwlAq z*7{R08WCsfH|kvwrnHrm}deY)H1mpZvh99OV$z9|l`{26v2EZp+8DIJ`- zFQ{(XJGgc`P1%QONV@(XRp->E36QMYF55P{Y}+=wtS;NeSGH~2wr$(C?b5-y;9oel`Zl5_@vzJwlHs1L+*Szsf1fslbco zzTeBm@pI7m{2G}l6mzd%EnZ&qg5Iq3nJXKwliUK-;vc>KgKf8=unGof(Z8VZgZr_s z(a}?$d7dicO{-p7bS7mkHRR&g>7hmc-OzI19Cf;*&P{H2IT3pCp&!_U#|a*(j&`BZFGxw zrHLRiAiQs%>_$hQ!~qYGs}%UhDSh|CchlZ=T}1}a-&=0?or*yXMl?&%SbnD8+V_H2 zsc=*iO`+hWu9zFS56B(-G?y-pI^iJ(Xz0o*FF+I!uSiqJzWEy)Trn{C{b{VwxPWYH z*wLwTr^4^3+48E1XgWHJjaiu45+V}e_1Pt`n0(<;`tWZZlT8AgiL}rHQ)S@bKNL>* zRDpMvV>6&(HZKj2g_~}!8Xu*ju{ZLm@>ca^n$`p@N=-YK)q!!^0za<_Ltk9H_<7UX z7yFX22}fH($)U6+R%IP(tC-mc%QYbsAN;OP(naV}@^E2Gt(4M$T#6c|<9IRVwwu>? zg{Ds}cIr8(qRRrP29RWXiff4tgtioz(4c~~`mTkmeIJ0ND zm!(fD455hU7DdurLXrAoTJI6k$lUW<*tle|+0^x?OJ*}mU`$HMl zm%i@f=>u2~;??L#!qnmS#o^Gza7THrQ~;M~K|2fl69aHV2sL#(vtmL2E?^cgT0lzs zH!*9N9{+W@7$VL=$8+SAXKu!*-AEVN1!?&l{ig)v`AKC9Y-bo+#w=n8gT}9`|7Z`) z*oIViyTdK|KKFzo3Fa%_PQNQ5$eAKJxmagi z%jN*{gc8C~43kNT4)Q>D+5ml{`^H>QmMFvS2ClnXL7x$K+U=_YrqAG`FmM1w6lR4J zo7EXC!=VR0{VH}GDt%JuBTMxqFJ5t)T$~wJ@H?QP_-{cT-1GUHl=xpbu7U{sM~YP@ zuwQB+giy7G0&h__ZfP(I_=#%(i>RcB-x7bWjprg$rIP?8JFT!9!>R|z+ znVSb^{Nz^EKN(ZE%z%%$oy|qgw*Zs(x;0FMs@Gis$M?O*g_&D>1jC+?9{C8)tOqS9 zee7Ub(z^Q#_;;C=5CXyXxSxY5?+njqt-I?fcJI}XmH^-k+F4URzb2q<9Up64NbF~` zUTCl!s-i^u5B&?bM+v!|uZx8d{v5!LR_|yDKyA|3RSB0vuV+ylBH78lV@E!x2*@dG z!_{f#d0(a&{Z+{eXG3ecL>bb4;{{Y#$M!pNis3HXV1FA|X!}`P=ugEd8&Hr*l8MG+ zgAMBHI~AlS1XzwfI#xtXV-FWJ!G#_mxtigepk!erdaM2hyo8?d!#8KzzXZ_TOIQ1f zUnR!S0?T1z4F^BCiJXk{3nvntxqm%??)PH z&8SZws&VcMAwO{2X@)+&$H%emQt*%-Gmf$ZQRH*o8;0{Lm#qa6=2R9Y*OO2Ni+rY+ z3%{GZ;id|1C?ddb7w1vzW)sj{g14E%)?yKVWZ%9_W$C>f1+1Xw*G$4tOA(t#18o#5 zq5VapLr&yN&PW2EiF@NWgnC~nNmAQn%##3`@4)OC7!Z-QR=Jx{#kA=P}m3`nkWV&MN70{8EY3HRLat)Vy#xM^PRa-LkMu|zgkJ@fO?eL zmWiBrb&cr%!;73cBm_4w9YqCS(I1r2&=xQ?7q*zcNuUxMn3*`o={ZP^5hME-hlTt( z1LW%TBY&kZ%h4q@5m5d`e^kJ`FE4jpST+ZS?~T7_E|}D;^wjT3e^UcKQ!=9#m;ZA} zU&C_NB7U$7oEQ0P7!=^IjrhjcKc%qxYaior1=Hwu_)MKTq{I?&AiFEv2Gf(<;{uuc zbPwlT$iOec$zJ)FuVywTq;AlF(mHT?D3MTgF`JJb_<_y-)uyuIslZ!gaKyFkh{8{8 zUGu#%rzMhSz_peO@1(1F!n2up5U@do_|o$nlCC2ZrS0@(1O=c`?*LWibatgUwd?>0 z`K3WJ4A}PJN*PpwU@3?Jo_6v*s3w(;p>ocSmFQ}h+5yAlJfo(ajb!OE$~z) zAU})p3F`rDKAj$y$A_QPr!$IKvN@>pW-K*&DYzzReLkP^q28nG&Z?+Qf44fpiB&~@LN9rlWZ?U^qVJ7$Se#u)_H_>qn zIdMKzLT?rhXxtEpC80GT?NBvsIfQqB;!(! zo)e>3pnr_Z$qC2o{vwbawugFZy^@A$=SgPFCU{%%e|~OLZI0qYsscAlj=kBQVofun z>3i#;GFiX{0yWZJn_T?)swo2jSWw?G2V`CG3sva|uGxryutpIW(p~&UrBG_A3kUnQ zPikWGLwdQkww4y{!e8;YxX;ubi}al;CgY~L(b#G=s=xlrdylC5glsA~eue6XI!BdO zgC<0fyHO;(>~ThJbdnxfIhF~kBEcI#_DR(q)w%#&l;BcsQ8%xM1)NK&76RqL%=Z#@ zT48bdOO&9s-{m^uDH6R^>k^>Rw~dyT^5heaLuAh}9?g~aws3*TfQ_quLi3NVe${~Y z^uiu$?pES9NxSjabZV>};V>nzTL*H=eGexOe&Wu8MTXf}GChJ)LJAyxCx$Yxmj0~4 z@jigjszBQ4Uh3B$sctNI)9G?5n7FRnv3(=z3vYQ{1WHv}BqvJYiaCvZA2w2k*V)7H znvgJG#5(PAKl08wsoHeKysdXKn_z1S4lxxFRL0-;yI&Y zV6u)HDUM9VCqXoCj{u>&Ko?Uo1VU{Um14lOQyed?Z`ToakGDcI>`TpK5)9-gI%(|Ii+ z8B9$iY(~qz>(n&=gwZV6f`S1P>$jtnvX1^eGcdlRhMJ*CVI6cEl0G@@-}Vv|n<7A? zS*Z++UmT_wDIF$thPCUp6q1TMMn-Kcc0fRAzJ)gIIH6n6C|S}fY?{u_dWXAzW0`7Y zuF&#F&_)TP4$#Q#(0r0+1s8y0?<^+-ETWU##bzQmn$3TgX&0m2I2pFF*d@8l zVC>tZbe6UJd=##5$ijHjExlk_ObsiF%%a8?rg%ekglmUAijx0Xl4!NmVwN#0TX6OR zGTGFA{D#~rQwz>9VsG-f`+=JOQ9xu7W(;&nmeN?BLd-FNzPyw`rZBf4`USu=#Id)O z&V9)cXyN5v++4D^TDGS7cQL8i{1B*c48bE{pN7|y@upO>GC7^~o*WkHGyL$Y_oM0d zaT2~LiMTs_2nUC)Hg^cToJ;gka6kc8IkdK!r9zbEo861!mzZCl!T<>JQE_$+Y@aThTDG_bA;#TD^8 z?4;?}Ez##z(O51Uo3toov{J9RJSzzg`@U>8`Ag!a{kDJ}SW+lVx0OC`c$*(^%B2LP0_d z=GA;EF@7{8{-RmUr)PlLf*v>_WRYdx!>My&@+H6N)9;2#aHua(l2B0Hv!*x?!kiM3c?l=M&a@A;5%Ni5dw)f1dK|R?CUl2 z;iL{s(&v_Lp4!$-1_*IILTjfo0oy%JGkV322Wf<3X$?lMp1vK7+I@}y?FN0&0P9N| zRqbO3)i!z#2XR2&4McS1LYbwLqaCF54+cgadZcm*RWe17p5%iKTUXa&cWrKQ_kmS8 zqOk}Q<%%~feG3}6p*q_zV;DqDccWzSY*0%;1}m0yo^603KQGMHl_D7&8SBQH7xQ8i z2D@b`b)aZwAziUWAZq!(orAfw3%}UYwCm&)^5QuftO1~04NHNbPo7;-d`N`@pt;dL zRSJm>2}(V!tWc&gEf&ucN8=b1G?7%FH)*oW2*zgKgx}-xL&3@5{rOY>s8#i9@e;GE zphHJb7x0I$OZM!A`lfB0-{V5I=lZKaP1FxT%jFDp;@H#v%F-a0h$tVgyC@cJ*%{-) z(QW^z01}|dn5VwG?;6=yR9YP&BGY`hI6U@XM<9kBvcWLFdQ;yxrkYUsW39u{|JQNx zn9XoTsn^SG+L<4LKJ?i|*49PjXj1K*Z zz1GU-XdBmLPiL<3V&EYjTqG;^NnZhIvJ{1OuA;Tc11+gW^E1bbS6m4%G!V#T*{^2y zYTm{{Sca?me%N00F6u|Xj3o>T}7tM`}-y| zw+U$NRABL&D(l04={!jlbJ{=dT&qgIm_LBAS*j%adWL@=YO(OWX=eJd6K{N=R~t-H zCU~ksR;AR|x#Q6Jv73v$gBin#E?A#g6=^q1@C=tf5n7**82tr7=YgbnN#yP)jkbZ% zB;RzL5v(zuRZJ-C@jZtJXcTs7>7TWbked4Rap(w!C zi$7ky+0-Pt-A9Oe7h~UO4?q3uAb4Z=eT)!Qui4r+=_gWIogL{p|MSeSMox72I93-x zq?Tl$J=}Fkbqy~REh%dEXfz{4welhW6PM5q_(b*x_4Ws zUF~AyZ$ zxp#p82goo%x6DPF$^kQbhF@|$eh6)^ywfFF+cAK5`rp-!QvYd^pr-g01OLn2va@mi zZ|-&?8n@*xyXKStGyMjF0TUGHFGx4#+|;dsD~l4m5+XOmk59P-x`t7MsrWLq1M=$n z+HvgA-@0wpt_}K(b<)q0=G>Ougi!MipY_7fxq+F&+Yr@_9iP>huXiBy{#Wpveo{F8 ze~8-!4K?&hKtSz2y=}`)H@g<{m}O6wcMl~};kuLWn7!x6bxA|wyM|OrQRB+QSbu{f z#^6?-;QWJor{BzU|L`c^tzS=9ma)C02qFw5NR>U9fX%U8sJq5^IgDLQ`04B049hoO znp^ivXuw*atfF<#QU-@PiIT%c$<#Wjww?9i!Sl-%9E}Q9zx)+qD1;lt;4 ztkaT+Ynisf8gySR5Ct1{`RbE^5E<@FhGP}uQJWb@I-IUpAz-{-zUP>h-`JZmb=yvpPfydkXs=W}3%QVLF z%67zdFi&t=SmlkGl_K!^e(K^4>owzNPVJJhvDDTV`^EmTJqxF*u6T4AUj-D*gmPlz za0sA45k2r=y*~1;LmD^~@0Nrq2d%M6Q+7F{B-JRntNjVhzujX(_@FG6{uA=C6H`IJ(}d!)>jgM@JXq<>08(IZ{|E-5dlG zEc0D95s+N~$)WYa#t|Y1NrOX1QCOR;wJd8{Ub?B*H1u;gbN$Ri^zn9yGuY22IzGX7 zY{Y3j428>@@h%Z)ESsnqHw*;`0N*WLLx1*980z0RIA~t%mSW}{D|i|Jv(2FDSa#Zw zd~=)TCsfPm@#<4m1-Z6q)9&vW4j21m0zymXUR0Uh`2{C2%%TomFSTb9`n1%M?6fEX zw>c^xo8I1t-zp@akw)2uF|FlVRh>lbsY_tH_509q4-VFMdMSTY2RfQ90QsVE??yon z7JHGGJfQdTqsLgBs~8R}^(fw)Wsc;@w#Q16CNwbt3g{b&UEm>1@&GsQH zTSFoHsR?W#wVLam&Mtmm(!R;VnNogZ1Xse>DX_AL9cucmPy5KR$KzE;Fh$OVA@$Mj zaeFlAL(4>`S-@q6JI7l(WcsjA*9b~(dL)VM^LyupFY}TB`6uft01BAloN+=^j*6XP z0tcplAvf%gQJk*XR&^=p>$F`MwLAiq-4#m@sVV;bk;#c>fRLo{eSz^SlZX^ix+QZz zb%6{O>Una?t3Bn5+|AeG-wl7z6b9MmsMic8#O5(rS4hg! zBG`*Uz9||Fo`65}fD!GtLIx@Jg-*zBPJl@#z;#B~Yt0z;>im!f)2K2j!xEia!M* z=*gNv5BFwn7UxeyPl}W0O}{}X$)^S$Ylwiip)WpDLVJJpP55X#KtDQHp3x{@a*_$G z_)R7p`*=U}B!5;E#P-3%YVUUS)*B<|i^{Q?lL-@z$2zh6mRb8aWe|Y^(G5>s*dROO zW>as$~|sbdfLj6y^bp_eVj-jaq34V}{YADKU{7Wg@HafyX$ZN_e#ZOgm_o-Q_P0iLwWvdP5v9`=1< zu~EXLU-#dT2~>16W5C_eto7ZzO=?fHN0*7CO9M0 z^3zXg+11$pSb8m}KOw1WegdDy#IHg2ozh2YND&pSlFOanLZuZ!TpuEutB%r`8)u2~ z!BOY42S8)#JZ4Wz0#>@tlB0dFEiE*7BKP)}np>DLG9;g)yn7oaD2^5%0+;F7S<(B? zM4Hk0Z~wq1GYZj)o=H=3jY`KyIkjg+Egx%=mT@qb-3J+PfYCPQB%u-KR8&N(MRy=C ztOz%p5{co)Q~PL4s>xC8TnueSRMqVi5`_t80xp79bmkRHC_Bv5(Jp#PmV@4ncjsHGZL|anlm0w8rvaFVMcEINr<%@!uz_6h>Si3OU_xt@gcR+(!%wP&VQlsI zyFhePV~?dj&yavaC%521DmDuhK1K(;Nu>;+r{UlmtBl7y?NMavm1B*P0Gzf8$yHO% z08adSeIV7fQ6`#FLsL-p`QD#wY*dJ$A$#$|ePq@0E1i5}fL4jN%VQE3dhPoaG$0u- zDKgmT@3_#mh^V+$)%c&^b5x6?Ay-Om?Jlc8OA!>otf;gV3c4^Av@0@EJzA0VHp6wq zXWUbANx1?c7x_dq2Qyf#F+7?N)Ft~OfV!%AR0k%PBsN0Uc5<_??L5tW>!#lH z?56O0609Z#jOKFlF}YK@`E{ONp31y~kdy7v2UD7DYGY-0<5IIavmR46gpX4Tb%nJy zuvhc6%AP3ax@=J+v}_Ojdk(7`$-A^2J$}9Yf7p%;`nekrPZ~|NSMsIus#@qkH)t^O(p&e}D{&tSZLcekkUdca?7WCX%} zzAT~2>n&p3>VU&&y+&?VMyu|6fGhRbOLoie!;-wA-h{0p1uz&ZS+vBZup5SZ$vH`n zt!t_({VN(?#%@z`shGC{T~OUy?VDrYw5i%O!xX6_%tBLFnKPLrz7b$B*eg$2tDIb% zBdIXVg~;)Ucs+UsI__CQg_Qzjk{~1wy)N& zM`o|ifF}Ol9t4CmrE8x`|CRGC~;_=r71oo(jB7@%pY!=qPm`HK3 zz1V~>jeJ?8HoOl50#$Z^6tC6)#n$thUQ=UK|LhsQt>lg?McehyBWB@g2r=IKCI;?)0YI#wmZY?}p!t@Sk#obCP=b2O6!ty; z6eE}XdflmK1rM#>M1P<*`XMMjw#0vXDb=8%E;N&zqM}ZuB~Bp_jI8{J5^ko`h+##e z#&c6?tcLU5bPHu70zkreBlfz{M#}%Hhw}g#CFk%7*cLy3hKw9GD~Yj+oHg%lkj%ZR zfX)q41)IfIs`*SSmg|p6VL*|EGL~`5v|||zT?x))pn9t{Z$&%ti`6~NwYmf?^z-v& z2@MIBZUH}C2In1ahi}yQcP=K7c_d2gFk}{dC-J6YDM(~2fCbcd-TpN5$q}EQ@KR*K zw#Jlq6^UBNjx>R1V{U<(sV4y1A*ramP_4L5Pi+l}5G1ydNXL+BC%k1PTQB!zE0R;H zdorgcpW|Bt;Ql)o(8&!x1_?!?3Z_=ydN`1o>Y7`esj(PYrFN1XpU31OeZxCPBQvya zDmB@u@-W%}P?4+koVq_mWld3}e(?qdBZ$!%tY5@)%vR7jtN4@?o&s0hy%=-YVd|4c zX(BT7q35vmhqLI^U7!1NQuj|KCQ(~YPM;0|@L)$^OzMX`*P`VK#G*;Lmq zSuxxI_HZxaOX>vwqpjo4-hLg}Im?(&qd|g6vJ8Zg$;6kp6jP zZPPvsmaTgTea2I=5o4V2GF2Nd^(74(*E3&I>_7G@=NlK(bkjIIt7C4E!=Y0+?k0_- z3SsGIbkQs0h1Otah0XsUFChYH&0`A$y$-!%^#0E{`U&2vo=&{9Mz@wi2K;=2p?ia% zZ3c(VSA@lHCC_vg@p7i}AG*1#Q!jtKAAkjfzl+|O$*cLsvhR_rQ;D~s_g{*28a0dH z|CPH5NNNA7U5@{?w;bV)MH~L7+I8b6giqNAXTcWqUG=q1n4G%J6EDcd9Tw#IXO36a z4yUijwyc)sRt5Jk!YxNL-+$o}(A7W2x%Z-mJ6^GTRDCT>faFTn6~Ke^2! z_1MY3GQI8!r<2_+WoMQp$hWHVmb2`ncCPM=KS@gUigO%^N}1TjK=&vs@qRzoNkdlbW zGD%7uV$ES^?k30x!JEE9N(}=-!JBi~eJLtitPdyt6~`%|G6`ix=z#SWyvD{O3 zErTJhJB%A(v8Z9B18naA!?tQCym$5L@pYOkn~^)!YE)g{$V}>_;VqE9KwsiWFMk|U z$eZp4uP78EA}JXYeA#tt6BbpciGx1Oq;7afjGp>fF;c^hdzQK^lXS5v6WzovgX z-^_-gN&JY?)9@jyI>82=#$lBP1~lN>hSLT~i57F-Ix~szk)-!VH7eShd8+jiHbtJD07Di&ay_KUCbD$OGgC5 z5zO(5EDM)qeKxKDzWtDiP_jYu4_@4U_8G|M)EiipItDTmSzYodY}Ps8^Q*P?9PT@; z??uz2g=94loHg0us8tl-GiCi!gH^azM{X00up;HT7tqW zn;22M(mZC)LaXfdwRM%XZnQrewxC99T&j(=emIFZy{P2qc;ef z2nT7Xb>j0!KLk`ow``@^jjkxWY$ zk;Ig+rD{*Lo%A*NqjHGuM7DNxKe!s{bVrTQ(9imk_bHY^%S#2Pnq>lLie?RsNCWTj zuCOUO(A&HY700<;C?!MC|E&BPO?p%NaT|DN$2vGxm|1dNG>mQNL10_D8ZGE${kdjUareH0aI7%m6 z0pc%$vJf*nbXio-Gp(LC%0{I$pN!vuO5CA*LzS|AVKK{bX3JQ3DB>(Il=?&7b>IrH z}JC|qXGfBy!cxxbK8WvwqEW(rOZ<&THC$$U@T>jML1CoAtUVA&_WX)e3 zN$(3wN@{9rnV=cDcuzNKx-BpXc+br8f#s6z0{h7o={YaWu6c*KCVd0nG?<>smn-#GbgIi;@3ez1ZUgII^jOh1TV z%Di!zA)7k!@V#SG#>Hy7A+J(=|12ngpq^;;#Smb1X!z+24-sh_dRwa^`s>oPV z0_2KKwvm*Ujovu`FwJ*pzyJOAi_6Sn$!OP08dOMZq`zGZc8ytu=P@*>JmM(GmNaer z$-^4zdEm;<(+RR6-D@$USACC_VH8wW6-UHDWX>KoTNxQpIW|QXOf>F#58n~Wl9~3O zvklUo^G(&4*9CJNLVM2%!S5{Id*pEt;>@+aT+=#1t`ONnBqS^Oi8s7HnVs8vfum$b8{1Kh= zm18lAGC5vNzB;{s!=Q)I8HT@WD6jn{ARHYpvA*R}_l4ZkLr`}N*Dp{xvmSB(w`rJ8P1y%EjnsCimE8U-&ASr0v%KdleoDUyzWfl6uBLC<#1UwOXhf zjGiA?2!-A@fOa1DVayU<+RQ{(b3r|Du`lXRzc{o+{FfqTNIACLQxPtV^0HJwJrbli zm;uwVSZU=DG%FYoUr*`mxEwG*H5bR(TQuG!Gcb7twA5o)nUJshv~CcaP_dipW%;|U zqjJ%zG1X$Olku-}o2HlpB{Sju?Og3*)H@?)y)id8#x?uV8`?XiiU3#%^(*5J+$SM(9`2`b$=+&_fh5_ z5EuN>^10sI2>EFZo{Fd>88BAfcXb0|Pf3$lK4-ijhXGlGCvf+9mfbNT#+j;DUcrH=BL?J{j?ma7+T@{FDf!qOc+|~WeP9NRsgc7TQrZ79J z`t6bzYS~7a&dBAqr|^JG-s9Lzt3xhf+1&0@jsOh6Y>0sEHxnnU;h2L>qiHVZJgRy+n{1nDvYudcEkXqIJM}UA}+}0-%MJdN#0aU(W`JqtYi_dd38^_C^mdd#PZhDvm4z<0 z7zIHhaA#B;4GiiI)oC-K+ERY1T4?nA?fL2^$MHu)K1lLb zZszVbQGRYkxEVKDt4HpC5XrkZ>)Iytwq5}=Cq*Y&Ypgu$tEhD<5${kZ$S3q*30+Ux zsvM zs&4C@YwvI$G;MUCE4uVR&yz{2qc@zM$M_f5)ua^mj^qBt;KJq4Ua z$3pM$plwvvZr9y>^!kmOlT+eR_g53JPZgL53^hFOL_gS945;|Di=Bf;n$;|< zZ-*4ksYr4tUTs6#PfZf+kU+XlPe75?sjf{)h{}2E`h_`bvH#T)6fM}Oy&?!upled2 zo>@N1v*j=PZji3_GIvB~bzQvB_v<{yga_}#8l`3A>P3dWd2~@vpehtE$mp#q<+^LA zMt%vQWA?8j7{4#_vQxGm!EJDL_QMG*VPe-_rbV@F1Jl=1x~zF8AI`Q?lfhwR^6*y4 zy49+n!brd9M{{M7u5^!9DEk?piSs}hd7k3q6T4mhjIdXn?(j|%>&FE4W+evQ+vHDE z`?z(f_WHqRwV%W3^QYw;k7ZTh!8bU7{jt$*hQIMu%ithrI#bs#Ni+g&gb}nhiZQletz69Ea{F@`{X8i%?LI z-Bdw`;oc}C)Qoe(UpnRGpNi`)& z?T>F~-dkHDF!N&H^vV4_iCsYuKqg(aFhA6huS%#?OHP;d{{^{!Lek}>t3US&k!+TX zSuf?T!EZ32_34GVGj0+9;J&%Dz4^RvY2l92n^P=+ut``T~!>9)PJV)MW;z z&HsaAQna*ejMlZ3C-}tMbz;EM3xxPo_X-VbH~)Mr(bqa*h7U$j09a1w|Eqi?Ekk0i zwZZ-94&{7r1P0DdUij$%K0maFb)2~(F3F=o2}_fR|B{MVp|nV%_gMUoCRIHL17Q4D z9h2x7vr-%S5$}M;@i|%uZLEV0t6^4m#XlHv*N#iZzUBF$53M|{d7M+4fRS>T_%MkJ zSQeOZBONjHb5K4#s!4Uis#!fw7H{kI;C9fn_HT%UM91PeJpx;*U&VK_Kq!8>=BkSQ zWR&Iu)g{90&@4C-c|2L#tYB7lR)%JFRxI+(J_$+@Ku3W0D3Z z+lAH1q-=8;gYz~fWgq@U`JLs9E*W77s$ZSGPko#vy%|Tp1UV-m@CbHoYy^-ezlFPilYs1tNCQ%iAj!=yj^-A z(1&>mnu5VHP&>=MClKWMS7w9Kg;&^I>7!8OyVYHTfAUq->>`o`qbA%qhISws5brM6 zO%Gw6*R7ppI7wUVQJhb!J>qmaSwMg!cn~$WdAtIMKBUO)2JYUG+&2h`_Xb2)`C8GJ zxAe{aAD?fAH>y95!)m@w_=~opB9xGK3%aQC5=SL$)B{_E&P$S(#Ga-2C z!|F*}@ig&=m5tC0kU9k3V{;wPC1cOt;g_n-$4$oFh+iLbzRs9Gz{3b3VQ!zxRHvG^ ziPz=6XO4uXaOgTz?H5N={QNvWwS4-%>U@CDigPfPX>gP_`71^@G@(V4w^2BCq(F7Z zVk;Jp3q*Sa)13F!xq7+?$5h^8-?Uz{Z;2_vBUEeTscF%qzb=ZC#2cweLQU)~lKlTb zrPh-|od|zUp~`O6J!tNi6d%?357IjJOZAeE+$q>`PLh$!NuDOLRt&^jTH6vJ%5(uX zaoE?^I$o$0{wl~eX_&&VH;PQmn~9UibA~7>P}WY3icdG~ie;qDm041I0GiKSa%fB+ zmB}CaxsxZ-Z`SA)ssm>9oJ+SX;=@t6o{CJcAOrBNA$=N=hqblisM1y1*VxcNdoL0RqJtp-Yj?D308fPklhImIALBG~f+ z(ehZFBIvfLH>^dRk${k8LX|ODiPDJy|K=O(540tl#`lkoHD3czc9X}BQ7b@=ILxvL zPK6b<8qB=)L5fbOwMmC+Vlt6Th_im5a@QVo*^-Nz#q!k)`yD>OmcJWm?&cpD1dzJ{ z);JC8N$u^tMd*8;l<#iUWKvwTzL-*l2D9PsgeWIBqeX+?3O`KsuuAmA)=Hvu;zyw| zF~bvFSKd41HqDG_>A)4Pfdfz?c{4VvU24H8?N^t{*Z*Zq=lm=GIh)0pA?Mz+R1=J- z)3o~&7KtU)zvw*3OZA}5z}6D1fckS=5Y!bHF|q@yQZ^%o?z+@8+hx{nVF+MSI6y(u8VR`( zUDG-fcUSROcXK}GkVNyr^B?2@-{=LY{a-{7P82t&6UeigD9yI-S_VNyeeP?k+Bzs& z^HwE_G;|F;ezh))Do#v%GBDa`y%9K!dypAJc8e~~(tUF7ORu0ZE~ElA z-|?1)Syd#F+`7CeD7yfxSwzHMoQpE#ax)hBMg;9A$q*VZmW5vyT(0H|u~A9axd)Un zD7dq5`juOn)Hm(pDqz%G)$K;66WGBAy$jX^f0Zqp8tBsZ=CRMOrYF7KTXbOER{JEBWRO;=Hv|3J<%C5D&1kU(t6+c%T1ig99^#sW{aaJu}y zydEhebZ#IYIiM$gO{V7)gPAIShYRGHF3^kqfUumy(8d*TaMIuD2^&wUK*4QMwnL=d z->2sYH%xK}5jy78J?2%5>D~AWJ1Z1ivo6g45fVzjbsz(vL#~Zi2*~=^!p3DnMpv_=HF_&JjC!FPnlQ=Z2;&Gf^l+eqr&>Ci z-1A;x>SEdl?~^G1jd9I`{s%O&iyJgIUM6j5*CEt9`vIFiTH0}QxMk7kZhlM)$v$G| z;r`R|x&s4%Xz?RmvCG~8Mb+h$@;17i=Oh*aYSsnqDMee!L>ftr{QH(C=5V>?Fq9pV zV4%ybVy`>l+2Q^Ky-(Y|QPgrCB)A$O&KQ>l5AuNt3@JVor{P)1AYq<-t^z{RqAS@( z6=Fd-a>|+J@$M2zfP1-2(^gpX}1$Medq!emP$Y$gIsMoA#W z3H>R%j?C4_i>NHn=6knJvtWTo+m{{i^-q=bC4BmMZy};k92xGEkiUZ-xz5MQQt2T2 z4@3^2DY@jW~^qD{HOX(d<$XD)n~DO`izdW=poWs(yHa zx=<#o8e)$lyZ?2=bEr!p=V}x$i^+zRZIx5472Ctlt34Y%Z&~|(f7>W4JosYk)wCNx zoCFR(EoY45D&WsKM`rJrhix6*b+}jJXb{b#sAzZy!~mX2?WUI#?1;>sSP{cmF{zX~ zE1VOtB{>6&J*|pioL#;vSXS1h>ReHp%~r^Xw0qf6NS^VxxV@W6XLKr`Dl6L)U30#s zZW35&UvTOl=~x%;#ZyO>viT~yJ5&Qe6_Ox2tc9gP;R)m7murt^L;vxYo)38s+u-%@ zStjAc#vSsLVUp~r!r2o8aIrvBdI5%wWAJaI_!Z7Hs@tmD)hcCiX`D`5?WmOoUQPorvOrrJ} zv=;B2W0Jc$nVIRCa196xcQ2BYN*Gw|)yrhoz_uwb%0nunEEX-SDV81Hm%>+h$qAN$hH z1HBkP#`nD&oRmPV1PrE`!3&`a54J8$c8u^Q0B(9G6t+x$E> zK-4E4mRX2@N&{T=uiwzMvrK5Y(=R}U>&Bv5r_hi{_#6?M7EO>~*>}@%rHM{)3}j!8 zu%oG;rH3P_aG;40BOS?)^MUu7+tmJ2rN~TY?(GMMy1{VECLC=T=l7#h?*1ujGPenF z^<@3|28S_)_fKTovmHM0Tw(pfb`5c)fC!gmeRXt{{;;69A{yKCGAdv3H?iWlQV80F zPXud4yg0E(NrHJ3Zl=Z|pOv7O1fzv>241&B#R{^hX9_cZNnV`1V*cba_xG!TVkESg zyrsbttL4JLDiQ8HCX#ps5@De~J`Df{KlK8gzY&{tu^8E6$DwBVRWA10Q>V{SfFZ6h za3oXHvL#>0uySU}$$Z}liqMI)o2ziMM9I3cN15GIZN7XrYX98{MHnwfswr$(C?Kd{6V%v67v2ELS z#T9n?_8E6{f7riaFFbRue_Hf|-ZKLiilr85OzD;wQ&(hcGf?E0DgnVFCumb7wB6{QbB8}&;61nk^%0%m zQ-;BVmn}kbKn7!w+aTl-WBXF%dXlej*9j#dgbhtuRQl|Ur=#1^K7HoN77*m6Ua$7e zkQMdDsK=|EV9rP*5UE>>Z6}Cb(Nn^^i-_cs9N0r#;*JVj*HfjHa-ef%%FV8D5ONw-0z7!W@YNEF~|P2 zAeHJh&1_~4Oa|n$jB3ztIPL4HL+a5FM6U4D6KGx4ix6$iA2rT9BJWM*FRY_Jt)FF) z9y8%Ydo+s6C=;38M1;0Q4h#KcYzBPM8zB8FNw!PBtqCGJ^Owgv=fbZO_yAX@-_feqC}5OV@5rz01?vMu$R~`T(;u{t zF?4ofFb6<+E2Up?965zAFA^Zm9Lso!kiwW)@G97)n9E%0P^S5Z*Ai1U{=r56-KeNK zbm(=dVUFh+5q@WE|&&YOl>IpXTnDVLw+#WVNw^+M7mU!(Z@ zOs)dH!ZfePZlm{z7IJ0x9UYknSwo}@U`>hxz$?>G6l@RSs!8RV01eg6%hDhy=uRJ=uFFOPrcHa z*4il9CR|yBg{DbA2euVdXL4Y_Ua8I1oqLIuMb|aD z1rBh{aemEB+}8#0olR0?{4-IkC?8km5QWtMGK9u<+#ozq+J^t9H$b5dM@W)!c>vzN z_roT`^35&a54h}T<{1uunyp#AH|1bawgn!JT5<~k)Q8m4(FA&N5FI-?J?f!=51GqW z!mgw1kU>j!_{mMZ1Ldevm8%z^l}$vX?tV9v^MN0E2tSQf22lQTWb66P;MUTP2gYduFHw zR)0On6MG_0%~199l_t1PO1hEP+r!*~uzKPjR)bD}Y|jpAd5e z#z-w)c;k0_o3c8i1wGrt?Px~jhN$xVl9K%IHD~pMFXGC4*_|4#)qLC(8%QCC%LRHN=IH@fR(T3FNCzBEwI2i*G zm%w}(vBKOJ9uSO2UfN_UUo_KC-;n@0$WG<)UCM_GEz6k7*d6 zZ2?BbF%{9mb3vGxJBV(!+p~_B#O8P5jEaUx_=;f(ApU#f8r5DYLrMHJrQi9RYfNP^ z0&V>mcv|aM`sbOF+g2Ay&1wqI8qP-KA2%8>%2;MjAv* z7){9IB^=2x3w|X`=p3_>s&K$`in*KSZROKhHAFpAF*+KX26Ctla<5cc*u=L+Wwew; zC_;gFa^ZI91Q#|(dk$ae>6JlPK{M|*H*=1j$4s{rwadRs1_3Hh6!Ti`vVX(|!)dQB zx^ochbWcyyFjq&755wjww6e6(eoyT%<^F{wmvI-@_n>MX`}$Sb5`O^N;U4xD)9c(PS&+6rB1Vhw7z6(E%k^yzvIOBGSTn z0*#xgohQ}68g(-AGd$&CylvyLCLWj&oCG;Ys&;!X*+jU*CinoN@fICS{OVCUs{{w6 zpwuQ5gC-CqSFz>D;(0DJ3uP>jKNn>e(lE09rsD->gS#7|D{6D$`L@Uhsr%2NF{svs zV=6a-V9M*XgOM$^S5^z^OzU~`yxcV$Pod9HXt8UQ3DQkIB;vrE%=np8Gb7H#D>EzO zPvZomFcbuyfx!Tqye64wc{}myH0raCuHdLc&h?dSnJS9H3Aj;pacW`-!mwYy7>z^A zEmLKXtqDui#abJ*KCEVGg|KTjS*&_KYbW#)lu;3nR0Je&2fr86?In^aQEHq`KL>{+ zzF)buHhY4cu+Yb2+^$1HAZi+_`^$2VnKGh|g`UQTZZY%ftf0Bm4=U5C1qB02d#dv~-Dib+4`ROtdH3 z|I};n;s>ff^j0Sw!ygpu0Bc`gZy0A8N36jXVsmdzz+muKP-{}b0)1cDoRGbL_FkNN zLjYv~9ydhjx5@VdV0&|Qx*6)F=t@PKUv2$NUxqN+d6#XPPn@3ZW}j5F-9F>tianHV zgsp^t2|~nw%eN1+FpxEIQXZ}YsIm&*~;uVk~d2MO}j*Rm6b} zBuB1=W@y6ya8dK;`__{MyWkg2XN%?Pod6nbw_gv>f9ukGQ!4&G_e!CYt1SEz`VLN@ zCN#V5+TK|%7xa8f@D`2E4J#Pdk2g*(c;wOY6cC3Io8? zGZK|)D^FPQa}GjlNV8knDoFGGLPsl85hf#beRGV-_G#Rsu-s`emxNJV$ccR=N}|gB z{1yMLe=@giJQ&BDwK+vJu@=DXAuKYFn`01{)zQ#4Zfsj=sm7F%h2>f9FncIK90*I^ z@O^}XT!FoAg0V4m+9~`c5Xew#3gCb)3t93%77+0r)M1l)|7qXBWOci;{WiJ@{CpaI zUc@8KPnc-&HzdLonW_kO(kvP&F1+(X3p1r%qmHA_*qI+#b<@B^63(ZU5fKqQ$OeqDxN6)G#8y;TM$v8G$@dLizY^_khfS7s!}^d zCOJRy;SIa|_xA0W!#2QOZbp)nRxbsd)zz&37>D7;JC!up&i}XhTZ2R$(zM0%I!eC? zST(^Jb$OEvn(~R(TCNSi%$fgZ#e4if7-`pZ+6HeEdjgH66|^>>Nay zw)TXxem(sbY*4+oA=X}{;&0q!2a1jahhJ7F^i+;cHR z$(%nE-GI`-`+R{JyiartRzO4S7zWHSEIJ1^U65$(Hg>g4Mli0KRjM5d2}kGjQk-3>ljhd=)dXYFifuQHzT>Y?#oZ z(1^24$Oh69DOL~Q?zi-P$-Y3O4kc0(r(C%AfbKkH2P8aN|y3ow%5Y0=#yqH4zZ>>_l$-qj^56nJl z?!S>Ney$3?SOPUrq#Mn&n&#}|&pAx5Swly$&EuL^wO=>$q1bVVb=;wHi8&NP*^6z? zC-1wnne-m_^%694z79}TEOaRb<&&z?qV&NX=M-sgi0qN*3t_aO_ZTNHIlj!>j@1;G zezJ#{x}whjhXbBQo6x^|)DtTf2LxV{d4sqcydjmv$Zq3AuV?ev*^s+{v8f9VkwC1$ zJq`$RzecmGK%!RSZ|F(htnStAH-Q{~9(ND^q{r`AZh+J=S{=?;0}ON4$PTEcD;}CU z!Yyrg^CJ??$=Q_1T>ADK$D_kLrqk2$mhTMz(g-wwavJp*jPxmJ)2#R$#&ZAS->+`< zO~2T(v*;|mMfc-@P%4?7YHnrP(Q&iewoY!^aCAXxVylzJ4XViunI1Jz zDg0D{LnTsHXn8;FX|?!2YP)0kVWg^3oA4LWI~qEFr{~AeKZgv-rK(nQ$!8+#UAgST za-rG*Acfps8+j0uv7kqY=#6^RA6$Oj(O{k)BQE;xNgKGwew+t4d~4o3q>wKH=VpM4`g!Wpt>U5=LxVmNJFUny>1;NBY0E`Z zJSn>+cYM#IJN{PIACyVnr<=@lLk3b7mMe+|Xzk%eKX~Qr);Ing%H_!o)qFywp)gL~ zYDE!EmX(b-sMb#jx-n%p&Ew8*$p?e^m>o?ya2u#1AQJSr3I=92nNi&G^U*V2e5AvSh?FhF%Q z(j$gkgu*c%mA^%z1$Mg3=9x;=X#WU0B{whK|6^Ij96>l{htZ^Gg%HgiezBUrZwWD2 zbI-_*5pE+=K`UOHB&rR?y1ZYjP+h|Xz!TR_R;vKVjsx0XOipI|)Ql^M=mZNX2`a+x~!uFsGEKvAUHPTKY0z@f= z_48D#GE2~{ECy`~@qRjC@C=Uti+BN+$I8e30__sSCW7RjR4%DffabOkXxOHfXs^ed z8;NqvG^gkh<1(t-Vo0>Sa0<_4ZNB3+*ml0+32u{M9^#>(ZpfD%{992o#J1M-mqxcR z*mIg40?!IT-W-0j&f}%&qvQ*u_PI;*9F7bXS?c+Ew7gG;i72Z_%BD6Tpcqs6T>LqX zov(QYfB4{3EitjGSr=s_K{^oxR(RcqrWSA0Ul3elXfiZ$qx4)xA*wpOHnD&ca2E^C zP(NF@N^j#+G9V0TE=*6aUb<47-!f7zVEjBDaRq-t?b;wJ!YdYhymUiH90ZP<+BG-? zx2%WN-E7J#`vkswOq2@X&fWwH^c9%hxS)SLQh>ECaFdn?W<;F=);ZMCW}Me?9l?L8 zlBP1CYwXO%ue)Ds5R7hg!+*(bsP%ph64DA2#VY%_Eji`(ok-FgogkR0hk&4QY<@;9 z`y+OH!VyIE;6*B0;GJQJkjh#!(#W^108_T))nOZLSDvU$KEVZ;e4HR+s`bca1ES8b zlCe%sVMCJzBMBVDsgZ=&JR*nF;3E5LE9Yvls@0NAYuos85?(F1+eV4nG@9ry*?VP% z?IyK|((^(mz0=Q@Jls*Oy?!yE)0#?2H-&DHFpPh3f%hmtTaT&1+O)TELhB8C&1>*lEa}H<<`u% zDGwGYFMvXu*s8pu8STYF&r^QA2dVA`6CP1u9@w#_6V?Ok?1ZgUCi^0M}-P|MqrAPf^|xFHB;?ZNvl`|gVw})(LxVu z74dF6qo@;YR8ynC@^?L@_zY$V4ko@EH|SRFEti^*aUham>b^_=@VP2zK-~DkMm-x~R1P z&Y4vwYcmHIF;8>t&myop<2Q>P4kiK#SLS0ZNNtQrT|MU3)mLfVCycv;K)42cjgh0p zZ-ssjk7eCK_gEGR;AcllAYvk2rHtaXY))U<5y6OVmXh{wo~&+=lkbNRw8*M82`A{0 za~J~Xg^G&#&o(&?0@{UeIl0-~^IN*&_z14dE<;%9{2pCec7)<{Cc-7=OC~r(6E8Til zE*Kr{D4|&9U8yd|4R^d3O9_4mjzS zwNNU?nO*IS;G=i~lyrAPOnW-kshbrYCjs6B_ncRgn!;x54RlYZ=4b+81&n!LR#O1U zm(wPDJ2dFb)lvUBQym)YQ3suB9R|S?R)B>q@M;{iGH4DzHbv@~Mj8%lbrIa~0Nm64 zE1i%)_vJQcnmSQoEY+}!3mu*0m)yVZ@Nd_DYwTDo!^EJavh@z8Bh@Fx?KYN~-80#P z&>04Jd-EvtS)kK3GVF0b2KdgNu6aP9HvJlW2!&2({BSDHg{6N*Y7x{Hr8fz3Rq|Ye zE9B7-!NhpFtiGY1{;reMRd`5S3*>Z%UF-=IivE*_(+2$*_l;#+{>Vs!_@cf%)RMsU zTKe)vr=>*AG1k4Y^=wHuh!mrMumGXTG#9|%c=h>#=7^MPG)baJc)jJE-x;u4z4LfD z1o3V$-)4YCQ2>9oTNs*D2avJD@%_sy1xs@uSi0+u5to}DK;XP8uUueJAGtI8U62i|7b~$EEn(==52m@)iN1-RDr;L+ z*IncO_7F2T#eKrt_ixPd@)ghu7esJ8?`k)C704Vzu{sgSthpZMmsUrgo1x-BE{?w2y(dWfr!=eydV(jff({i7X#x%RbSy$Q!t?a&KN(b_R0^ua(ti3{gKa$ufe zy^03ci*#h3zWza<$GLj5WOoFN;;gE=L{_A%BB5*EM>*?zPEa=;K27yyYZdMXHM3i8 z*?td_W#Z#Dhv&^N*Za%}Lp@eFxc(|HT-gID!4^o;8Mxt4t9Qy$xIF^`q$7VRk^1>0 z05kDzN`4r8IUO384OR7i5IP|TlK*GC@Za`X04p=+{~0f|=*rgV{(;|l(R@XGRYSZ0 z$1*^(f;@BBxQb@Dd2(;e(3a!)d`}TmA#sntwM7Aox^^XtE`EKLqAn9ucs9Er^p&|u zHd%M!-VUfk45$F~PR~wbYJP_yUGxGEoE!Gy@qK({oyi-x0Qx6VfjCHawNF!#azlgIAl@(g-On1u za?6nBRv$d=bM3Pu>+J5Ymq|fI!1p~V*dw_HayJGtN%Tn+EEAn_C13c2 zp_QYs$FgD*(LTiGB;*#9?~e(`xen9xBjkg zdefxvM(4p-n)h8IzPWVJrbit~Pt@t0t_YcOy3K$rxx6$D^WAm0%h&F8)6Sv`g1wBO8HIpl>b$unETW?9V5W z(gp(7)yoYl0!3a)nQ9Aow_&f&3s23Gp1C#Rj;#VCJ#NHKR(^HK{z3&gFIIe9Tf1*g zZXU)R{xmlQOJx9yMPClv%yY>|uC?-<-yUtLl{c5}WrJ(#&+{YRn<6srWnwTKlTEh~ zJG({a+#A!nk{?^ShH(VXR6bN7zao^>agcEJjzcEjSmqhn+|`+9g)4qZ4rAM1CNXUV zQ@2neg@z!P{Nvz&m{{hZbfG$|jJvvg(K0XL@;Y7abWa5|pu-;LsK0RAcjZ;7cDeRB zay9>re5T#D5K4Ewd?lFaE6)y5P6wOcOf-@mM-WbblE@UtW=(Qv^J)$v%8XK|MpTm{%C_zu|4BCgk z2swf1hdrG~j!VS2tsf5A_X5+?uPnLj_p|w@8#ez2#K<#5CAb@W?IW zjUfTbA7e=q7V;~($x9>TfOh92%<<^GS+3XoSE5Sdi#DZ^EdJ_n<~|dKCKM-EBU4ID z;|!_z@?~)=h8@yP`zYUW>G#0~{roCj)UiP{;uhk3?=t%W4Bvhph=r1Q96pZP4F&Zj zOaDpamdx{@Xb+2VWxU<&^-)fr4bK{X+erZ0ap#lxC`O%mqkxcktb)3T)onFOX}n@_ zkbuTU2wjZ?5BW`5?pvWwY>Qi=v_+~sN?)QfJ2qlpxNJZZH5_XiSMBPugyo(W6knem zI~mm(`w==_Pw=Jmy97L=$t1!h%9h;d-tYiKJ=xEHheU#|KA3oMwhANY_ik+KhzdxW z;?99v&DM)rSg?~mjA|@qwxDd5#|+EilDH&Ck&X>bmLBgwF>L$Ag>tS+SP7o!6WpJB zHRhCO&Qcdx*~n90bCfQHe(prT8*(Q^B?;jf{x+!5+*>!Y*JIXQkYSq+;V?Z*J7y=H z1%Ezk#v)#DklqBs!jB>dM@%{}=nvQcw+*+#TUMlSoZ-428rLU-P(lv#YOqxr^7A$P zXD^>T@6!lI_G#NSJN5+8$Ro!sm`#({DnLal=%o3?KWM6E&4M0@N@f{Qe+O~+++Sa7&O2u`;8z= zu3qoS9}%!po=jQ>*e8_5B8Mgm9}A?oRvXrAq|0nBzHDl`ZOC>yd+9dNHipTA9efXx z?G67M#K+=Q9W{n-TKw+4{qRRYKwX(xugTocWn+u=I zCA^9(d9Bdh@qsQF$8bnBz*X6&*4~Gm`^V6%sjhjXr#7!i%YqfV+kW8Snau%=B1eqX-HNH6Yenni*@>mc~r&%vK z^f%-0kQ(N!aXma@r8G;P>$(lTP8_#AL7@Gs_}|en;(cl(c60zTu*6_n?EP^$hpBRg zQ$VkH{C-hbE_7qRd0adW{bYA`8Hw~2K(}!?A)Y*OOWeRRmmSY7W73>p^|>iHiiX41 zbr2DsX9y2jp&GIG=l+fq2m(Uu63+^%_p8>0mw<##17=S^_HX`wKv~edZvy(Hb1gyj z2DpKUdbs7hd`JL`3CCT0FDG}S$&_y#U|tE(n0xx!N-sb*2i1D+*QgFCs4jSH*{&jx zIV|;*WN*%w|JxI(FBj91;--66tIdxcAHVN~*=6;g!eE)FAsy(S!iD-;(Afpi1>?9% zMU)o78Us22d;4%LS(VjwZ%5EzSzJWf5f&}$2&3$}^*unGpBmLbqdEZWo}aB`R7pN8 zMVXi*y+lsI(;1PNurVbDuIh8OoWv0?)T@=JcG{eq5P4eIuajUfVS_p;!XyCK*Et<2JqY|MG>?x1}C^ZnT zT`ja|Km{P@wRVvSL^j}aNjh-(#X1MdILnSEp}vf@q#mQjwfoxlgkQ3UMWA07^(Abb z8l4(k4hNljxTS9Ys)KfgN)qnr%NtWpr}B3*fsFo1(c+g^vRQZ_ zHLX*c_$GwZA@Nm*3h91DQ)jn?%2%KS(x><~)jptA32C(735(oxC|hY>C?SA3R_dIr zGAd|v-*aD35Od-xNe8G+SOHR+ekZ_8W%07Pcti?auD5DQB+9z?O`nzVs^m@;D~#6z za(TJ4B}~c;Jdtt%wEr?zS{WN5gaxzRIZywLY-5-{Haia6$wJVJ^!>9wRcpJAXQa1| zycJ+j7mJ|*<9M=}OfV3LeW5n1Dm3?qtVpPJ%uoyIjo)Hf(ExJTFqH$=KAyg-OT%@i z91$DMhhCzSH@Y9>CO8n5#(Z<95x*pd>}0$%|G}MhA}6|e>?#q|c7hi6d;Y%Y3%wiNA~!s3PQ@?Qc~P4iz{eq5u~}m zE=6AU%8c<{7g5UX+?GH;(+1o)Q@<2QQ;-J0TVPIGb6 z#=s_4*O-o}OVH(C;q<+cshLn$a3%n@NPI7zip~@RB1zkY7QU(9%65r-V83d{`{(Qv z30IfUAv7e2GeLNy366>s-7E!O^JHl>VwDRQkBnf8R@yh`L73ld?6R#32@0+FOqc&1 zeqF}6TCiScOFyjcefI;b|8-$(urD8Z_*i}Wm*U?x&ln@_&1IX z(J~%KMc?#)UlGh$Aq7$fFU7vOI_JeyOd}l6v*E`t;*2ogk0?_petK=~SKo$9l~3W{ zaf7Q>yC3(Ly%ceP55$Or81PEk4~>a%S*6#Sml*lo8z1frqy=2xsMljdThQkL!DGBHFI5F&>BauDpN<=8pO%flMGb&FWTVm^HwbH}~Z8p4BwlrV*qPxvp`=_$9Qc8CoW zFs76V)-qGGJgE8CS>t39!Nt|=km0&Z{F9ecMs`rsOpR`UH*&Il>P!1)Oqy!kHczLehinPbJ(To}{tJ%1rxl7T9m^E_w!Ojbd}Soj z=ejqLm5+ZcaJI_6&y9)&6U#67ehYc*NolC$9ZM(dN zb`iS!0#c-8)!^9b7^=UVM4@3WeMapq*6lmEyj!;MEIIdtM!k+;2i{{NgReGl$>Z=i zy=p9<iQIxsXygU)B!PoK(U!9dNfS%AMd`G#(7vOl(!1_@&9JosYllxS zvV}n3L6Dp+gr5vw4W~LRWI2%dPV=+BZ(i^^O6tV{Jv+d-uHxf3GM6{a^XRW^a6jWn zLWgzxcPMf&KSe3N9szPe>|Ad98HyA!o5I)BA!)_EJFn~w_OH63hc>-yWQ1}sE&tEPJsb5Q_`N(OGezK5kT?-)Dz(6R z^9=N^X~XBt}pjiZLzoXMa9IW)E0Zs~}sGGI}YYT2$zd6uX&cc7lv zRJLD?O5yHMnTBEk)MdYAM00>olUi2<8;Z)hy)h5YW(+prPnJTYXxYj|CNBskBs?Le zmQ#WX9`*981}l~hQ$d{<##g13#L-7tWcgs>C2SSALMy&ne(q3ksz_=97}7NJRm<1G znnte9L<(mpF5vu_WS(uZgsL+J9oo(O0xtCQm3q?!@gqvwlb{v5X_SGk3?aMe^ad_- z@ne6(a$SFp-DMau_D>jcxvbxPP-t^tFdXc9(fH>??vNs$c%H)-Qy|nBA5ST^P;4GF z(z~~fLdz~~X+3cx1)YQ}J&U@sVBzb0F3#DFt-Kw76QG?Regobx0L?83-ge0Y&V_)0 z`CLD3Q&f}N={6LTK+GmpfA=zb&7ULRbkNh`Aj~vssuRHfTrdg#0VUyO$Y|0Nv z;#&ox;G%zd)$uR&a)3jBmMo@aye>NW=XMCPY-4?iW9z)h0BU&l9B_(yaVK!)1XK}d z&2E`I4uHlv=4$=4C8hd;6rqKn%yC>+lcR+CbVOJo*QdEsmDtsYvt=Tg&)ffE9v^op zQx(2JX9YcK-FM{8YFwOdL2`A;-xeB|QcG_I`QI_nhVFo(5bCE$0&Ll)5zAv|=C|>D zpS2bzXw#z1cgB2v-l;L0Qfv0akP8(VtqjjZ5qY090%Cyy=Rc}Vl5^LRlvulOwT%|&Bp4ee{Mj9Mzm(4v!6Q-`3#B9IO$1Z zlAZc5g6oYnQgR!9Y&n zDBU6uf&A3>H$H9|muhEe6DGBmK9kEpw^+b5F4oY`tF;gn#EU1S!TOp-^U})Ox5mqx zUUT!LtSKrQ#V$sjNU`M}3Ogi24BA}L5LUeGwi}MMpXjWn;Hfl*a}q&lhmSK@?QGt0@2EH5w#yW6 zJio*QgF-ptZZsR`Jl~?7ER^~M)`v*wbaveKQ^xdsUhGp){FO=1+`D zKaKhR@dRz!Y*|PYvX8bq&sh=6hN$iNzm1qWEM-+Zo9=p`YS%W@<==XC!yfL%+rREjfZR6& z%SD@M8rvn-51efmAXJ>$CiC2Lw|Z$p>$WwJ=*XVt%%TN})m^91fK$a2IUD$nE+u<& zC+pl6g`b1Y>8R{qJ_`g{?TSm=0ms|zJxY1rHg(#y+#bA`);Cmq5TC*12{8$nIu)vX zoyahE%T;=+vmWo;iwX{&wDMVW0HHxa6-3AT7M?maI5%q?REM(E5QM5GJAOSseGz9P zP5gGM5Ke^~e7mfY);oze%E!N~an+SOuN@5?TyFn$r{hO>1?lo-aQQ1t0)Y9W-Ox-L@US@gMyZ$_D-p!lN=jx?Q+k*kdzQOyoDV}Z zX5qZ#wQZD8@?I#sAN3l^r%6JHrPf+BB>Z8yc>j^+93i(#Zkr;>ERbc`bfy`o`9y`Ss04sIWw}7AR4P`C8O6 z@XB%HMyt`QH(b{8fO?o{I_b=UT%FshEC0m>zkz0V;4=rhrVy@5dWo;Xx`wr|8poMW zUmb$n5n{12W;*9R+tB3H5|56iuObY}Y=lMPEOZTRd}6*UD9AFb$dZv z-|=meN&cfolFL}C@mijr{$xLgZgW`mwT5gsFP7(dY~Ozv5Vl`_?PU{sG>R}L8cfS^ zLpQa?e!8?O4tW5ZX^2!_!I9p~@pY`M&n z0u|m*TPy@@@30kT1#vH{UqAMbohgUGnz$_TW)p9xRU|unNNrYRqCpjODObDm7`KAy z!V?ic1^>1fP{b5@KLbO|xg}QCE+P0fo*IASG#`~W&j;Rz>50XFh-j z5>=KM^Y7atL!)VMvPKb4^mt&<=Dm!OyKc=v`Y8Vi!0Y@z6@968>cVTUySf&pdtis< zciYA}EWR`*rVxhd$^c0x<$hK2>dws_nXLxqqlBCt%?Xwq&rcZU2fQ~dxVd)&vK}Qd zCdDS#fWx882ZwZ}RzT2@Ffnnyi3(48beGaJ>sXN`Q|?GeDM34Vm~!J%&|QwECrpHz z2?R3*v`?D%$f^{(>a_q$Nmym-|C)WqInB9>BH+=-(-bmwr&*&{@;f-rZgD<|Xw%*r z(3h*N_hdcFJp>o+y%QjFur%Pi^8A7F{2g9KZg)?U==_r+z=uPaG%)p}fF~f|GIhFq z0R2k{x!-_jBN?^igR8UgxH{3Ht{~5Lx2S~$sD%UTU1z2DQ6q?s;-hwV3~~Pb)INH1 z(Q><5z?a|X?9H)UrcB@hg1IOGm*3SI>3VaRqdvTUGmwsn_p{g0bSQ;5VsAV1=NIOr zL0z--n=qXRklwU=o3FcKGdAV zs825BL82SfjE7q0oBTpT!#>SL`CE zvwowMg|la30D5l*fq97h(mU9Z*F+qiiRFAef2OE!49`l>4ZBY9+vKQ6TZMJK>7n~Y z6V^TFYa+{LUa-Z`^mi{DJO|HcwN6wHppWvbvcybW3z2?pik*r;V6h=3+&6{JWPNU2 zF4f^H!^!6+qd-92hwwV3BEG*Y^H`|(!iQkfm|3)Silb)qdOsHX2kxRV#8$Jm^D@#ap}L?Bpg5L3rBc7x8)nNSE7V z57!v8OGp6|y!KOr{alDPgfuul2|iGkNc>yj_=H9|MfzHS<8__PcDq}#r&rD|lbWFZ zAhaT@SP`x`%O1bJ7Pk{daR*!mmE<`%Oc^B3I8wtbjxA#!+Qr+Hlst)ha4>>;us=oB z`x|ii9IFs8VSLlgElQf9dX7c_=-_UmSVPRGNJJ{L?p4LLducOw=RQB~y?zb^u~M@B z=kClBn0Rb}Tu+nYn(2J}I+>=&6ifbo>s&8d<^Y+6tU5!H6$4{DWg!)bW|@Dc~*3S-ve zb$liL1snW=zVZHHo{o2CvnKk%W5p>Od2z%g!3;GTGp4MYI>?!5Hg3YzuKa5n8I;ET z-<#c_XktZ(H_0XzIT)+JlPjU^evhv<3&(S{3&Nl6Ub6w_`_y|s*IqkyKL72|%vMxM|p8wT#t(pBb$GRk? zLZhpRcsiCQf-Gcvnvf%9;69IalAWR&fllJv=gsD;-HO=*YbGIC@_i5rWXmoX)*o5M zarQCvo#8=eZ*CJ&3+MbU0Hw?%8YYX(>WOXZ z;9}$iUG&TReQ<00YAFcv^1qQ~rTiIdU-tb8`ug8udNrf=O4n6ebV6S#jR6DG641-j z9ST=Fi!qdklC%#|du$>@AI*Qi_kgMh@XcsKJD9M1i3=F*-LGG* zzJDtyAG!jep!gi=aQ>ijC#@W6y(!R&X z0Cbt9r40`x4CE}f0qrJJUz4`7KVhCBCyb9NjF!N)W+QkT`LeA2_^Y@G0%W(dYhRnd(LIm5?ufB=I&`&UC}FjAY@;otxNu zSNQ$QpS$d0cT-roOQ*T58^JwNcn2kA*mhUp>l4rJJF%+m!9W;0m@y{ z`xexMz$Wdr7#tbwrvXMO|%%N3E@_5}s zlAO9`j&8Mb(ZE91f~IVm9n$)aQ!(1}K)6rRu|o(HPqf;x%g)&L()b0CHll@W7xpH; zQCVP|aNg~sJv{^rJwEYGl2U!>QDxv~`WVw$&b%uyl8cE?IIe07vRg4c0K5tbCxz7I zl_SI{O}mO=Q!}Z((?PfzP9BrBq0XcZq!jjo!m+$d|NclEW)ez!2e~zn;9xR5X17p* zg7S0HJBvr*ZiT@vy(0cOhSOy~Mu#4*T-@Sr4CVcRb- zdgTBG+DTq&7eeexpP(#i06DDU`hY7pc*AT=)9t$2o~z6k*l}xja+COTZfEu(dzk@M z$ZNP+p-I!M3mE*tTn;9QlDk=B!t8#<2~GN-mupr0J5N zeO%5~A=e;yk1>$HMCJJvU5huEK@Rw+f;7f8X>%ct>yKHf#(qN%K=DfLxY4K9++-VR zgW(&w!j}g-&Q?B@ZxQ+S(4_~QWsaLj=W4=0dttfaJ0g`&yvy`ru)0m@l#}E|PQqEM z)L+rUDuH7e+aLUL^7ISdHT0P{A$A*XWs{ApM&jg&F!{M@MWEvM`kBtrIs+;n_^6S4 z>OL~`uCCO2%znE%pl5H;*Uc^kUY)wkRZs$Yuy80^TMf%vW4j+_ZT>G=6`5wrDQ^IS zZ3WNJLi=he=Dk)AUOgQ3vT_yD_|S^y<3R->+2F@-u8QNqA-qM3@IN`A+3KT;5zM6GhgMDiM9@HO=f12#AlPsF4} z>A?fA;r$xZaP6VW05_@s!c_QPOk|d;Pe1c&=Q+35!`#R3OjS#v-nc)I;I_Wt1NG=p z?K&w5H!ADhfVnBEZJ%W*=^p5U5oHv=t`Qw~?eSABp*CFL&qfV67{9_xuT(uC2kX#L z;<;=!bpw44N+EgSx!S@?d>S_I-}lkD7+XT!VJ85WgO;Je$zfuU4yV=VGjw=+x`0B>6hW6kFX-l+7EO$W@&HDEbV zJXTPwC{e^!qa(*fJCdv2UUy+2J>=;}cNU5n4)Z^5=eOqx=WV)My4V*0xzC!!rkZ8G zEu@a&f4?gLg7E>ScPsLy=)-PIlDX~Id7~lg6lwedsknX)sLRW|9xlROPA!lmHcTB9 zp7g}wfbw}A8kiIJcD~zMx8JoL?gsS7xJAudb?7C!My4GowB&rdRiGWIM|b_QU(?(z zJC@Vh9jvw#*DbJ)c7NE!GNoZv#6xfs1l>opi$_N+a@uQUeYa~O4|-h|A$05Xl|J<* zYNZB&HkG8-8h-H1wrj_zLR}qGLRTT=eh)-G03E$?QZoO}0>~om8b(EugKKw5qr?q6UMotTGL*Ny;P&0d&>2A@;}!6w*dBxE=V0Q4W(CgK9Wi4UgEc^Tefse9OM8 zROUQ8B@dq%E&`_pU8ln*UxI<$BgQo$n|;gb*WzGjo2=&~|y}Vnoh1sL}|`X7xN~56g_%z1#2~oDk{apg!9Qd_vZ}e02RcA(Mmc zzX+L(Z2!w1r3z;vn&dy`|Fa08#~HGMvqXYcge_o>unx127T)(n-#;R1wCL(yk_%Vb zIO|x>2}1^5x{hxZqZHjKvhdTU9xpFI4<%Az`)Mq?ZAy&oPn%Sk3)K~h!0=|%?-~CN9QS}51rNuc zn-KwqpVwEclZ;aoh~kl64vQdg8Mvqh^!AK8ZH ziG9vC5)$Wu)uMnARxDRJ)b? zWw-bs8u?i;cgs;_jS`XuZnoil_C$wHp$%zQMxiF2)oYlyeJPmO(!ms&$mm(<&=@gc z9eb2TS%EW7)O}h#%GS_s2uh;9=*Y0v-_srSW+OVVZ~f%}61xJJ?!Ocw`uFxv8Iq<5 z%*8zibp57W$y+#*FfHKlLKRI)iXLy$&VPgF+x>C-;xCB|u3C)Fm(6ej3z)>3%p7LYJ zrQikPrO6in1*=4oYKN>a#!#n}@&*-PNl9t#bsXM>#6dGjqA_Z5cO|c7iOAbJQK8AYV#j|;%67*U^p-KH;W=eb*|-&}2Y5JL zib0v2><++&XduwPN0!W;S%I%IM^ubr1j`+?_%{In_ZP8L8MJp&x`mNwI@XaBKuK^I zX--`Q8(jI_axho@P-~X)1fPc;YgfyybJ<35O#o%StHLuZROS*Nk_)qRgeNp$J(xFGI*ocJP&=7duiD{C-%Q0z)o+)4qJ)v>8lP|j~TG+n*8tzp#X7}hD7|L zu?#9yVzgcKN}lY@yIK>pjT}Vjs>oXQdO{jMSGq-eNLh_~Bk>`m?fJr>vBD^wR9aCt z%aC$M$;KECxvF@Mw7w?+U2quPv}DHA3epaK1+*{Qo_O85m$gDODi5A# za%clpSus@f@uZE)rc~zfepv!pYZD3ZtBQDHnYH`$)F{4;UXZ83+y@pkZC0j9%h!ST z!?^i2MHi1^7p?yYvsgH|OLGi2F5GmDopGFEUH=kdO8LVBzuXV1n-^Z)8)YZ! z0b8dP^R0^Ld|L*=lFJ0#M#cbnNA8Pq^t5Z%K8jU-x1z0jguiL*C;UtB1)4}@tfR59 zpPmQQIOR7C;ZFaeL!WszS36>Y`~XLol#*I z0#?Qg^unRfS#v0pS~8DaJ3y$TZ`%nYInftidfQKH3O%nE0eNC}J=T{P3~!Ab&PYBuYc?wqL(@rIE^$h7YJCn5Ll# zgcG!)g3t=!7HGO4f* zEOLeYf1>$lEWonz8(cQy@zh zbt;;v5q%!p3mSR7o^bzK>K{tPp{P`Rmv%O!^v3ZdjFgoE*v{Il>x|pDEsdyPX%nM= zE{9qA^n-^F#WumtXcew09UGV<)_X8HnciBbR3OkU2w#Z*z1Y6D>Wgy{MP!Nvtq9|J zScBLcsf#Ut+-T^@;zODL93hc2 zBo5=Y(Qe78kT0WtSf-DWg3pDH?!@-mJq%bFl03 zcs~QwSsP=Uvdkr06)2}M`_iAS22qn;R%>^bSTXw@&^$~Z9WKpa@e$g2(sc~(o3cRM z)Moa`x>4WK>4u%@(A8x~1(@2F8%<}SC(2E&j9Md6_6$F$b7tTBOF_ip_bw98R=yx= z14Ur1I#-a_S$+&2)s1pTtywLJpoD`1?EMg`P*mqO2ZK>^9%+Y&k$xmOF`*t#-BUa< z%!tbe(B^mPjY*VST5tB$U5^%+jg#yP4QiXbn|$E|rJY#vH?h*#!MHpgtWzP-U1GH$ zyH$I^#=v++eO3@#$}BxrdQq~-n&cyD)QBEEjUg%ZsXj-3Ct>HOWCqd zlo&Do7OOal1R5q3&a&MDS!Jt4a7rndC6@yNs894?e&OC!5T79GFNuFPvliQs z%;&wY80uFIEwGAvOg-y^&%`Ahmg1A0}_a@ z;6?KVvT-#O_q!uz=q#U`XN+^_#f6q#)}^sWucD+A2sbk4pseD@6n_mjpaWfGF1_^u zSTY;Pi+1g6mmXes{FvSDU&td76*JW#H0m6frf(W&J#I=q%8%YWsGa{w*2-~S_8Gkd zX0395cZ_jPUR-s4>Y04a2bAu#xjr2$ADt8$_+%T_y!3aZbsgRORuZ7-J;%E-PBdBGZt@;$x^iyd!41o&j3=fVpEwa?p!r<*&H5Wo zy{I$O2edcl_HD5zX$s=|qBG>j<0Pf$yA|~F-u&%*;C>6iWeh+KiIR*E6QN?i6cIMd zNk@S1)26BFt)c2#9MdFm0F3@MSMB=RS>1;Z__|%VPeHh0>Yf6otR(@bAD!EE=fjtY zlpOhLe}^ORa?Ibk^AVYO)D~Zg&{5X;mtw&WBob8<@E+q^4{vmNmm7zQ%YR^sUnnO= z=&a(Ny5!52S^yYe$kh~3%C3IoG5eeX-RB)hgMfPi-3)0ti#`r_Jh27L*dl7$nl3Qy zAbB|E>m!-ZO31<;?L7|28%~tQ1gl%ieKtRU#1WI*9_M-Wt^xPmhE*fo)TNbMV#jgIlJvi)PWHdw-mkk=_yckfRe3dbks^SNEMHYkVj>ih z8Cupp_=Qq!gYpZN(2%aA|NOSyEc3~^IDbqgnYkkLM0@`%2gbXtM}&9qh1KzS87k4A zl7az2>;(x2!J6)f7m>V@jX3RM_H&2tpKlHGG8A6Nr3tau`joX!XXl<5S0TC_(-OP( zjK9Jt5(H#a3P0kkM~gKh8mg=6*1l5@1Wh{IzC}a+&eZ;>*Q~u9shy#-JzIjZ;yFJhEzV z!SNhG#t*`?r_f-!f!qjn#6+W1VR^_`Cb>>0X8@fU98d=p3+qj+Hx^8_w6vF{G4mN^Zy2Pugnab>>*0X9l~$ z-wH65WgeBg{{|XF3!-TpnPe%s+TY|r`YSngugK}{hLg~c2b?Fu_du&}Gb(bFa zqa;pkq!gi!B>e;9o>gStZAxowE$x|(egL30G`U5-%x$nv=Ln5J7(aBc65b-r#9tYh z^HAH@!qnni^rNO4S{Lovk{f|XHvWLw0GUth%#dOu;4qppFi@o%9IOt`wySG!@~3-a zy>;fWO88I-xZ5@z9?KWLr8!K&p6}Kf!T6S7;c9DwQvjKGzt6BRn1#OSgF<0<4+1!s zI8hh57w{rnPnFfI>s^dWt8ukd{}E#_((~)FWz$)sIRQ~js;CRUV2V}!tZz*%T<#2@ zY6x~l%QnoKv!`EI$%gncipgz?`4ngFNJ~-|(JG&xd)5!(DX3V;GSMW>_zYF5;u5v{ zRBA~g22}?$B$-JHhIdyU7P+hzh5?|$&Ga(l*qDu7V4$T+Q!LM{R%(S$J|&P|yZAQ` zv=%bIPb_d+uE;O*Yvfcb6>O-kw6tey&sW65wFQC=`nnNmubC6O_ybqvU?k;-P4wW< zbSmcR_Rvimuflr=@X`S^eg2_KN8%VyP<^1G?P2JpqpB>1JVP9@dWm$bhX7?iHZv+5 z2mI!`A)M64A76X-+WzJNSA1 zSZKx#(MC*CM1Z*By(CvvwU>T4_^Fq(KrR(I#@4bD^WWP_ zsWYid8rgB)wv`Ojk|f*eKu=6ZWcNua+(>hEt_|GC8=qTbM?2{PmOjdp2hfC3+*Nel zH=A5eO<}hYQvTaz_QoMu4k=O_OAswU<#kC}9=KZr>O|9I1E64MUeH0w><_P00C3ot zYdn#NbW%E7fLi*t1>#h!I+GQm7(IZ3PR@@E%8xzM|A-D)^8tDR6IyY)DCm#A;XilP z!oj|r$ifh?eoH)O9LDb0XrmyldlE~5lY9rJ9m!3fgbG&y$^#CcaAc>3t^CL78jZDgWa-3OWi1b6lYbUel_6-a z&lT=4G*QMcZ(%c_70UI+JWfBdT6uk%)FX64QzoHy920?jRzPgTYg z^Qy_Cwq3(43X*g;mbzDjrQc2qR>r@GUI{jAdauD+NYNgCCQKflOg^!BER*iIxSAUf zKQtffRsquntVSx3^05y5BBBWTF)>^%KA({mIEm1D!Psz3|HNO2LS>l2(}`gfXQ@1a zBjQ?chxXZEk>=*35#JC0%-7lsl>yfS&C2|a3sQIX&?d>|j9TpOW9$%8%WSFtLJ~l$ zgESHlpiSXTV6oZ0ne_AsLG71o7k^b6L-se)sR7<*>s&*Ph(O6;v4$U=E|d1k- zozTGS$=2_S$(y4;mV{HfcZNJB=U#E*d^};J%9cCLDxCghBy>4WYL(S8f}6T;Deyr) zngD1?W5v|!fBAx%>dIwbNGX=BJKTT9GQHPM(O5t!;369M;;nlbnu|qrqf;WZ>Qw}7 zlhts7UQi8TC)B}*#zq!3r8*V!3W2L7Jz~=pq0wCS_EJD6YD0hI6_`?EpOT*PL9I7u zae!Kl=E5BbUMoU|*&GydRPJRGA7FTO@&SrklM-J>DT&9<9K`KQm+_%fQNoEcFm1-j zZBCpWndJ?UnGV%+?#wz5&%&SOscopMR?862&lx6|C(`apZ@g5P5N2UURmwgYPTE=% zqb_WY^!k2v0bASzH#vHCs$CK(x;0qg>O>58Z++q zIOtD~8{C0gY}ssT zsHgMHkXd{{wpVLAP_b`j)1yiID06t?n0`h#sndfWDQ;Ixy}xERsUL7+-T)#cR2;RL zMg$wBLPgy9X_4bkEx?;e);7ZN@xk_sN19^yNOZ^B>sK4M(bN+nw})9qUF3ykYh1}0 z&4g+wCpoSPH^Nx!d{)sFu_AseEmMVfTv_{jyW6ZU2dorcH2xuApc?rFf(Tb>K)`gV z%&Kva)21VCTho?CL>-amSOI8k97-4{nCJdmefKOGVCkxP*jnW>L_#V9do+EpW@t8$ zx7d|tLL^(sc+HB}vK&5pj5fUj7^juiufG#M9d4^7LVqz$Y%+IvUT^AsgHXC(KqE=$ z-&#sBNQcy!wWG7|GMB$t!#X;gWZpVB!WCyjy-U6xSWfES`a8M>+X>jvqz9w37{^Ti znO4w{Je5W zn{#zQ^~5P)kTGkG2?oSYc{gTFVD`Z*!-Q^8%PtJt9f`4PKF?qC5H%CmUC9MBGuw-G zsTYl81gKy#XQbP*F3q1VQHDUq;a&;Yq-xk2F6Oy)ST!CJx6V2DWx@K(*zwvEp-cauN4@A(um;!8+%#u130GPUV@fxdgoZefsHj);F-Y^TES> zh@m5oK{Ts!iI3#V>$bxy_K(WS_Fh^P-t*d=j3egNWeGb6|<4mxN8p z&AU=h$O@NZukU2I(Pkm2v0QZRvT`b>Sl*DmGC615)kAw}k7>WU5wFpU6n#HxUrQ)W zQmxAbk1fZmX9lRXTM;h*Zl+P&;N^55_6Tr-lvMWM)Glj8DHjkL2*zZT{YsELRBW`v#9%3IhDFZWfglEiBNTYRK@>Y4pyA z%rf2R;Cgoa6~iQNYk^ww1(w|yYOIsfZ?xrZOJSJRZ}=iCRWCQmpyj}~t(wn2zc$Ee z;kUe=ex$t@E!KXK4aL_oG(7=xG4|hR=MYUqIlSkeayXdAf&9Q)DufQld8;2!;(im1rjdI*SR!(Fq9asWz!MtQ&7W!PAX=3!~P-B{uMCVQGeodll6P zU7+NMr@3vll5vbB!ET>4Hpw&{4|I{F^}PKjTP4K~5^b~B()eT>YJ+@vjo4LY0uZI zEQ0UPW~SGhm%Wvey$MyGLMCvO;F1%7P>lRppJ*yC{GXGaXX!I_D%ZS;^?*A@^sl~h zJ|9);)q$NqJqSH~-hh5=CDAus4&BT==T@m5+v%;zYNq zFk+oUY6)@!zS8YwrjX$r-B27k)z&R{oL-UDfbGm(K`HZBA?54G!-)B=0G?oggqN=^ ze+FUSiXE@nVi<=Jmd9dVvYsqF$`cm<Wko)gpiv1zx6;;Nt%9x-0rewIc&EqO8gtl53_FWqjB zGBe9hvnp_w*!Zim>b#?W!3nbf-13%8+Vg<1jKJfK9{S=+5!Xpr`CfkPwc; zdi72`*v18Yr(|nD`dVCyj^6&FCoo?913AHPt^r>`j51Eg?ua?IODOS)8f2WPK~cfs zZ;{;~2;qu-&h^=wA~i~@<<)NUn82pys*EpDQ;DW0CZ!r0L=)5TjQkNm`*xChtaYY)UF|N(1cW9M4zW}e^J6ZYA~+MT8L%3-ZUgMLLQOiVDSvLV zB2&-3LAy8(G7qV~yrCy%5S^NZ?jcCk{Q%JjH})q^1dZMlmwDW_pnqO=LWs=UJ!hyauG?a1}_>3ZY9;xKI3t53ZvLED%5+hP82E~ zRlN2=S0>ceRPU0=K(!Pbn|kE!9@0)R{mH&Uhgi`QJ->dC1oy=$VjF_rt&}+%w2x}` z&n++zSUZLM2L;+o#6IJLNoZ->U#aC04p7M7G2JuJi_j7bdDiFv3goibr`==pQ?rq> z(K*nFzi$PBLazk5tyWPD!VXkb7Pv z$CEQ{_9Sb_TfcLV3)k?U8kM_gOb(ELgd;C8Gth~&Wk$HF-vpq{pyKErc%yYoEHX&4 zvN>FHOseLIC(4nYQYa&Gj?$DZ+VL-BA@^5{pEOblJ1q%GhvY*Qx~g591^4A z7m^CyKA!R0HQiZ+&LqU~9W^2s8}OpgJ1tZV+h=__`;YEM2^E zoH(~#n>ejAPf&q0h6SdQw;&qpetjg0wNT}$^cyC{9HJg2no{M5DZ{#i3Tj`oe{o7b z6Kx})G1HTL+c!m)hNe5cv@>r0RA>Z=IVmAI`;znQe8!Q8`E+E&mAS+Qhr>_c^@!gF z8BKD8qYfuQS`_H!7UXJE$U0#_tkH3PsrpSKub&!$z@}h7Its?)J&TY7vu7tX8dpUt zUgySvvpP}DX+D%%zEL5QyM{F;BATp?U)vnOjCy~RPV|m`asU?(i}i&CapvN$Caka3 zB-sy*_W6gkNq6xI-m{Uor0%RbtiueG307D{(r7dVm=5GwJ9Z>(G>vKPj74VgD$tyT z8$|S&Je(1Ao`|YXPPE21;ZtN0@$t0l2ae5&2i$19ymVKl>h5#9bNgD%P2jzXkK4<$bsa>dFJIhm(X>n8|GI=+~!er`(H1QlJ$VD9#Li2 zylQhr^r@&={812OL3~|u>*B4tXyWt!J}AaMEXs7q2_86ICze}Fx$r6f@{t_rCP>F(gP1Z%54 z!JO|jE-oh!K{S1UGf+41gdYLUJgVaWh$8fBoj9u=28F*QC&aU1QlNOI1_?U|5YB*v ze-%h7G72T5DyN{ilPKoAez+M{{~_4v6z*8Dzhe|P%4wl|OwpxC0n)1Ao%k9mq|-Ro zI=GDzI6!2MBRz|)QHoxTMBR>+tVKPMrq+w5)k7|bHLicy0SqCD3CI7n8f zBZQojSr8B=5maE! z7qieL-)I)z1hs8M_GOIlwyPD?L(#yZLCjg@>lLcMBH8e{h15u#B8o)^W2$DHB-xKL zv21V$+B+}cKrnDLxDcvdZ;lLZ29FIfUpHGzJ+JL&CpExl4Gf|Pi9(pkKguggw+@8& z2PCil3jTvu&xgeglgYQXt$c|w;|oP6c>qMZXmt?k+r;@GV?<|4cgY5Ro{hDwWF^#S zbZ+jIJN0M-L6^K(z`ZFKW+fZjn)Wt0-Ev7J~jMw03Mx?}|Yo9!fGRa}t?bprz5wFsWnTyZu)p0zib30ZV6QE5A1sU)XDCMpREnJu7`IWS0!| zW5%jMxFOJ|5egB-ZXsV`5~8-Q6v0+kSe(p5CLC|@;2DTNOO_xsyNxI`kw3V`yRoou zg|DflZUsd#0ZJ|cr(Nf+wyB0d7Xej=2BYKe?Wb~AZr_Zw#GDRKqnUvyRXEI*22fpU zO-w8{(WHm&Z8iE$jE)1Pc>uY%OPp)MBZ#LdO=_vGLszg0noM40e5%1p^v}|LK`=nf zpuP#p%dI^VLofw~PJ_Ow6f|_#R2~wMZUfsC7Yk-20qDEUhFp{8ElMo0O#ih+(Y z{m0Z{a-k4Ab*+~5O~sYHU$mKD3LxG*H;+`=au3jiA}6c+yGG|qPl?&wg&aC1Sznja zbnwO2r{U{`0718lQw!IHQ%LLK>G@&frR4*|6(fZwlUDro{`uI~UH6~F+B@_TJGiyw zxWRNo;M$f!_wpA*3*d8q;)SDL6Y5uA6?*95Ly5a5_sK?l-vFT~s z^0_)G*96tqH?9%sfve=610(Yg zo+L=XQTHQ%k7{@LIy+k3A0CmcaSsZ6RV3#XYB`2~trx}Q0j9o|V*c!0+Y(%B1kSp+ zHgs+5bi23mZVGhmT=n$SuX=dfKWr^cZTQ&%eBUuXI8*^$wO{pL58qV8H(v&oVyT3A z(7hZRI7AqBFkg?6y2jye&L@gmb^J=lTN}1F&$haT_d|QN8#`7#t5~JFfTuOay_OV= z)m$@O9oFwPKtuHRN5w_Oz}EzY?xqfZ(EP{1@z}$_^*ZxX#T7tX>qgv=338!2r@juO zN@OMPkMhoNL|@Hte*2sqDrrFOL1E z;nKgMf;>u;qPdvVCu09qG<&ckzl;`TE=yhbImx_bPAbAK;t0CetmRhH$H0jrR zk*guB0qnEc)F()Sh2!pef~cxTxURH+TxrEaEl`2jmneZm%QH<4Xm?MLpN zMhpU5nL~itRgW1bPsUy09@-r6F#-9~8cCi`)2PaDh%3vz9@SHRPg+6510H3azDN5r z+=_BYLW^QRFL8a|Mv`iVAQIl5PtW>24GnsgyiQ3lAf?nv2QmxOwj)nC=i8LZa8k*_ zcbwOdYe_x9@*js~$B;Q?1oFFV@FXyw#rC3pCyDrT!g3mcZOC@M(N$p7+yHizUNdZY z->Bnskf{XC72uQEGAS0a%S34snyXK#e+5jMH6JKrkOZby%7Gs-97da0>Uv+I&69jb z;OTC_a(e6q*I-88VCF0isPD|xYWiScdX_gF0W(u#57_eoibE55<%kG*dxv0KcZ z^Gq1RwPrHH#pN&K$m};cVS!HpE{XjzAUWfK@9J6wz2=vVe#$QJ4-OLstfYq;-w?zU zrKM||xnS(xZ>3zXk;y*qWuDphE2_#sc4i+_gKpbsMvlSNPmX~A?m>KD9Jou&)_L1R zj+n|3$x%3IebYHMcXr#4PJ_!~SDr9hNnC&ZE^Eetmmn)wyyxuS!YjT3t@#F7c>-1f z_+7jUmiF}GVL3QeI5eebsv?XaMK|#Y5-mUXettu&FbSqq&`-wPyAF;Lc7fr#s-E3{ zmEB*`XB=%P(y*%msOH(s3_!&>Rn&F~cfavhp8Uv^>A^sYYpFwG9PX8VF_vJ%>dCQT z_Qq2amjqFXk)DK;l=C+C^Ru0Dj2_LI!o!`z8jx$B{qR}L@XfTZ8)GK+2 z`ej-n!huE1=$~r;C^*sG#95(3P`|-Jq@k9_-6@IGXioA1^{O%l_| zH8wyWn(9XeRk}r>`!wEueX2dAtMQxAWt-4N+LccVLHCt0Lz01&jKfkAaVpyYiO6{Y zmOH%Kw;Wy5=y#%NXBG+6B2wDVH#6??ZSA5O*7t-f+R;PsUFu z%{WyF`;I%)y$A7%WEybAIaODoy^?8CV7=o0yW|8Aoh)6FNJJK!1A*>STZ+!sRLMWm zq-eI>y9v9py`lbGGi+ZoTyK8?EP2>C4bSGX7f5@MN)!!NLw_q8Y=%xK5?c>;jPUW! zm%t`(LjHEurQ4~Z`_q$|Du?TPc1t8}t<5Ill}Qa9$R``t+tfpurRGm;7`_pz3$wfT zPlsAio?SXz#FGYz+k9k~rB?C6-+-;vQ`=wF%hW|4fdEbVK6WYuO8mZNWlR|bxXxy7dr7-? z(e13#&s>+Qc2{-TCOs z`9TNjN$xBk(qrMnp`1GXaMveflZaqnbha`Ql#eZ4WqNyFydEnfi#GQhW-NdGk@QU65!;6$Bc(EO>>Acen zjMG`wEfBo-bKYy2S~Jc9+rUO7zvaiKwERZGk;i@T_A@eAr`^(r)`EOn?gECXrzB%< z>tfOtLdbXW9>I`qD%#mR+i+N?%L-8f>>k$6p5*FFN`cI??5`rjuWYNAWHy`b9mBel z^(BR)5RMbZEW0DX91&j{6&0PHUuN7f)5P(@CHm>DEWc}OE!sGn&~km#R3h(79O_%6 zAjey$AP0ZFpoG<-<4A;zqqEIHeK)(}J#Gz{_tMnec+fS*seS=H?JUXZ$#gPYg@v%% z#yh(_9z&@9tg#a6J=O1SeIXhGvbGwj!9h>t4ZF>xn0sYJlBGf;{hnr3Y9 zxD3H@2)H4}k$Lx?l>(G*#*w-A-t^hbE`b;MQ`#yi?fitW!(f2+_!2e`2j0dn`mOI@ z-*e)0tOgSZqtgkiVwMM`3kBBD4_2XFAJ4QJpC*o;xMVSf`QxugH?hha#ga=qV)p1* zKlRIzc{T!IF9!h=$4{$aT&mer0X|Vf++pgPcxnL6yx)hCZ9eCgQ6hihLE>hZo=`Vu zn4Pd7em0rCC{=hd9yD`IH3g(H;H_g;y2sDJx@eb!flaO`HBH6ceOsKQxNuBW)CQ-= z%D}kDn4W@hWR$9kL9X~VX(E&0$XY{b@sMav4~hwxf{if3oVo~iP@0+ucTli@uhp9z z7*SbI%0H6mJR0-PeMoa8A&4T-?NXhW=i)T>b=P`LGgl5`qAwoT)4~aSQm$fJ5Hq&U zkt+^PW2*ZrF+90kdRl+!@Tej-YBZ~IT-3K}uAM}}5~cLzo?t-rM6n3|GMQXDY&@s* znBN2tT8LPIh^nH6d*)Z*vz*P3xc@X^)6Q!LbG$wzZ($C09;=D$OGZT>37=8?+_g@) zph~jdbzC8F{R00;IoNpxN-HkBy+lf8lSm>oZd2BEBkq+e5yfH$iWScKQTUy z{<+HeJ^}RAF9oBGAhK~F-0bx9+qsG&3=jeKpVNc&@evBdD^xtw;2_87=m#qX0t4|< zg1fFbJ~9wn>jR&jh&EiqSAtpm-FB-3_O|^uS^af53F1KtBL_ z;1%9PI^k z!*no4!F=o`=@LX|mhWL6F&)bnS0DkHvsFSshg9TB#YPAQH#ca?8r#$BJS3ImDN4(} z8jbjr8Chd03Z4rP@j%+bonj8vpG9z8KrYO16-IdI-pAx3u-gFyH%nE0JO+T;&ELvm}jTS&R81eFyq%JZ~?|UR-Mmkp4s`rj=+>l)%48FSP zA(mU&fD}t8!=Z)eJHi`AlAy$nk)xQU&#+WaE!jm88Ehc4kAE*60W~EREoldmkh$yN zJx1^VHw`G=Jr01lkm~aQ?gn|_XH*be#eH2rdKO?Z_O4!ZaaU<3Fxn)Zx87}7{CuQiVywxNEVTF(xR=OX+63a4JC^` zK2rDxl71sejrgyX`OPMo3)6z|(8c|=1k2QdWIa5lhA{F7efCb`9tlh&D=>Tw~K6G#4< zneOFw*d0&*1r}^DDmc8g>&=KS6bSm{l2D*LipOPvhG}o9=5!59ObXS5iB1VIVC`?w zIy9$I;a6)7O)@~h2x4f z>SIV7ozC{hO`;6v=-Q1-Z@VsGxSyGg4#HcACJc19fGh!Mgf0xs<0g=RXgezz5}j6B z(7TqB(hg_USiv%v@)wT=4e&6;U*bwbzaQjSCBKu=aBxjH!oPhv07?4VHZXQn=ziil zyLSg#xe`RGOz!$(Y0}xCc`A79I6)q9Qc^FhIA76H6lmWv?o4Fv^GIV%^nlU;N?%1W zan@Pja1S%5PN-e#I7`O7u=6^1wz<7vWHX6BmcMj@vBvV^B8TXS#gqKUolHsj?q5siKz6n`b|N` zQg8>OK~_xO?Y(1}+vvO&J$%^2x!DOI)URIfjg?kS;vq??$Ar~-aDd6AQ1ZYwZ7SHX zjDh661~W8iqfr~MBw^yw@I=;;32#D*k0scv6u9+zzILar67iStw2q!BA9r{QOuFEo~81uY2NBbX57 z5)b8yl{7UYPU4yqWkPDio?b^5>rNtsXl6ujDv?iU1wT+F9_%N+X{3uZjW;$J5eMrg@M(;iTt5y$mzCTXcT6QPcBtcM_+uYE-q`1o-~?xaskE1LpTe4FSrA4;>2~EglLld(Sc_e$48Gbsm@k$jwDFz z;c~cKz(Vf^?xJneoMO?AC2VXd0&5c+jddcEIm1VPE7zWz^6;Ey@DBBF zLOXA>k8}l^t{s=uMt=K%!ThL#PPjI-h#A5ZE#i?htU)=<+YE^BHOaxliMfk-oLxX6JEag zoNxY&)Y=4$G?_4LbB~~zF*I{{o++DYj(F1yUAfRi^MMBTOlj-JE+o0-{H^hpn^@XM*w zY15xHMHDVSWnRh$>oo6};~r!_O?-1FaK9%iRdr`$cAZ{jrlCI!HbsZ~C7N#H*XJ)J zzJnb!=zuED9F009-MV$)>l!^ucbaar156dya8kPL*Klm_+1b2gJmt^DX;paXDXL?L zToch-8uATWV?C)L&3e!@AK!!@h?i4q;wsd5=v!Ap2B8(l%+o#nsAd%lw=8lfyQ@zu zRdfc!x0fZlE;S$0J+gl3_;7wscOGLsSWCZXRektLz7!VfPKxMG^4QL>=w>nN0wUo| zHS9Lhs})feULN@8=Of0%ubH&JbcggNM{wUA_Lv|02A?X_3}q7E`k(kM#yT+y_Rm{5 z^qjSx*@xYQx>syktH;DM2n-)pJYNsu%e#FXPWiXMbO(9&+P9w>G-+?5I2Mb}tpoP) zS-f#Q=Z3dzZHU#wpg*uvtiB$+0GWv^wq)j90P{QVh_?@t)1j6`vwvTo6Gnld>?rEO z5j5jZV8#Z7HVpq-r2;#{plV>I{2V?YHt?#};j*mM17*pq^Aa3#q1;sDrL82{J6@6!GqgUjX6{h@km zrYk+Ri-QSQd)v>y%7}i!=SFv$dsWR2fhjrMI9&VFMG|jh5<8~aiP0iP4)Dg znwpvF>F(2~&$+JC)8M68Mbwfc(g}ZMTf`YP?Nfxl#h>y&R<@}p}$ao*P$zE)a$aqDW`k}qz8$O!E#~Z z-hg!ot@B}0EvLvW$Dc(;?+WS3$$cTQ8n7@38dM>(@8Oug=Sj_Swi)4WC#idc!h=Sk zVCqCrdc-;~!VZyB9$!hrzQAxN+kSbyk*Zgo*$Wd7PzhOK{upUb%%QOA3ZWy%_7VDP zCGM#NFkx_Cf=MOcf8}l}n&N+qpb<_;L|$7Ujlo-Xv!u+pB$9#%$CQeGbc4RpJxGuo zx!u~cPwhp_XF!7$k)Vb|KS78Jzxt`tqwCHdcqD9tu{(J;?fL(J3`J0 zex!_kfN?^tLO~8IiG)Cno>X!=QMkir(~~+3F!a+tm!H7;hU6_(TA{S!d{=@e=Q2Tw zRv`g%3!|r%4phc2TObosGmY|zM=LauL6>rQhW~|Yc@6tIhjRk^DzrQQ)PN|j<`Y*} zn2%D^fC#FL-b${<{somFK@vxLs`X;v9H*{u48{;zH!Z&+}!4LsS#$d!SlmF0@TN<>mlwG66?IymB%wp3mcXJG6PPH=9bh@QJjbfIBjCk-W#aS{CyDw-_hcZ>+R15xXjZl9D^aOa$@ z!SCEYpjV}6vbb#M=v-wYT#W-{aO_6dN@K8AWOs;p2sYC>p|hA#SRGPDP?q2eyBkfV zcVSgR;C*0Ha$m$djyS{De?i1}V4V=*_q>G0_lR~qJ3&xeWtv1?de zk2PfV?nUt|Wb;6j(#LCpY>vkm3`8q6A&+p)0~YWzXs=rhFnZiXoKg=|Sui^jGFhj?$vNR53vaaZfBZXqqp zV4+niQSp4~PwHft!br3=!m2i)s78q9n^)PKUvM$)0S7gsK~`Mq^5Rl`*((Gaq=ZbiuH+IL=4F}kS)e{0%9w~nw5KjO{zn|-&uKe>kc(s++mTjNdPIPruF?Js(R7guxJ2o>tjrZA!U1A+6`+XknN zX%uaxZsKO&e2+uUpT{@=G$y~=dx&k4z4dvc=lvIbjK%a(*L~jMvSS=Cyl@>3P;0!ME$(gr{Z4?}Os=AJ3$lFO!Pf0e%0jT>x)jl>-lkqobl9*L%U< z^<-7a3u}3Y-AA+H=8iQ$iW zA@IKb=lyhD(Xf95X-M$xc|StX|KsrEaGl^cN;J^@@2Z{u41WEaz2HYw1sih#NRUJgG5eNScrGhvX@GAl?D^N@1Bc1jk*{Jf#_-##NN zWr#$+saAZ8yb+@$^N|bDD2Ef<7EZCDmXbH5h?jb;C3$_jszN`NeE(9j?w=$}F{er~cYWR;BEYAiFW;iDqrfy+3mK57gN~1HTS&oW=OD6)?_GX`T(NvI~tBbif{ zX=r9K1>YuduLCqkF*&%LI_)alFDJMTde6XQW2XmQg||3KbA#pn`VN`dMhRL`FCejRO6Fg zo8NgG#%r&~QNDRlntNt#GV)JeHtVAgnUL`BJE7t=1cHvZ;&-nOV>fDjAV6$PWJ`@8 zxJf|NThjbj6-!k~ElWLrAsUTpEJN=P*2d|B`Rq=3TJ6NiCGbp|6NJ;UKPkDUVX*fk zq^i@=0n0%Y{*+tD2~)RX#>sWw|>C)gP%1Rf}rlpwj`7<@4g3?ad5>6D9Yt z!Ifjz;$(02DWZsu!XR2$RR@rsm@9kC`vm~rC`ZSdC8fubMHN&uPDVRU@+SKwMQ3hvEt~x4Hw3894dYtQI zM**k~r@Z9fSw*BCA*`zExOwasE!)gc*IF&{(@+izX(2S%Rh*SDM|_n>-u=cNzq1Uw zDFBIk;gCKmq8;QfCoqk4ITwSchuLClPe>bIQ_n*7lE5=zD)|NiA|&u#Jv{+=q-}20 zOU;S_OT-MSG8Y~P7N{5`+fS^2|lnO9~MofhYcbW+)3xhsm#H*U9cGni)|$ z{<*Ik*FEX*gihI3wq5RWo8Sd?30RgdvHM8~9WeWLS>pVW@Y1|f?p3?UakN;Zw95hC z!}E_vH|~PQgK0w|ld1WN!36oJSBF{^Zxd(M54;)Cd2Un*>IvPSIVOx zt`E-*U2%{$^Zr{x-GSbbvM0xD>tR!qIp}YZh;jba;~`PrwOBa~fighF4puQCr|1Z~ z0P$$Cr(W7*UmdOhoHcU^nudav>X2MaHq-+}+fA*d@bos@6 zw3ETZUitMSY^ZVh(F{;n9gn3T9a=3DSe=1=W1QsW<)_wjavXL&Q>lf52)~y?aue0N zg^^Slb`ELzdN-KtB;9zX+B10B#bFg@Zh58mU7KHMPf=ekrG@iON#SS6lk(xwo|cx9 zIhDG1)Yg>3xYplc4caNQd4}4CJo=V}@-L$vMs^wO>nR#eF~0zt;3{Q-^yA2qg+`yl zQ>s&+qcTg<*fJg0FQVK|AD|kmN|vzBhO^{h0`HrF*1YeIk_;4W-EEkRd!qc6!RyWS z^ojz~b9kt%y4u%dD%_WouEAQVY)M#m{#eE3bFml1UT;j2W*%DgQQT^hEqfQZWFUqx6&__XGI&khHIG;bWl@u%)A{GtJbx2E z29&XCqy8fBHSR9?lq$VrSaiUmjj^ZFd4}1_iH=RgaVG=A_Uqa~K*C(P_%RF|K> zHX#-p2P}ZwH(=>jQ(VS^CBVx;yBp4^n#GnhMLdT5q$cMr34Q!pZip46FN8vqgn$tQ z%eFyua7vJCY_YWx*FgJS@j{RvV~+H8PI3l>y#HHdR4>no3?{$+@LB&jJ*Ge#C&v*i z^QV%7Kb2p&?HdW&I7T;J4U)xDd)VBiog4vLARaK^LNMipO}82`67uP1H2zWOLY3R) zDpjSU*lj7P4?W}PqG@{aPUTHAOdtMr+JM)1)Mv#h@uwwC_^VVO1qmMxen1Gvic;Fd#iRbvzw76}H79!LC*$5qv=9w%i4l{+E1VQHfUSQ&Oj(l$Yj z+Law0d=<_s@&oscr+6;gLFlQg3B_KLYn?CywS}*kyXwO!w?)24lb`ixZj=kOzhu0y ze|d1_w!3Txve+Lga`Cv4t4+%B(IQ@OU08s9ZrNVbU%CWhE7xCiv}(Zo?L@EBbgYze z?td|=@D{mD&AL8;T7^x&D{z-LA8_aoc}a%QJ=##*Hk)uf*-$*lE;z4)8+2F@#6Q4s z^b4HFIdY|kyxT#>+8~eKfNlQ(snwq-kq#~DOPhR+8ToM9#OKqtUQ-qUng8Oa#8d_t zP6$p@8!f{rp&?fl2V7gfTo2Ms$`4vmVU*)}h9gMuWZ1=aLswu3)ws~w62R2y$f>qX z*d>%sAz5Mna6&5!K7&N-${q`FK$Lecx@M{ZSHD+Q;xfIMk}1r#nshaoqu)!{W2ZCZ zbG@4`GS9q{082Te%Ri(pHLXF@Hh%-Sw>UkQ4xLjIlBVV3%G7Io-dw{X9uNz6a0}G* zi`()ujz9h+)}GZJ&CCmf^cNZj(}zGxOY4OOdoMjXnzP200P1@0vOgE5^o6B8 z#6~8qNcRVh)R_;u2S-Jk@rG`Ca<(xV26>Gx@S<4P4~S~yC*mDd8001=A<2O132?zQ z_MHa^qZXz~MH6n6mnK03;kB8wHWx4xthp)~d2XNC&5{Vn`!d|MxoVATLqAVrX-Y|9*zi>(pXa6rh1~9&8to{}s=4+5(BN}p<`MW$JvZuE&J<-Gxe*tcm zp$Cti{QJ{+t_Vb1LSO`Qi75giM7=AD=8z1vrj$1>eZ;R$0e3zUYa%XGzoE zY{KT@CKyzMSAlG7UspgNOfp=2?5d$k6vLEnwgE#@+Cv3rWP>(#)QdH8$yXhpdt^5= zsx7(?3I;qs0tdF52fdOjjrtkVh_8D-`j-!wT*L}V^xc)w!_tk=$Pb&5qF`dWLiM~M zelM74#|SHW^gcYowSx(yyt9&=sPCA1aGascd2%zuVKYVYj!=MCp(%$>t~BQ0NyjqU z7pC2Cd$g{Vt8GP^$&w^+!6jz9hBK04l+2L&U-`rbloRHuDU2fjx1T(j(%+W)6_$0Npzf%`AkT*@*+P2vTZS)q-|hZlSsRBG3x|}gvDQsHL2m9VY9JYAhEPjHAB`4q-Dmt{>RsvdOU?&0H6|8l zDG6A#ndJ~=u%2|#s&4GF0nZCs3@l#lz z7y7)VTN2Q44FI95`77Ki$yP%ucp#+vxb@oP8hs*q9J5waQ=0W!JXYUkFxClW$D8H3 zLkKarCys;aBn0TCC7;TML=~zqOBXW**){Q-51TLK+uGxtTHw!i?aO5GnEm6g8`QHQ z<5$Gv-AIOlX=iKZ5$-CM5PR_`ox7cd4?{)UW&z@9ZZk}-gWp_kRr~$|#}i?UX8rYp zvaj|JxMXigjqglHXLiz@)zpMu=`hE71+J{_GVVjK;|op5Y?_o)XuM?jp!To`_=V|G!AyJSDRyO+Z!|e6eptk-agI zGGeXh%8w*0s$z*G1dKF$#`z>3410f5NC*x|_kIZ$F73`8bnU6JT}B=womTD7EriGNKbdKU>s zN3_;gYLoZW$S_Hgbi*huEegMwih#6=zP(fDfs-*UIuaA}@JJ5g;U+(Ke(B@((tF*77y7FA^flgE@>WV!UzY!h z)$k@{bJ_`u)N?5cQ6PCd*^Gre%TAiYG9=r(N8N0V2 zw2b~U5P3V+38(@*W9Q z{^JhLSRjY60OR~$u`2~cG%6T(5;A!PDG!9oC!Xkk9@4-A0gsU}9}l9)*jIw!1!Hgj z6ca#${RcxP&oI@3V5MYZCSoG`FF=Ejk5SFr$(&Kq#75Q4o>75_nTefI!rH~vjfk6> z>(h;c2XJ$<{-0qRtvc%txRNOT+ut+W(~zjnU(JobCYDion2ZG-)_St;g5=}P8O&6O zs>ohm`Ij(VxU$P5VC(`fEesX%>UlO*`BOD(P11InP|9x?G;_86#81`8Ln}ii+UL>^ zPBfd65$p%m7ruS&JvU|2h?iOC0>9FJa1ra9TjlPhsUz&G9d?LV(;RcJDIaqf`6|9+ zc)-KiNJCTxW7M;2HAa8?!8}^DM@Q4Om#kzQMyqs}t*OVV172=hJ7OCY4IK{_zgsBt zbJR3eJj0w#qDZ8s1*+z-5S%CX**gxyQIEXg>m|KiP=e7~3FM?{s3@U=8o;j;T6|ha zi{0m~$PoA)>#vZv+FTiT&(AN__*p_DMw=)H1JXso%)eRHelk%>(dl{(3&FDN3xT%2 zVDv!KpdbWuR_s)KW{okV41^qodTxz*HBC zi&}1dkz6#2_#vSLNuQ3~Bs#L-hUJbzz}lli@NpNYl)tA0260o@QZy1&jX(p+^dQ4# zVoM&tJDvZbYh+b6u!qEEi$tlb4UMTBOaBfX1k0kq4KqY=TS<*i3^S2?J!?uOT2EW(0$5BF1(c!dTy?G@xP7Xg~as+C7d7#<& za9(crZ?^(w-4CX=ZF-*VSDcUJt=I&sO@FZw}=ZY|=z2#O{kGO-!GPz6YeCG%wbV zdr~oVc#&&J2yQfD9)G`FRdbhextZ9!UEx;vJ{f_Uu~Fz>4;W-d<|1~;=uax53&Mypbm{Wy#N4`Ji zJwlI)P>#H?>_lRU0M|<9!-ltfGc!le1R{;k*8wU#W@X{~bHB>T_8P z)ne|aXlliqMiA=>M$by2o0oOw6KZC7p)7T56zTWn6_n3A@%S+J|hc8q(%M;qziKfBaldhWmtT)NaOJE@2C`hj{ zbAlzBOku8W{`yPuVeIH8;n1dSMz5P=5f+H@-QkFF%4$cBdSc5G@!j{lV~fdG{YO|o%wE(el~u!E>(RuKU;HnZoF2j@R9p2 z&|l#H_GbC89%UrLI zWk1~glniD$_lY)_C$(8$%r|?yS2m_Gl~~yOvi+d;@0z zr)&3^VcI4Hl-^~Ptic}lwO_GNZW>4{TfW1xHyhmmpN|MPQ>s=K>`SPt#}DVm6ZFQx zcJ5IKbY?rZ6w3;SR?8jt#>0N1z^WLcQzmd7{WFyW=TiQMa$`}38}q?~U|9^Ttd2EI z4sS5Z-7Lw#D4)r_d?2lU$glaXc_(N{%c^+<(@Bj=pTR#;2`+XcSt8L*ei%qQa*s^C zY1Oo;ErYuVkARGeyDo|)-dbQ!AIz8lMt*_WVzh9;7#-{jJD>%FE{mVm zh7OZ1xgV?*ie-ySpJ-+B=10{VhTDPbJ9bic*B7}4uF|um3MWOQ`t+@mJh$=jL4#E> zAv@h*G<})A(+)ARw)-)k39su#nYU$hV!j2M92SS)3SAuUVv z7O2!D1cis5QLG zV@Izo4M$gB&PKRldxwP(AO`ghPJ6+m(;>g{ZMLx&IJOQ;wN2}lBCS96j2pgPe3gA} z{gF0$lRx4Z0~_s0x>j>NH{O|u7dGt{eP`_;;RQe4KE>ZIyAoLy%4Hm=_D9lg$grOaZZd$1pP zF2=H~3YWGLghg2(iuklRfCe|9$8M|N&)xfPK!MUdaI&lvcUnFAj=#jEsS)Q;BVn&@ z%AUt=VaRT&N4BZ;m=Wqa5M{r);=lP!>ZaP}RBPwugWExg=iK9Dpl9&*@^L(Ma%5M*~xzo^~B)Mgaw_*;9#TioSn$ zYz-j;(BboU`tVQAKbJ=}JVP!Ck0K5Q3-g9L2CZW@rEQip_dyTJAqDh%wBqeYu4A`6 ziL(U->V;QpIRyfx0lfjQ{*=Xr)V#5upqnjsiFA_y1)DXsMW6FiMN*@^Q17ahNe1GnrVJnKHAmnVOiHvY4|m zGqG};v9ob8F$?^^@6cydaddQp{m;-)VU%{Ta3o^+pQ@_0kNKyS!NkO<39ZXa#7e}X zpMl&Dj{g}h_J5ig>?_`K=-L=#H$3VI@`>U?%F4nUVB+Sg!bq56Vvy)a$Y{z=+fVw) z%NX`gkWcu?N=}9Yb&IE`X5}fTA0?KuvxF&fEUa15J`~Oi``3NP*@=n@0q$ER)mwGC z>dQ@Ze-oo?6$l)OW&Hj!{znQ_08P>FF2y#qya*G4bI z))~Kk488-IbJt=cSl1cXVGO|UK=s^p*a%VYz^rj5yc`3WgaItR0g3#;&j&6{+2J!) z^ELt#+h#4%#f@d_iSL~pe>N>^H@!{p^(}Z17gtGsYb&Ps3Eyhf@~+FdgdRk7{9~&k z_NLLWjh2p2=0p9H1C@eD>P`IHQLx~GQQ9E-;g}}{kJ4M-U!=DG-w1F(Ij%eMqGq40 zN@hq)}Hl-)0rSOtw_}Qb%Nk{%1 z19D1}#Exev8sC%UJ_7ZQ7a~^ZJS+*DTf^#jx&=^pZQ{lWQ=WEL2e;)ulJ$rSA;ZQFj}QdWMZA{azL>#MpF?-B)rZDR9WIe<)J8 z$ec|8O-mHaaizw8NLDC_$qlt3RD^z)BkpRnwd7$@_yF${gKt}NiuyYma3s)?w9!k{c*}@Rb0zt2eOpPZd0zuf&7(k zq{CHLW&-Hr6-^`7QJm7RR&F92o)?ckxDPsdk_$kF55L1eTn7y2;$ni&H;0jngY~NZ z!b?Se2-|Baf+5CJ>_Cuo{5}j{nB@x3SI=ZN({BQUV|H=n|Ft!?z!>KJuu^~NmiRlY zp((959Rh{*5a%<3V1xVDy9NQmwG)zU(F;+0$%}{}tl-bV$}d_q)IjSY`fmQoFOL`h z3S5677{vDi?cY#rjdpt>{$U2YG6iv%pk9uO`9)5z1ScrXdZ1A3fAx!2*NS^}CLe@!}Z*AJw1L=*5k);Pl}2>13q&Xri2 zGHUD#Y$WjHQ<@^2apDb&dSO*Gxzjwb_r!DFaGYfpNcbaOnUdP%%PKOs6FhM90STB5 z818@EdLlD!XbO6Rg2-NUA&e>EMv>skB!Q}#70A!#+A5|j@WC40$FCLbjsB(t975R* zLRV^D(%A@O`lR&lRo|2`6e9@r>Ym~~Lv8Qcy$x_`ofKkv7}+wPVf_i;tz5Q%cr337 z?LwP$0@EnW_ulkDX52^bnFu%oVwp46{+nERjU;`JK4&pXM3HxTn`^4{eP+Ml*=z2z zUQ@6>Gey{Ivi#9zJAEmCgjrL10}=hO$^jXa-r2J}wW~BA!q{?484=?`nvmTSUi4~p{XWgJrV!g=_pk!;5^G{x;3c>*e5OYTDA@=roG zl?znd9#6uOu)({As)zo%@-@O`vAWzbMk)qq@8vt z;Twqh-*(2ogf>IA?}79ZaGgjUP>$CPH)f}{p>vLdq$zF${oDjfv!j@z|bXQH`qPhLBg@4e`;5=7CKpe~%2)3QDWgj5AQR zb>?Oe*Voq$qje&$m%xwUbtbJJdguWAPX)y5Isvo}L-<}_K2^Xk%SoDzn57QHr;>X2 z6P@V#tP`KedfAh%h|kNH@OtYLo>7=z?lAh_J0iOnifoa)KAaF{_aSRXXuV(<_vwFm zzv38-;a1jw@t|xOlWvXpdJz&DqY8|*S3a@$^Zxdv@~3PaR)1OP89K`;=`%fh&FceB zfgF9La1%ZWBdoJ^4X(%GkIF06u6(?ZA!jUwAIMxdW5!TNI}o|C+Iw%LEBp~}cL@$a z+s4On@Onbg5!LPR-zzT9TiH@+i>SMP*YbRZ`W-@feY*NY64hBG&8hZs26RokqAJU1 zTRBA4jMimJ`8>w@;O(3)5xuYydHuNnB!S~hpP-I(KU3|0&^WtlQ5ggF5Cq@2Ia#@3 L$;rhPC1C$QKSLtR delta 125782 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 sft2Y^>%h_0H{wtO$^@xTQnD5 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 b13*Z6%b6D*h!hxn`~tk_w6xNyGU)#Un-Om9 diff --git a/docs/manual/fr/user-manual.tex b/docs/manual/fr/user-manual.tex index f4586c39..25dde525 100644 --- a/docs/manual/fr/user-manual.tex +++ b/docs/manual/fr/user-manual.tex @@ -961,7 +961,9 @@ \subsection{Enregistrer et annuler un règlement}\label{sec:reglement-client} \begin{itemize} \item \textbf{la facture a été créditée} par un avoir~: le règlement est alors un paiement - \emph{à lettrer} à une autre facture --- il ne s'annule pas~; + \emph{à lettrer} à une autre facture --- il ne s'annule pas. Ce cas ne se présente plus + que sur une facture créditée \emph{avant} la version 0.12.1, ou restaurée d'une sauvegarde + antérieure~: un avoir est désormais refusé sur une facture réglée (\S\ref{sec:avoirs})~; \item \textbf{l'exercice du règlement est clôturé}~: un administrateur doit d'abord \textbf{rouvrir} l'exercice~; \item \textbf{le règlement est rapproché} d'une transaction bancaire~: il faut d'abord @@ -1016,6 +1018,16 @@ \subsection{Avoirs et notes de crédit} L'avoir est consultable dans \emph{Avoirs} (menu \emph{Quotidien}), avec un \textbf{PDF « Avoir »} téléchargeable (mention « Avoir », référence à la facture d'origine, sans section QR de paiement). Le \emph{Décompte TVA} de la période tient compte de l'annulation (la TVA de la facture annulée est exclue). +\begin{keshwarning} +\textbf{Avoir refusé : « la facture porte un règlement ».} Un avoir annule \emph{toute} la facture. Si +le client en a déjà réglé une partie, l'avoir lui laisserait un crédit du montant encaissé, que +Kesh ne sait encore ni afficher, ni rembourser, ni imputer sur une autre facture. L'avoir est donc +refusé dès qu'un règlement existe --- même partiel ---, et Kesh le dit dans le dialogue. +\textbf{Annulez d'abord le règlement} depuis la fiche de la facture (\S\ref{sec:reglement-client}), +puis émettez l'avoir. Si le client ne paiera simplement pas le solde, ce n'est pas un avoir qu'il +faut, mais passer le reste en perte --- fonction prévue. +\end{keshwarning} + \begin{keshwarning} \textbf{Avoir refusé : « compte de produit archivé ».} Si une ligne de la facture impute un compte de produit qui a été \textbf{archivé depuis la validation}, Kesh refuse d'émettre l'avoir et nomme la ligne et le compte concernés. @@ -1037,7 +1049,7 @@ \subsection{Avoirs et notes de crédit} \end{keshwarning} \begin{keshnote} -En version actuelle, l'avoir est \textbf{total} (il annule la facture entière). L'avoir \emph{partiel} et le remboursement d'une facture \emph{déjà encaissée} (un avoir est refusé sur une facture marquée payée) sont prévus pour une version ultérieure. Les paiements sortants par fichier \texttt{pain.001.001.09} sont, eux, \textbf{disponibles} (cf. \S\ref{sec:paiements-fournisseurs}). +En version actuelle, l'avoir est \textbf{total} (il annule la facture entière). L'avoir \emph{partiel} et le remboursement d'une facture \emph{déjà encaissée} (un avoir est refusé sur une facture \textbf{réglée, même en partie}) sont prévus pour une version ultérieure. Les paiements sortants par fichier \texttt{pain.001.001.09} sont, eux, \textbf{disponibles} (cf. \S\ref{sec:paiements-fournisseurs}). \end{keshnote} \subsection{Supprimer ou dévalider une facture} @@ -1453,7 +1465,8 @@ \subsection{Annuler un rapprochement}\label{sec:annuler-rapprochement} \begin{itemize} \item \textbf{la facture a été créditée} par un avoir~: son règlement est un paiement - \emph{à lettrer}, il ne s'annule pas~; + \emph{à lettrer}, il ne s'annule pas --- cas hérité d'une version antérieure à la 0.12.1 + ou d'une sauvegarde restaurée~; \item \textbf{l'exercice de l'écriture du rapprochement est clôturé}~: un administrateur doit d'abord le rouvrir. ⚠️ C'est l'exercice du \textbf{paiement} qui compte, jamais celui de la facture~: une facture de décembre dont l'exercice est clôturé, payée en @@ -2070,7 +2083,7 @@ \subsection{Nouveautés et limites (v0.3.0)} \begin{itemize} \item Mono-devise CHF (multi-devises prévu pour une version ultérieure). \item Décompte TVA au format officiel AFC / e-décompte ESTV (les montants du décompte Kesh sont à reporter manuellement sur le formulaire AFC). - \item Avoir \emph{partiel} et avoir sur facture \emph{déjà encaissée} (l'avoir total sur facture non payée est, lui, disponible — cf. \S\ref{sec:avoirs}). + \item Avoir \emph{partiel} et avoir sur facture \emph{déjà encaissée}, même en partie (l'avoir total sur une facture sans aucun règlement est, lui, disponible — cf. \S\ref{sec:avoirs}). \item Sélecteur de langue dans l'interface (la langue est fixée à l'onboarding ; le changement en cours de session est prévu pour une version ultérieure). \item Pas de signature électronique qualifiée (QES) sur les exports (verdict (b) de la recherche réglementaire, prévu Epic 14). \item Format PDF standard, non PDF/A (migration prévue selon ISO 19005). diff --git a/frontend/src/routes/(app)/invoices/[id]/+page.svelte b/frontend/src/routes/(app)/invoices/[id]/+page.svelte index fb992f87..c4c2ce6a 100644 --- a/frontend/src/routes/(app)/invoices/[id]/+page.svelte +++ b/frontend/src/routes/(app)/invoices/[id]/+page.svelte @@ -833,15 +833,26 @@ Voir l'écriture comptable {/if} - {#if canManage && !invoice.paidAt} - {/if} {#if canManage && !invoice.paidAt} + à un rappel) : les boutons Suspendre/Reprendre sont masqués (review + P1 BH1). ⚠️ « Créer un avoir » ne l'est plus depuis la Story 25-4-a : + il s'affiche, et le serveur refuse avec le motif nommé. --> {#if invoice.dunningPausedAt === null} - diff --git a/frontend/src/routes/(app)/invoices/[id]/invoice-settlements-page.test.ts b/frontend/src/routes/(app)/invoices/[id]/invoice-settlements-page.test.ts index 52f6cf4e..d707f4d2 100644 --- a/frontend/src/routes/(app)/invoices/[id]/invoice-settlements-page.test.ts +++ b/frontend/src/routes/(app)/invoices/[id]/invoice-settlements-page.test.ts @@ -1,5 +1,6 @@ /** - * La fiche facture, côté annulation d'un règlement — Story 25-3-a-1 (#414). + * La fiche facture, côté annulation d'un règlement — Story 25-3-a-1 (#414) — + * et côté avoir sur une facture réglée — Story 25-4-a (#456). * * Ce que le composant de liste ne peut pas prouver seul : que la fiche * **confirme** avant d'envoyer, **relit** facture et règlements après succès, et @@ -70,8 +71,9 @@ vi.mock("$lib/features/reminders/reminders.api", () => ({ vi.mock("$lib/features/projects/projects.api", () => ({ listProjects: vi.fn(async () => []), })); +const createCreditNoteMock = vi.fn(); vi.mock("$lib/features/credit-notes/credit-notes.api", () => ({ - createCreditNote: vi.fn(), + createCreditNote: (req: unknown) => createCreditNoteMock(req), })); import Page from "./+page.svelte"; @@ -176,3 +178,35 @@ describe("fiche facture — annuler un règlement", () => { expect(await findByText("F-2026-005", { exact: false })).toBeTruthy(); }); }); + +describe("fiche facture — créer un avoir sur une facture réglée (Story 25-4-a)", () => { + it("le bouton s'affiche sur une facture ENTIÈREMENT payée (mutation m6 : `!invoice.paidAt` remis)", async () => { + const { findByTestId } = render(Page); + expect(await findByTestId("invoice-credit-note-button")).toBeTruthy(); + }); + + it("le bouton s'affiche sur une facture réglée EN PARTIE, sans `paidAt`", async () => { + getInvoiceMock.mockResolvedValue( + invoice({ paidAt: null, amountSettled: "40.00", amountDue: "60.00" }), + ); + const { findByTestId } = render(Page); + expect(await findByTestId("invoice-credit-note-button")).toBeTruthy(); + }); + + it("le refus du serveur s'affiche DANS le dialogue, avec son motif (le serveur tranche, l'écran le dit)", async () => { + createCreditNoteMock.mockRejectedValue({ + code: "CREDIT_NOTE_INVOICE_SETTLED", + message: + "Cette facture porte un règlement, même partiel : annulez-le d'abord pour pouvoir émettre un avoir. (F-2026-005)", + status: 409, + }); + const { findByTestId, getByTestId } = render(Page); + + await fireEvent.click(await findByTestId("invoice-credit-note-button")); + await fireEvent.click(getByTestId("invoice-credit-note-confirm")); + + await waitFor(() => expect(createCreditNoteMock).toHaveBeenCalledTimes(1)); + const erreur = await findByTestId("invoice-credit-note-error"); + expect(erreur.textContent).toContain("annulez-le d'abord"); + }); +}); From 21ee2648d2bc1ed8b0003cf0690fd83a503e19d2 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:02:32 +0200 Subject: [PATCH 17/34] docs(25-4-a): prompt de la revue de code P1 (refs #455, refs #456) Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-review-prompt-p1.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 _bmad-output/implementation-artifacts/25-4-a-review-prompt-p1.md diff --git a/_bmad-output/implementation-artifacts/25-4-a-review-prompt-p1.md b/_bmad-output/implementation-artifacts/25-4-a-review-prompt-p1.md new file mode 100644 index 00000000..f19f3516 --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-a-review-prompt-p1.md @@ -0,0 +1,51 @@ +# Prompt — revue de code P1, Story 25-4-a (le résiduel juste) + +*Versionné le 2026-09-27. **Une lentille** (Sonnet), contexte frais.* + +Dépôt `/home/gcorbaz/devel/kesh`, branche `story/25-4-propager-le-residuel`. Diff à relire : +`git diff ab9b9448 9bf2bb2b` (commit de dev, après la validation close). Fiche : +`_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md` (AC, Dev Notes, Dev Agent Record). +Issues : `gh issue view 455`, `gh issue view 456`. Règles : `CLAUDE.md`. **Arbitrages de Guy** (fiche +§ Arbitrage, fiche mère `25-4-propager-le-residuel.md`) : ne pas les contester. + +## Axes — tous obligatoires + +1. **SQL** : les quatre constantes produites par `line_ttc_sql!` (`invoices.rs`, + `invoice_settlements.rs`) — développe-les à la main et vérifie le texte exact (espaces, alias, + `\` de continuation dans un `concat!`), l'égalité **octet pour octet** des deux constantes TTC + facture avec leur forme d'avant (`git show ab9b9448:crates/kesh-db/src/repositories/invoices.rs`), + et l'agrégation de la forme jointe de l'avoir (GROUP BY, jointure, filtre `issued`). +2. **La garde** (`credit_notes.rs`) : ordre verrou → lecture verrouillante, scoping `company_id`, + chemins d'erreur, rien écrit en cas de refus. Les tests prouvent-ils ce qu'ils annoncent, ou + peuvent-ils passer à vide (surtout l'entrelacement : que se passe-t-il si + `attendre_une_requete_en_cours` voit une AUTRE requête que celle de l'avoir) ? +3. **Tous les lecteurs** des grandeurs changées : `grep -rn "INVOICE_CREDITED\|amount_due\|INVOICE_TTC" crates/` + — un appelant change-t-il de valeur sans test ? Et tout appelant de `create_credit_note`, ou qui + matchait `IllegalStateTransition` pour le cas « payée » (API, frontend, tests) ? +4. **La fixture `monter`** : le gabarit tient-il pour les quinze cas ? Un motif lit-il la facture + auxiliaire ? +5. **Le manuel dit-il vrai ?** `docs/manual/fr/user-manual.tex` — les passages modifiés (§ Avoirs, les + deux limites, les deux « cas hérité ») contre le code ; **le PDF aplati** + (`pdftotext docs/manual/fr/user-manual.pdf - | tr '\n' ' ' | tr -s ' '`, vers + `/tmp/claude-1000/-home-gcorbaz-devel-kesh/ca5ce2e2-67a3-4eeb-817f-c2de35620a1c/scratchpad/`), + `??` compris. Le renvoi `\S\ref{sec:reglement-client}` pointe-t-il où il faut ? Le CHANGELOG + décrit-il juste (l'API, le bouton) ? +6. **i18n** : la clé ×4 — sens, registre du glossaire (`docs/i18n-glossaire.md` § Registre), terme + « avoir » du glossaire. +7. **Frontend** : le bouton, les commentaires, les `data-testid` ; les Vitest tuent-ils vraiment m6 ? +8. **Dev Agent Record** : chaque décompte (tests, mutations, gates) **recompté** depuis la source. + +## Ce que tu rends + +- **Findings** : sévérité (CRITICAL / HIGH / MEDIUM / LOW), fichier:ligne, **preuve** (commande et + résultat, code lu), correction proposée. +- ⛔ **La liste des axes réellement exercés ET de ceux qui ne l'ont pas été.** + +## Interdits + +⛔ N'écris aucun fichier du dépôt ; aucune commande qui écrit dans le dépôt ou dans une base — +`scripts/prepare-release.sh`, `scripts/regen-test-schema.sh`, `scripts/install-hooks.sh`, +`scripts/test-fast.sh`, `scripts/mem-guard.sh`, `make`, `latexmk`, tout `git commit`/`push`/`add`/ +`stash`/`reset`/`rebase`/`checkout`/`switch`/`worktree`, `sqlx migrate`, `cargo test`/`nextest`, +`npm run`, `npx playwright`. Autorisés : lecture, `grep`, `git log`/`show`/`diff`, `gh issue view`, +`pdftotext` vers le scratchpad, `cargo check`, `cargo expand` s'il est installé. From 41e14e4c2a90d1e63d004904d6c73f5a5cba04eb Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:11:17 +0200 Subject: [PATCH 18/34] =?UTF-8?q?docs(25-4-a):=20revue=20P1=20=C3=A0=20z?= =?UTF-8?q?=C3=A9ro=20au-dessus=20de=20LOW=20=E2=80=94=20story=20done=20(r?= =?UTF-8?q?efs=20#455,=20refs=20#456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-residuel-juste.md | 15 ++++++++++++--- .../implementation-artifacts/sprint-status.yaml | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md index 82b5e340..0f17fadb 100644 --- a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md +++ b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md @@ -1,6 +1,6 @@ # Story 25.4-a : Le résiduel juste — l'avoir compté TTC, et refusé sur une facture réglée -Status: review +Status: done **Issues : [#455], [#456]**, qu'elle **ferme** : `closes #455, closes #456` dans le **titre ET le corps** de la PR (squash ; un `refs` partout laisserait les issues ouvertes sans signal). @@ -399,8 +399,8 @@ Claude Opus 5.5. précédence restent verts. Doc-comments de `InvoiceCredited` et du rang 1 : état hérité. - **Frontend** (AC 14) : condition `!invoice.paidAt` retirée du bouton, commentaires Suspendre/Reprendre corrigé ; `data-testid` sur le bouton, la confirmation et l'erreur du dialogue. -- **Textes** (AC 17) : manuel (refus nommé en § Avoirs, deux limites reformulées « réglée, même en - partie », deux motifs d'annulation de règlement précisés comme **cas hérité**), PDF régénéré et +- **Textes** (AC 17) : manuel (refus nommé en § Avoirs, deux limites corrigées pour couvrir le règlement + partiel — l'une « réglée, même en partie », l'autre « déjà encaissée, même en partie », deux motifs d'annulation de règlement précisés comme **cas hérité**), PDF régénéré et contrôlé aplati (0 `??`, six phrases de contrôle présentes, « marquée payée » absente) ; CHANGELOG *Fixed* ×2 (dont **l'API** pour #455) et *Changed* (le bouton). Grep large du symptôme « avoir sur facture payée » : aucun autre site. @@ -456,6 +456,15 @@ Claude Opus 5.5. ## Change Log +- **2026-09-27** — **revue de code P1** (Sonnet, diff `ab9b9448..9bf2bb2b`, prompt + `25-4-a-review-prompt-p1.md`) — **0 au-dessus de LOW, 1 LOW** : le compte rendu attribuait la + formule « réglée, même en partie » aux deux limites du manuel, la seconde dit « déjà encaissée, + même en partie » — compte rendu corrigé, le manuel était juste. Les huit axes exercés avec preuve : + les constantes TTC facture **octet pour octet identiques** à leur forme d'avant, l'entrelacement ne + peut pas passer à vide (le motif ne correspond qu'à la requête verrouillante de l'avoir), tous les + lecteurs d'`amount_due` inchangés et resserrés, PDF aplati, i18n, Vitest, décomptes recomptés. + Non exercé : la ré-exécution des gates et des mutations (interdite à la lentille). **Revue close en + une passe ; story `done`.** - **2026-09-27** — **dev** (Opus 5.5) : formule du TTC de ligne factorisée (`line_ttc_sql!`), avoir compté TTC, garde verrouillante de l'avoir sur facture réglée, variante `CREDIT_NOTE_INVOICE_SETTLED` ×4 locales, fixture `monter` par le gabarit, bouton aligné sur « Dévalider », manuel et CHANGELOG. diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 7502347b..ea88fb5a 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -354,7 +354,7 @@ development_status: 25-3-c-annuler-facture-fournisseur: done # 2026-09-26 **REVUE CLOSE EN 1 PASSE** (0 > LOW, Sonnet x3) ; non poussee ; PR `closes #454` titre ET corps, sur accord. **IMPLEMENTEE** — gates 2472/2472, 783/783, E2E 220/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 **SPEC VALIDEE en 2 passes** (3 MED -> 0 > LOW). **SPECIFIEE** — `25-3-c-annuler-facture-fournisseur.md`. Geste par le socle (autorite `SupplierPurchase`), queue commune sur l'ecriture d'ACHAT, lot en dernier rang ; payee : reglement DETACHE (colonnes remises a NULL, ecriture libre, lien garde par l'audit) — **Q1 tranchee par Guy le 2026-09-26**. Aucune migration. Prochaine etape : validate. # 2026-09-24 NEE d'un arbitrage de Guy — une facture fournisseur s'annule DANS TOUS LES CAS, sauf si l'exercice de son ecriture d'achat est CLOS (l'exercice, pas le verrou de periode) ; payee, son reglement RESTE au grand livre et REDEVIENT A LETTRER (Epic 15 : un paiement doit correspondre a une facture, au besoin creee pour lui — « comme bexio »). Reecrit `cancel`, donc absorbe [#454]. Derriere la 25-3-a. 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* - 25-4-a-residuel-juste: review # 2026-09-27 IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. + 25-4-a-residuel-juste: done # 2026-09-27 REVUE CLOSE EN 1 PASSE (0 > LOW, 1 LOW corrige) ; rien de pousse — PR closes #455 #456 sur accord. # IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. 25-4-d-solder-le-reste: backlog # [#384] ramenee de la 25-6 le 2026-09-27 (Guy) — perte sur debiteur, escompte, frais : clore une facture partiellement reglee ; la part TVA reduit la TVA due. From 8e8508b12639368fce47e43a99a0b6f0dabe51ea Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:15:43 +0200 Subject: [PATCH 19/34] =?UTF-8?q?docs(25-4-b):=20d=C3=A9coup=C3=A9e=20en?= =?UTF-8?q?=20b1/b2=20;=2025-4-b1=20sp=C3=A9cifi=C3=A9e=20=E2=80=94=20le?= =?UTF-8?q?=20reste=20d=C3=BB=20=C3=A0=20la=20balance=20=C3=A2g=C3=A9e=20e?= =?UTF-8?q?t=20=C3=A0=20l'=C3=A9ch=C3=A9ancier=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-residuel-aux-agregats.md | 194 ++++++++++++++++++ .../25-4-propager-le-residuel.md | 5 +- .../sprint-status.yaml | 7 +- 3 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 _bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md new file mode 100644 index 00000000..8548faeb --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -0,0 +1,194 @@ +# Story 25.4-b1 : Le résiduel aux agrégats — balance âgée et échéancier + +Status: ready-for-dev + +**Issue : [#416]** — cette story en livre la partie **agrégats** ; la partie **rappels** est la +25-4-b2. ⛔ **La PR de b1 porte `refs #416`, pas `closes`** : l'issue ne se ferme qu'avec b2. + +**Mère : `25-4-propager-le-residuel.md`** (`split`). **Sœur livrée : 25-4-a** (PR #472, #455 et #456) — +elle a rendu la formule juste (avoir compté TTC) et posé le test de parité des formes jointes ; +cette story en est le **premier appelant**. ⚠️ Branche **empilée** sur celle de 25-4-a : à rebaser +sur `main` après le merge de #472. + +## Story + +En tant que comptable, +je veux que la balance âgée et l'échéancier montrent ce que chaque client **doit encore**, et non +ce que la facture valait à l'émission, +afin de relancer les bons clients pour les bons montants, et que la balance âgée concorde avec le +compte débiteurs du grand livre. + +## Le défaut, vérifié dans le code + +Depuis la 24-2, une facture se règle en plusieurs fois ; le reste dû se **calcule** (24-2, D3). Les +écrans **unitaires** le savent (fiche facture : badge « partiellement payée », « Reste dû »). Les +écrans **agrégés**, non — ils totalisent le **TTC** : + +| Site | Ce qu'il somme | +|---|---| +| Balance âgée — `crates/kesh-report/src/aged_receivables.rs:111-131` (`generate`) | `COALESCE(lt.ttc, 0)` dans les cinq tranches et le total | +| Totaux de l'échéancier — `crates/kesh-db/src/repositories/invoices.rs:882-900` (`due_dates_summary`) | `unpaid_total`, `overdue_total` en `lt.ttc` ; le commentaire `:890-892` les dit « montants dus » — faux depuis la 24-2 | +| Lignes de l'échéancier — `list_by_company_paginated` (`:815-821`) | `total_ttc` seul ; **aucun** reste dû par ligne | +| Export CSV de l'échéancier — `list_for_export` (`:2456-2461`), `routes/invoices.rs:1463-1465` | colonne « Total » = TTC, commentée « montant dû » ; statut sans « partiel » (`:1443-1449`) | +| Page échéancier — `frontend/src/routes/(app)/invoices/due-dates/+page.svelte` | colonne Total = `totalTtc` (`:453`), `statusOf` sans `partial` (`:306-314`), dialogue `amountDue={null}` (`:493-507`) | + +Une facture de 1 000.— réglée à 900.— pèse **1 000.—** partout, au lieu de 100.—. ⚠️ La balance âgée +est un **instrument de décision** (qui relancer, pour combien) et doit **concorder avec le compte +débiteurs** du grand livre, juste depuis la 24-2 : les deux divergent aujourd'hui exactement du +montant des règlements partiels, et **aucun test ne compare** les deux (vérifié : aucun test de +`kesh-report` ne lit un solde de compte). + +⚠️ **Les formes jointes du résiduel n'ont aucun appelant** (`INVOICE_SETTLED_DERIVED_JOIN_SQL`, +`INVOICE_CREDITED_DERIVED_JOIN_SQL`) ; 25-4-a en a prouvé la parité avec les formes scalaires. La +24-2 (D3) a prescrit la **jointure dérivée** pour les listes et agrégats : la forme corrélée y +serait réévaluée par ligne — un N+1 déguisé. + +## Acceptance Criteria + +### Volet 1 — la grandeur, une seule fois + +**AC 1** — Une constante (ou une paire) canonique porte le **reste dû sous forme jointe** : les trois +tables dérivées (`lt` TTC, `cnt` avoir, `st` réglé) et l'expression +`COALESCE(lt.ttc, 0) − COALESCE(cnt.credited, 0) − COALESCE(st.settled, 0)`. Elle vit à côté +d'`amount_due` (`invoice_settlements.rs`), avec un doc-comment qui la dit **miroir** de la forme +scalaire. ⛔ Aucun site de cette story ne réécrit l'expression à la main. + +**AC 2** — Le **test de parité** de 25-4-a (`tests/invoice_amount_due_parity.rs`) est étendu : pour +chaque facture du jeu, l'expression jointe de l'AC 1 = `amount_due` scalaire. + +### Volet 2 — la balance âgée + +**AC 3** — Les cinq tranches et le total de `aged_receivables::generate` somment le **reste dû** +(AC 1), non le TTC. Les tranches restent assises sur `i.due_date` : ⛔ **un règlement partiel ne +rajeunit pas la créance restante**. + +**AC 4** — Le périmètre des factures ne change pas : `status = 'validated' AND paid_at IS NULL` +(une facture soldée a `paid_at`, 24-2 D4). Le `HAVING total <> 0` reste, et porte désormais sur le +reste dû. ⚠️ Un reste dû **négatif** (trop-perçu hérité) n'est **pas écrêté** : il apparaît, en +négatif — le doc-comment d'`amount_due` l'exige. + +**AC 5** — ⛔ **Invariant de concordance** (test) : sur une société dont le compte débiteurs n'est mû +que par des factures, des règlements et des avoirs — factures sans règlement, partiellement réglée, +soldée, créditée —, le **total de la balance âgée = solde du compte 1100 au grand livre** +(`general_ledger` ou somme `debit − credit`). C'est l'assertion que #416 réclame et qui n'existe +pour aucun écran. + +**AC 6** — Doc-comment de `generate` (`:95-99` : « Montants = TTC dérivé ») et libellés : la balance +âgée montre le **reste dû** ; l'écran (`AgedReceivablesView.svelte`) et son CSV gardent leur +structure — seules les valeurs changent. Si un libellé visible dit « TTC » ou « total facturé », +il devient « reste dû » (4 locales). + +### Volet 3 — l'échéancier + +**AC 7** — `due_dates_summary` : `unpaid_total` et `overdue_total` somment le **reste dû** ; le +commentaire `:890-892` dit vrai. + +**AC 8** — `InvoiceListItem` porte `amount_settled` et `amount_due` (`Decimal`), projetés par **les +deux SELECT** qui le désérialisent — `list_by_company_paginated` **et** `list_for_export` (⚠️ le +doc-comment `:263-269` le rappelle : un oubli échoue au **runtime**). Calculés par la **forme +jointe** (AC 1) ; `total_ttc` reste. ⛔ Pas de N+1 : le nombre de requêtes d'une page ne dépend pas +du nombre de lignes (invariant 7 de la 24-2). + +**AC 9** — `InvoiceListItemResponse` (`routes/invoices.rs:333-364`) expose `amountSettled` et +`amountDue` (camelCase, chaîne décimale). ⚠️ Même discipline que la fiche +(`routes/invoices.rs:242-248`) : **toujours calculés** dans une liste, jamais `None` par défaut. Le +type frontend (`invoices.types.ts`) suit. + +**AC 10** — Page échéancier : +- une colonne **« Reste dû »** (`amountDue`) à côté du Total TTC, qui reste ; +- le statut de ligne rend **`partial`** quand `paidAt` est nul et `amountSettled > 0` — même règle + que la fiche (`invoices/[id]/+page.svelte:424-438`), idéalement **la même fonction** partagée ; +- le résumé montre les totaux de l'AC 7 (les libellés disent « reste dû » s'ils disaient autre + chose) ; +- le dialogue de règlement reçoit **`amountDue={inv.amountDue}`** : pré-rempli, et sa garde client + de trop-perçu (`SettleInvoiceDialog.svelte:108-112`) redevient active depuis l'échéancier. Le + commentaire `:493-499` qui explique pourquoi il ne l'était pas est retiré. + +**AC 11** — Export CSV de l'échéancier : une colonne **« Reste dû »** après « Total » (clé d'en-tête +×4, `CSV_HEADER_KEYS` / `CSV_HEADER_FALLBACKS`) ; le statut rend « partiellement payée » +(`payment-status-partial`, clé existante) ; le commentaire `:1463-1464` « TTC (montant dû) » est +corrigé. + +### Volet 4 — tests, textes + +**AC 12** — Tests, au minimum : + +| Test | Prouve | +|---|---| +| parité étendue (AC 2) | forme jointe du reste dû = `amount_due` | +| `aged_uses_amount_due_not_ttc` | facture 8,1 % de 1 081.— réglée 900.— : pèse 181.— dans sa tranche | +| `aged_partial_payment_keeps_original_bucket` | la tranche suit `due_date`, pas la date du règlement | +| `aged_total_matches_receivable_ledger` | AC 5 | +| `due_dates_summary_totals_are_amount_due` | AC 7 | +| `list_items_carry_amount_due` (repo **et** e2e HTTP) | AC 8-9, les deux SELECT ; la valeur traverse la frontière | +| export CSV : colonne reste dû, statut partiel | AC 11 | +| Vitest échéancier | colonne, statut `partial`, dialogue pré-rempli | +| **E2E** `invoices_echeancier.spec.ts` | le cas de la 24-3 (`:147-158`) cesse de **saisir** le montant : il vérifie qu'il est **pré-rempli** au reste dû, sur une facture partiellement réglée | + +⛔ **Chaque facture de test porte une TVA non nulle** (leçon de 25-4-a). ⛔ Mutations à exécuter et +consigner : TTC remis dans une tranche de la balance âgée ; `amount_due` retiré de `list_for_export` +seul ; `partial` retiré de `statusOf` ; `amountDue={null}` remis. + +**AC 13** — Textes (`docs/manual/fr/user-manual.tex`, PDF **contrôlé aplati**) : § Balance âgée +(`\label{sec:balance-agee}`, « encours débiteur (le total dû, TVA comprise) ») dit **reste dû après +règlements partiels et avoirs** ; § Échéancier (`:927`) décrit la colonne et le pré-remplissage. +CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la balance âgée. + +## Tasks / Subtasks + +- [ ] **T1 — la grandeur jointe** (AC 1-2). +- [ ] **T2 — balance âgée** (AC 3-6), dont l'invariant de concordance. +- [ ] **T3 — échéancier, backend** (AC 7-9, AC 11). +- [ ] **T4 — échéancier, frontend** (AC 10), i18n. +- [ ] **T5 — tests et mutations** (AC 12). +- [ ] **T6 — textes** (AC 13). +- [ ] **T7 — gates** : backend complet (base remise à zéro), frontend complet, **E2E complet**. + +## Dev Notes + +### Ce qu'il ne faut pas faire + +- ⛔ **Ne pas stocker le reste dû** (24-2, D3). +- ⛔ **Ne pas utiliser `amount_due` scalaire dans une boucle** sur les lignes d'une liste : N+1. +- ⛔ **Ne pas retirer `total_ttc`** de la liste : l'échéancier affiche les deux, et d'autres écrans le + lisent. +- ⚠️ **Le tri** « TotalAmount » de l'échéancier trie sur `i.total_amount` (HT) alors que la colonne + affiche le TTC — défaut **préexistant**, hors périmètre ; ne pas l'aggraver, le signaler. + +### Où regarder + +| Fichier | Rôle | +|---|---| +| `crates/kesh-db/src/repositories/invoice_settlements.rs` | constantes jointes, `amount_due` | +| `crates/kesh-report/src/aged_receivables.rs:95-175` | requête, doc | +| `crates/kesh-report/tests/aged_receivables.rs` | tests existants (leur facture « payée » est montée par `UPDATE paid_at`) | +| `crates/kesh-db/src/repositories/invoices.rs:246-275, 800-830, 882-930, 2436-2470` | `InvoiceListItem`, liste, résumé, export | +| `crates/kesh-api/src/routes/invoices.rs:333-364, 1364-1470` | DTO de liste, export CSV | +| `frontend/src/routes/(app)/invoices/due-dates/+page.svelte` | page | +| `frontend/src/routes/(app)/invoices/[id]/+page.svelte:424-438` | `paymentStatus()` de la fiche, à partager | +| `frontend/src/lib/features/invoices/SettleInvoiceDialog.svelte:35-112` | pré-remplissage, garde client | +| `frontend/tests/e2e/invoices_echeancier.spec.ts:100-160, 200-201` | cas de la 24-3 ; « la colonne Total affiche le TTC (montant dû) » | +| `crates/kesh-report/src/general_ledger.rs` | solde d'un compte, pour l'AC 5 | + +### Gardes-fous du dépôt + +- Aucune migration. +- Repositories touchés : **gate complet même en cours de boucle** (§ Exception `kesh-db`). +- i18n : les nouvelles clés et tout `i18nMsg(` ajouté bougent `sitesTotal` — recompter. + +## Dev Agent Record + +### Agent Model Used + +### Debug Log References + +### Completion Notes List + +### File List + +## Change Log + +- **2026-09-27** — Créée (Opus 5.5) après découpage de la 25-4-b (arbitrages Q1 ⇒ six modules). + Sites revérifiés dans le code ; 25-4-a en est le socle. + +[#416]: https://github.com/guycorbaz/kesh/issues/416 diff --git a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md index 65f7e7dc..44de9114 100644 --- a/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md +++ b/_bmad-output/implementation-artifacts/25-4-propager-le-residuel.md @@ -20,7 +20,9 @@ Le découpage (quatre stories depuis le 2026-09-27) suit l'ordre des dépendance | Story | Issues | Ce qu'elle fait | Pourquoi à ce rang | |---|---|---|---| | **25-4-a** — le résiduel juste | [#455], [#456] | la formule canonique soustrait l'avoir **TTC** ; un avoir est refusé sur une facture **réglée, même en partie** ; le test de parité promis et jamais écrit | ⛔ **les deux suivantes réemploient la formule** : la propager avant de la corriger propagerait le défaut | -| **25-4-b** — le résiduel aux agrégats | [#416] | balance âgée, totaux de l'échéancier, **colonne « reste dû »** de l'échéancier et de son CSV, dialogue de règlement pré-rempli, **montant réclamé par les relances** | le cœur de #416 ; première utilisatrice des formes jointes | +| **25-4-b** — ⚠️ **découpée le 2026-09-27** en **b1** et **b2** : les arbitrages de Q1 ont ajouté les rappels (texte, QR, PDF, frais), et le tout touchait six modules | [#416] | — | — | +| **25-4-b1** — le résiduel aux agrégats | [#416] | balance âgée, totaux et **colonne « reste dû »** de l'échéancier et de son CSV, statut « partiellement payée » dans l'échéancier, dialogue de règlement **pré-rempli** ; formes jointes du résiduel, invariant balance âgée ↔ grand livre | le cœur de #416 ; première utilisatrice des formes jointes | +| **25-4-b2** — le résiduel aux rappels | [#416] | le rappel réclame le **reste dû** (texte, QR, PDF avec montant initial / déjà réglé / reste), frais configurables et masqués à zéro ; question des frais non comptabilisés | les arbitrages Q1 ; repose sur la grandeur de b1 | | **25-4-c** — le résiduel au rapprochement | [#420] | filtre des candidats, score, re-score à l'acceptation, montant affiché dans la proposition | indépendante de 25-4-b, mais du même socle | | **25-4-d** — solder le reste | [#384] | imputer l'écart d'un règlement partiel — **perte sur débiteur**, escompte, frais bancaires — pour clore une facture partiellement réglée ; la part de **TVA** réduit la TVA due, seule la part HT va au compte de perte | ramenée de la 25-6 le 2026-09-27 (Guy) : elle repose entièrement sur le résiduel juste de 25-4-a | @@ -131,6 +133,7 @@ Pour 25-4-b, donc : ## Change Log +- **2026-09-27** — 25-4-b découpée en b1 (agrégats) / b2 (rappels) : six modules une fois les arbitrages Q1 ajoutés. - **2026-09-27** — Arbitrages : avoir sur facture réglée refusé en 25-4-a, levée tracée par #471 ; #384 ramenée en 25-4-d. - **2026-09-27** — Frais de rappel : configurables, aucune ligne affichée à zéro. - **2026-09-27** — Q1 retranchée : la QR du rappel porte le reste dû (le TTC s'il n'y a aucun règlement) ; numéro de facture conservé ; point ouvert sur les frais. diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index ea88fb5a..b0f9565a 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -1,3 +1,4 @@ +# last_updated: 2026-09-27 (2) (PR #472 ouverte pour 25-4-a ; 25-4-b decoupee b1/b2 ; 25-4-b1 creee.) # last_updated: 2026-09-27 (1) (25-4-d creee depuis #384 ; #471 ouverte ; 25-4-a validee P3.) # last_updated: 2026-09-26 (5) (25-4 DECOUPEE en a/b/c ; 25-4-a creee, #455/#456 rattachees ; #467 #468 #470 mergees.) # last_updated: 2026-09-26 (4) (**25-3-c DONE** — revue close, gates verts, non poussee.) @@ -354,8 +355,10 @@ development_status: 25-3-c-annuler-facture-fournisseur: done # 2026-09-26 **REVUE CLOSE EN 1 PASSE** (0 > LOW, Sonnet x3) ; non poussee ; PR `closes #454` titre ET corps, sur accord. **IMPLEMENTEE** — gates 2472/2472, 783/783, E2E 220/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 **SPEC VALIDEE en 2 passes** (3 MED -> 0 > LOW). **SPECIFIEE** — `25-3-c-annuler-facture-fournisseur.md`. Geste par le socle (autorite `SupplierPurchase`), queue commune sur l'ecriture d'ACHAT, lot en dernier rang ; payee : reglement DETACHE (colonnes remises a NULL, ecriture libre, lien garde par l'audit) — **Q1 tranchee par Guy le 2026-09-26**. Aucune migration. Prochaine etape : validate. # 2026-09-24 NEE d'un arbitrage de Guy — une facture fournisseur s'annule DANS TOUS LES CAS, sauf si l'exercice de son ecriture d'achat est CLOS (l'exercice, pas le verrou de periode) ; payee, son reglement RESTE au grand livre et REDEVIENT A LETTRER (Epic 15 : un paiement doit correspondre a une facture, au besoin creee pour lui — « comme bexio »). Reecrit `cancel`, donc absorbe [#454]. Derriere la 25-3-a. 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* - 25-4-a-residuel-juste: done # 2026-09-27 REVUE CLOSE EN 1 PASSE (0 > LOW, 1 LOW corrige) ; rien de pousse — PR closes #455 #456 sur accord. # IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. - 25-4-b-residuel-aux-agregats: backlog # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. + 25-4-a-residuel-juste: done # 2026-09-27 REVUE CLOSE EN 1 PASSE ; PR #472 OUVERTE (closes #455 #456), attend « merge ». # IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. + 25-4-b1-residuel-aux-agregats: ready-for-dev # 2026-09-27 [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE, validation a lancer. Branche EMPILEE sur 25-4-a. + 25-4-b2-residuel-aux-rappels: backlog # [#416, closes] le rappel reclame le reste du (texte, QR, PDF montant initial / deja regle / reste), frais configurables masques a zero ; point ouvert : frais non comptabilises. + 25-4-b-residuel-aux-agregats: split # 2026-09-27 DECOUPEE en b1 (agregats) / b2 (rappels) — six modules apres les arbitrages Q1. # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. 25-4-d-solder-le-reste: backlog # [#384] ramenee de la 25-6 le 2026-09-27 (Guy) — perte sur debiteur, escompte, frais : clore une facture partiellement reglee ; la part TVA reduit la TVA due. 25-4-propager-le-residuel: split # 2026-09-26 DECOUPEE (six modules > 5) en 25-4-a / 25-4-b / 25-4-c, puis 25-4-d (#384, 2026-09-27) ; #455 et #456 rattachees. # [#416] [#420] — la 24-2 a introduit le reglement PARTIEL ; le residuel n'est propage ni a la balance agee ni a l'echeancier, et **le score de rapprochement compare au TTC et non au residuel** : le virement du solde d'une facture partiellement reglee **score 0**. From cfd2a7796e14f5ad6c659bc6553abd8ed3dfe27b Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:16:04 +0200 Subject: [PATCH 20/34] docs(25-4-b1): prompt de validation P1 (refs #416) Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-validate-prompt-p1.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 _bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p1.md diff --git a/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p1.md b/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p1.md new file mode 100644 index 00000000..63c3960e --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p1.md @@ -0,0 +1,52 @@ +# Prompt — validation P1, Story 25-4-b1 (le résiduel aux agrégats) + +*Versionné le 2026-09-27. **Une lentille** (Sonnet), contexte frais.* + +Dépôt `/home/gcorbaz/devel/kesh`, branche `story/25-4-b-residuel-aux-agregats` (empilée sur la 25-4-a, +PR #472). Fiche à valider : `_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md`. +Mère : `25-4-propager-le-residuel.md` (arbitrages de Guy : **ne pas les contester**). Sœur livrée : +`25-4-a-residuel-juste.md`. Issue : `gh issue view 416`. Contexte : `24-2-encaissement-client.md` +§ D3, D4, invariants. Règles : `CLAUDE.md`. Checklist : `.claude/skills/bmad-create-story/checklist.md`. + +## Axes — tous obligatoires + +1. **Chaque référence `fichier:ligne`** existe et dit ce que la fiche affirme. +2. **Inventaire des sites qui somment le TTC là où le reste dû est la grandeur** — pars du symptôme, + pas de la liste de la fiche : `grep -rn "lt.ttc\|total_ttc\|totalTtc\|INVOICE_TTC" crates/ frontend/src`. + Pour chaque site, dis s'il est dans la b1, dans la b2 (rappels), ou légitimement au TTC. Un site + oublié est au moins MEDIUM. +3. **L'invariant de concordance (AC 5)** : est-il vrai en théorie ? Liste ce qui meut le compte + 1100 hors factures (écritures manuelles, soldes d'ouverture, contre-passations, annulation de + règlement, avoir, facture annulée avec règlement hérité) et vérifie que le montage prescrit les + exclut ou les inclut à bon escient. Le « périmètre `validated AND paid_at IS NULL` » rend-il la + concordance possible (factures `cancelled` à reste dû négatif hérité ?). +4. **Le N+1 (AC 8)** : la liste utilise aujourd'hui la forme **corrélée** du TTC + (`INVOICE_TTC_SUBQUERY_SQL`) ; la fiche prescrit la forme jointe pour le reste dû — cohérence, + coût, et risque de divergence entre `total_ttc` (corrélé) et `amount_due` (joint) sur une même + ligne. +5. **Le frontend** : `statusOf` / `paymentStatus` partageables ? Le dialogue pré-rempli : que se + passe-t-il pour une facture à reste dû négatif ou nul ? Le filtre « payées » de l'échéancier + montre-t-il des factures dont `amountDue` = 0 ? +6. **Les tests** (AC 12) : chacun prouve-t-il ce qu'il annonce ? L'E2E modifié de la 24-3 : le + montage (facture partiellement réglée) est-il faisable depuis Playwright ? Les mutations sont- + elles tuables ? +7. **Le manuel** : lignes citées, et **PDF aplati** + (`pdftotext docs/manual/fr/user-manual.pdf - | tr '\n' ' ' | tr -s ' '`, vers + `/tmp/claude-1000/-home-gcorbaz-devel-kesh/ca5ce2e2-67a3-4eeb-817f-c2de35620a1c/scratchpad/`). + D'autres textes (admin-manual, api-external, README, website) promettent-ils le TTC ? +8. **Le découpage b1/b2** : quelque chose de b1 appartient-il à b2, ou l'inverse ? La PR de b1 en + `refs #416` est-elle juste ? + +## Ce que tu rends + +- **Findings** : sévérité, endroit exact, **preuve** (commande et résultat, code lu), correction. +- ⛔ **La liste des axes réellement exercés ET de ceux qui ne l'ont pas été.** + +## Interdits + +⛔ N'écris aucun fichier du dépôt ; aucune commande qui écrit dans le dépôt ou dans une base — +`scripts/prepare-release.sh`, `scripts/regen-test-schema.sh`, `scripts/install-hooks.sh`, +`scripts/test-fast.sh`, `scripts/mem-guard.sh`, `make`, `latexmk`, tout `git commit`/`push`/`add`/ +`stash`/`reset`/`rebase`/`checkout`/`switch`/`worktree`, `sqlx migrate`, `cargo test`/`nextest`, +`npm run`, `npx playwright`. Autorisés : lecture, `grep`, `git log`/`show`/`diff`, `gh issue view`, +`pdftotext` vers le scratchpad, `cargo check`. From 73f72e8dd07e1f2f107857cd51f758fcfa89905e Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:26:26 +0200 Subject: [PATCH 21/34] =?UTF-8?q?docs(25-4-b1):=20validation=20P1=20?= =?UTF-8?q?=E2=80=94=20port=C3=A9e=20de=20l'invariant=20balance=20=C3=A2g?= =?UTF-8?q?=C3=A9e=20=3D=20grand=20livre,=20quatre=20passages=20du=20manue?= =?UTF-8?q?l=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-residuel-aux-agregats.md | 50 +++++++++++++++++-- .../sprint-status.yaml | 2 +- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md index 8548faeb..fe02f127 100644 --- a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -73,6 +73,18 @@ soldée, créditée —, le **total de la balance âgée = solde du compte 1100 (`general_ledger` ou somme `debit − credit`). C'est l'assertion que #416 réclame et qui n'existe pour aucun écran. +⚠️ **Portée de l'invariant, écrite et non supposée.** Il ne tient que si le compte débiteurs n'est +mû **que** par des factures, règlements et avoirs de Kesh. Trois choses le rompent, légitimement, +et la balance âgée ne les montre pas : +- un **solde d'ouverture** ou une **écriture manuelle** sur le compte débiteurs ; +- l'**état hérité** de 25-4-a — une facture **`cancelled`** créditée **après** un règlement + partiel (plus atteignable par l'application, mais possible par l'import d'un `.keshbackup` + antérieur) : le compte 1100 porte **−(montant réglé)**, la facture `cancelled` est hors du + périmètre `validated`. *(Relevé en validation P1.)* + +Le test de l'AC 5 porte sur les quatre états **vivants** ; ⛔ il n'y ajoute **pas** l'état hérité. +Cette portée est dite dans le doc-comment de `generate` et dans le manuel (AC 13). + **AC 6** — Doc-comment de `generate` (`:95-99` : « Montants = TTC dérivé ») et libellés : la balance âgée montre le **reste dû** ; l'écran (`AgedReceivablesView.svelte`) et son CSV gardent leur structure — seules les valeurs changent. Si un libellé visible dit « TTC » ou « total facturé », @@ -129,9 +141,20 @@ corrigé. consigner : TTC remis dans une tranche de la balance âgée ; `amount_due` retiré de `list_for_export` seul ; `partial` retiré de `statusOf` ; `amountDue={null}` remis. -**AC 13** — Textes (`docs/manual/fr/user-manual.tex`, PDF **contrôlé aplati**) : § Balance âgée -(`\label{sec:balance-agee}`, « encours débiteur (le total dû, TVA comprise) ») dit **reste dû après -règlements partiels et avoirs** ; § Échéancier (`:927`) décrit la colonne et le pré-remplissage. +**AC 13** — Textes (`docs/manual/fr/user-manual.tex`, PDF **contrôlé aplati**), § Balance âgée +(`\label{sec:balance-agee}`) — **quatre** passages, tous au TTC aujourd'hui : +- `:1576` « encours débiteur (le total dû, TVA comprise) » → le **reste dû**, après règlements + partiels et avoirs ; +- `:1578`, légende de la capture : « encours débiteur **TTC** » → « reste dû » ; +- `:1580-1581` : « La colonne « Non échu » **garantit** que le total général réconcilie avec le + solde du compte clients » — vrai **seulement** dans la portée de l'AC 5 : la phrase porte la + réserve (soldes d'ouverture, écritures manuelles sur le compte débiteurs, sauvegardes + antérieures à la 0.12.1) ; +- `:1587-1588`, note : « le **total dû TTC** de chaque facture, jamais le montant hors taxe » → « le + **reste dû** de chaque facture, jamais le montant hors taxe ni le total facturé ». + +⚠️ Greper le symptôme (`TTC`, `total dû`) dans **toute** la section et dans le PDF aplati, pas +seulement ces lignes. Puis : § Échéancier (`:927`) décrit la colonne et le pré-remplissage. CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la balance âgée. ## Tasks / Subtasks @@ -152,6 +175,17 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba - ⛔ **Ne pas utiliser `amount_due` scalaire dans une boucle** sur les lignes d'une liste : N+1. - ⛔ **Ne pas retirer `total_ttc`** de la liste : l'échéancier affiche les deux, et d'autres écrans le lisent. +- ⚠️ **Double calcul du TTC dans les listes** : `total_ttc` y vient de la forme **corrélée** + (`invoices.rs:819`, `:2459`), et la forme jointe de l'AC 1 recalcule `lt.ttc`. Il est **permis** — + et préférable — de dériver `total_ttc` de `lt.ttc` dans ces deux SELECT : `invoice_ttc_parity.rs` + prouve déjà l'égalité des deux formes. +- ⚠️ **Pré-remplissage à reste dû ≤ 0** : `SettleInvoiceDialog` affiche alors une erreur client dès + l'ouverture (`:104-112`). Comportement **déjà présent** sur la fiche + (`invoices/[id]/+page.svelte:1241`) ; un reste dû ≤ 0 sur une facture `validated` sans `paid_at` + n'existe que par un état hérité. Ne pas le corriger ici ; ne pas l'aggraver. +- **Montage E2E** du cas « partiellement réglée » : régler une partie par l'API + (`authedApiContext(page)`, patron `invoices-settlement-cancel.spec.ts:46`), puis ouvrir le dialogue + depuis l'échéancier. - ⚠️ **Le tri** « TotalAmount » de l'échéancier trie sur `i.total_amount` (HT) alors que la colonne affiche le TTC — défaut **préexistant**, hors périmètre ; ne pas l'aggraver, le signaler. @@ -188,6 +222,16 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba ## Change Log +- **2026-09-27** — **validation P1** (Sonnet, prompt `25-4-b1-validate-prompt-p1.md`) — **2 MEDIUM, + 3 LOW**, confirmés dans le code et le manuel. MEDIUM : l'invariant balance âgée = grand livre + (AC 5) était énoncé sans portée — l'état hérité de 25-4-a (facture `cancelled` créditée après + règlement) et les soldes d'ouverture le rompent ; portée écrite, test borné aux états vivants, et + le manuel, qui **garantit** la concordance sans réserve, recevra la réserve. MEDIUM : l'AC 13 ne + citait qu'un des quatre passages « TTC » de la section Balance âgée — les trois autres ajoutés. + LOW (Dev Notes) : double calcul du TTC dans les listes (dériver `total_ttc` de `lt.ttc` permis) ; + pré-remplissage à reste dû ≤ 0 (préexistant, hors périmètre) ; montage E2E du cas partiel. + L'inventaire des sites au TTC, refait depuis le symptôme, n'a trouvé **aucun site orphelin** + (b1, b2, 25-4-c ou légitimement TTC). - **2026-09-27** — Créée (Opus 5.5) après découpage de la 25-4-b (arbitrages Q1 ⇒ six modules). Sites revérifiés dans le code ; 25-4-a en est le socle. diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index b0f9565a..8ebac8d1 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -356,7 +356,7 @@ development_status: 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* 25-4-a-residuel-juste: done # 2026-09-27 REVUE CLOSE EN 1 PASSE ; PR #472 OUVERTE (closes #455 #456), attend « merge ». # IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. - 25-4-b1-residuel-aux-agregats: ready-for-dev # 2026-09-27 [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE, validation a lancer. Branche EMPILEE sur 25-4-a. + 25-4-b1-residuel-aux-agregats: ready-for-dev # 2026-09-27 [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L corriges. Branche EMPILEE sur 25-4-a. 25-4-b2-residuel-aux-rappels: backlog # [#416, closes] le rappel reclame le reste du (texte, QR, PDF montant initial / deja regle / reste), frais configurables masques a zero ; point ouvert : frais non comptabilises. 25-4-b-residuel-aux-agregats: split # 2026-09-27 DECOUPEE en b1 (agregats) / b2 (rappels) — six modules apres les arbitrages Q1. # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. From 9cbf960059eba805ce6532171eb34962fe1402ce Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:26:43 +0200 Subject: [PATCH 22/34] docs(25-4-b1): prompt de validation P2 (refs #416) Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-validate-prompt-p2.md | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 _bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p2.md diff --git a/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p2.md b/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p2.md new file mode 100644 index 00000000..e9e71f2c --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p2.md @@ -0,0 +1,65 @@ +# Prompt — validation P2, Story 25-4-b1 (le résiduel aux agrégats) + +*Versionné le 2026-09-27. **Une lentille** (Haiku), contexte frais.* + +Dépôt `/home/gcorbaz/devel/kesh`, branche `story/25-4-b-residuel-aux-agregats` (empilée sur la 25-4-a, +PR #472). Fiche à valider : `_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md`. +Mère : `25-4-propager-le-residuel.md` (arbitrages de Guy : **ne pas les contester**). Sœur livrée : +`25-4-a-residuel-juste.md`. Issue : `gh issue view 416`. Contexte : `24-2-encaissement-client.md` +§ D3, D4, invariants. Règles : `CLAUDE.md`. Checklist : `.claude/skills/bmad-create-story/checklist.md`. + +## Axes — tous obligatoires + +1. **Chaque référence `fichier:ligne`** existe et dit ce que la fiche affirme. +2. **Inventaire des sites qui somment le TTC là où le reste dû est la grandeur** — pars du symptôme, + pas de la liste de la fiche : `grep -rn "lt.ttc\|total_ttc\|totalTtc\|INVOICE_TTC" crates/ frontend/src`. + Pour chaque site, dis s'il est dans la b1, dans la b2 (rappels), ou légitimement au TTC. Un site + oublié est au moins MEDIUM. +3. **L'invariant de concordance (AC 5)** : est-il vrai en théorie ? Liste ce qui meut le compte + 1100 hors factures (écritures manuelles, soldes d'ouverture, contre-passations, annulation de + règlement, avoir, facture annulée avec règlement hérité) et vérifie que le montage prescrit les + exclut ou les inclut à bon escient. Le « périmètre `validated AND paid_at IS NULL` » rend-il la + concordance possible (factures `cancelled` à reste dû négatif hérité ?). +4. **Le N+1 (AC 8)** : la liste utilise aujourd'hui la forme **corrélée** du TTC + (`INVOICE_TTC_SUBQUERY_SQL`) ; la fiche prescrit la forme jointe pour le reste dû — cohérence, + coût, et risque de divergence entre `total_ttc` (corrélé) et `amount_due` (joint) sur une même + ligne. +5. **Le frontend** : `statusOf` / `paymentStatus` partageables ? Le dialogue pré-rempli : que se + passe-t-il pour une facture à reste dû négatif ou nul ? Le filtre « payées » de l'échéancier + montre-t-il des factures dont `amountDue` = 0 ? +6. **Les tests** (AC 12) : chacun prouve-t-il ce qu'il annonce ? L'E2E modifié de la 24-3 : le + montage (facture partiellement réglée) est-il faisable depuis Playwright ? Les mutations sont- + elles tuables ? +7. **Le manuel** : lignes citées, et **PDF aplati** + (`pdftotext docs/manual/fr/user-manual.pdf - | tr '\n' ' ' | tr -s ' '`, vers + `/tmp/claude-1000/-home-gcorbaz-devel-kesh/ca5ce2e2-67a3-4eeb-817f-c2de35620a1c/scratchpad/`). + D'autres textes (admin-manual, api-external, README, website) promettent-ils le TTC ? +8. **Le découpage b1/b2** : quelque chose de b1 appartient-il à b2, ou l'inverse ? La PR de b1 en + `refs #416` est-elle juste ? + +## Ce que tu rends + +- **Findings** : sévérité, endroit exact, **preuve** (commande et résultat, code lu), correction. +- ⛔ **La liste des axes réellement exercés ET de ceux qui ne l'ont pas été.** + +## Interdits + +⛔ N'écris aucun fichier du dépôt ; aucune commande qui écrit dans le dépôt ou dans une base — +`scripts/prepare-release.sh`, `scripts/regen-test-schema.sh`, `scripts/install-hooks.sh`, +`scripts/test-fast.sh`, `scripts/mem-guard.sh`, `make`, `latexmk`, tout `git commit`/`push`/`add`/ +`stash`/`reset`/`rebase`/`checkout`/`switch`/`worktree`, `sqlx migrate`, `cargo test`/`nextest`, +`npm run`, `npx playwright`. Autorisés : lecture, `grep`, `git log`/`show`/`diff`, `gh issue view`, +`pdftotext` vers le scratchpad, `cargo check`. + +## Contexte P1 — à vérifier, pas à re-signaler + +La P1 a trouvé : l'invariant AC 5 sans portée (état hérité `cancelled`, soldes d'ouverture) et +trois passages « TTC » du manuel absents de l'AC 13 ; corrigés dans `git diff cfd2a779 73f72e8d` +(**diff unique, à lire en priorité**). Vérifie que ces corrections sont justes et complètes : +la portée de l'AC 5 énumère-t-elle **tout** ce qui meut le compte 1100 hors factures de Kesh +(lis `crates/kesh-db/src/repositories/opening_balances*.rs`, les contre-passations, l'import +fournisseurs n'y touche pas ?) ; les quatre passages du manuel sont-ils les seuls de la section et +du document (greppe `TTC`, `total dû`, `encours` dans le `.tex` ET le PDF aplati) ? + +⛔ Pour chaque finding, cite la ligne exacte lue dans le fichier ACTUEL, et vérifie ton affirmation +par `grep -nF` avant de l'écrire. From 58a874ab61532d2675baa9770fe7caa4470d4416 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:32:25 +0200 Subject: [PATCH 23/34] =?UTF-8?q?docs(25-4-b1):=20validation=20P2=20?= =?UTF-8?q?=E2=80=94=20la=20port=C3=A9e=20de=20l'invariant=20oubliait=20le?= =?UTF-8?q?=20rapprochement=20par=20ventilation=20ou=20r=C3=A8gle=20(refs?= =?UTF-8?q?=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-residuel-aux-agregats.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md index fe02f127..9596c8cc 100644 --- a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -74,9 +74,13 @@ soldée, créditée —, le **total de la balance âgée = solde du compte 1100 pour aucun écran. ⚠️ **Portée de l'invariant, écrite et non supposée.** Il ne tient que si le compte débiteurs n'est -mû **que** par des factures, règlements et avoirs de Kesh. Trois choses le rompent, légitimement, +mû **que** par des factures, règlements et avoirs de Kesh. Quatre choses le rompent, légitimement, et la balance âgée ne les montre pas : - un **solde d'ouverture** ou une **écriture manuelle** sur le compte débiteurs ; +- une **ventilation** ou une **règle** de rapprochement bancaire imputée au compte débiteurs + (`accept_one_split`, `accept_one_rule`, `kesh-api/src/routes/reconciliation.rs:1619`, `:1997`) — + la contrepartie y est un compte choisi par l'utilisateur, sans facture ; *(ajouté par + l'orchestrateur en vérifiant la P2)* ; - l'**état hérité** de 25-4-a — une facture **`cancelled`** créditée **après** un règlement partiel (plus atteignable par l'application, mais possible par l'import d'un `.keshbackup` antérieur) : le compte 1100 porte **−(montant réglé)**, la facture `cancelled` est hors du @@ -148,8 +152,8 @@ seul ; `partial` retiré de `statusOf` ; `amountDue={null}` remis. - `:1578`, légende de la capture : « encours débiteur **TTC** » → « reste dû » ; - `:1580-1581` : « La colonne « Non échu » **garantit** que le total général réconcilie avec le solde du compte clients » — vrai **seulement** dans la portée de l'AC 5 : la phrase porte la - réserve (soldes d'ouverture, écritures manuelles sur le compte débiteurs, sauvegardes - antérieures à la 0.12.1) ; + réserve (soldes d'ouverture, écritures manuelles ou rapprochements bancaires imputés directement + au compte débiteurs, sauvegardes antérieures à la 0.12.1) ; - `:1587-1588`, note : « le **total dû TTC** de chaque facture, jamais le montant hors taxe » → « le **reste dû** de chaque facture, jamais le montant hors taxe ni le total facturé ». @@ -222,6 +226,15 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba ## Change Log +- **2026-09-27** — **validation P2** (Haiku, prompt `25-4-b1-validate-prompt-p2.md`) — rapporte **0 + finding**. ⚠️ **Non pris pour argent comptant** : la passe affirmait le CHANGELOG « à créer » (il + existe) et n'a cité aucun des fichiers qu'on lui demandait de lire pour éprouver la portée de + l'AC 5. **Repris par l'orchestrateur** : la portée oubliait un chemin — la **ventilation** et la + **règle** de rapprochement bancaire (`accept_one_split`, `accept_one_rule`), dont la contrepartie + est un compte libre, donc possiblement le compte débiteurs, sans facture. **1 MEDIUM** ajouté, + corrigé (portée à quatre cas, réserve du manuel étendue). Vérifié aussi : le filtre « payées » + de l'échéancier existe (`due-dates/+page.svelte:45`) — ses lignes montreront un reste dû nul, + cohérent. - **2026-09-27** — **validation P1** (Sonnet, prompt `25-4-b1-validate-prompt-p1.md`) — **2 MEDIUM, 3 LOW**, confirmés dans le code et le manuel. MEDIUM : l'invariant balance âgée = grand livre (AC 5) était énoncé sans portée — l'état hérité de 25-4-a (facture `cancelled` créditée après From 72255481b49a508e4eac283abf1994fef081cdbb Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:32:51 +0200 Subject: [PATCH 24/34] =?UTF-8?q?docs(25-4-b1):=20prompt=20de=20validation?= =?UTF-8?q?=20P3=20cibl=C3=A9e=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-validate-prompt-p3.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 _bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p3.md diff --git a/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p3.md b/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p3.md new file mode 100644 index 00000000..e1b97c6e --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p3.md @@ -0,0 +1,43 @@ +# Prompt — validation P3 CIBLÉE, Story 25-4-b1 (le résiduel aux agrégats) + +*Versionné le 2026-09-27. **Une lentille** (Opus), contexte frais. Passe **ciblée** : la P2 (Haiku) +a rendu « 0 finding » sans exercer l'axe qu'on lui confiait ; l'orchestrateur l'a repris et a +trouvé un chemin oublié. Ce qu'il reste à relire : la **portée de l'AC 5**, et elle seule, contre le +code.* + +Dépôt `/home/gcorbaz/devel/kesh`, branche `story/25-4-b-residuel-aux-agregats`. Fiche : +`_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md`, AC 5 (bloc « Portée de +l'invariant ») et AC 13 (réserve du manuel). Diff des remédiations : `git diff cfd2a779 58a874ab`. + +## La lentille : inventaire des sites NON RÉSOLUS + +L'AC 5 affirme : *total de la balance âgée = solde du compte débiteurs au grand livre*, sauf quatre +cas énumérés. ⛔ **Ne vérifie pas que les quatre cas sont vrais — cherche le cinquième.** + +1. **Inventorie l'ensemble clos de ce qui écrit une ligne d'écriture** (`journal_entry_lines`) : + pars du symptôme — `grep -rn "create_in_tx\|journal_entries::create\|INSERT INTO journal_entry_lines" crates/ --include=*.rs` + hors tests. Pour **chaque** chemin, dis s'il peut imputer le compte débiteurs (le compte + `default_receivable_account_id` ou le compte de la ligne de débit d'une vente), et, s'il le + peut, s'il est (a) une facture, un règlement ou un avoir **visibles** de la balance âgée, + (b) un des quatre cas de la portée, ou (c) **un cas oublié**. +2. Pense aux chemins **de clôture et de réouverture d'exercice**, à la **dévalidation** d'une + facture, à l'**annulation d'un règlement**, à la **contre-passation** manuelle d'une écriture de + vente ou de règlement depuis sa fiche, à l'**import** de pièces, au **rapprochement** annulé. +3. Pour chaque cas oublié : sévérité, preuve (code lu), et formulation à ajouter à la portée. +4. La réserve prescrite pour le manuel (AC 13, `:1580-1581`) couvre-t-elle alors tous les cas, en + mots d'utilisateur ? + +## Ce que tu rends + +- **Findings** : sévérité, endroit, **preuve**, correction. +- ⛔ **La liste des chemins inventoriés** (fichier:fonction → classement a/b/c) — c'est elle qui + prouve l'axe — **et des axes non exercés.** + +## Interdits + +⛔ N'écris aucun fichier du dépôt ; aucune commande qui écrit dans le dépôt ou dans une base — +`scripts/prepare-release.sh`, `scripts/regen-test-schema.sh`, `scripts/install-hooks.sh`, +`scripts/test-fast.sh`, `scripts/mem-guard.sh`, `make`, `latexmk`, tout `git commit`/`push`/`add`/ +`stash`/`reset`/`rebase`/`checkout`/`switch`/`worktree`, `sqlx migrate`, `cargo test`/`nextest`, +`npm run`, `npx playwright`. Autorisés : lecture, `grep`, `git log`/`show`/`diff`, `gh issue view`, +`cargo check`. From 861823e56756292728ec3470ef593287bddac2a5 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:39:42 +0200 Subject: [PATCH 25/34] =?UTF-8?q?docs(25-4-b1):=20validation=20P3=20?= =?UTF-8?q?=E2=80=94=20la=20port=C3=A9e=20de=20l'invariant=20s'=C3=A9nonce?= =?UTF-8?q?=20par=20une=20r=C3=A8gle=20;=20#473=20et=20#474=20ouvertes=20(?= =?UTF-8?q?refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-residuel-aux-agregats.md | 67 ++++++++++++++----- .../sprint-status.yaml | 2 +- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md index 9596c8cc..7c5fd788 100644 --- a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -73,21 +73,28 @@ soldée, créditée —, le **total de la balance âgée = solde du compte 1100 (`general_ledger` ou somme `debit − credit`). C'est l'assertion que #416 réclame et qui n'existe pour aucun écran. -⚠️ **Portée de l'invariant, écrite et non supposée.** Il ne tient que si le compte débiteurs n'est -mû **que** par des factures, règlements et avoirs de Kesh. Quatre choses le rompent, légitimement, -et la balance âgée ne les montre pas : -- un **solde d'ouverture** ou une **écriture manuelle** sur le compte débiteurs ; -- une **ventilation** ou une **règle** de rapprochement bancaire imputée au compte débiteurs - (`accept_one_split`, `accept_one_rule`, `kesh-api/src/routes/reconciliation.rs:1619`, `:1997`) — - la contrepartie y est un compte choisi par l'utilisateur, sans facture ; *(ajouté par - l'orchestrateur en vérifiant la P2)* ; -- l'**état hérité** de 25-4-a — une facture **`cancelled`** créditée **après** un règlement - partiel (plus atteignable par l'application, mais possible par l'import d'un `.keshbackup` - antérieur) : le compte 1100 porte **−(montant réglé)**, la facture `cancelled` est hors du - périmètre `validated`. *(Relevé en validation P1.)* - -Le test de l'AC 5 porte sur les quatre états **vivants** ; ⛔ il n'y ajoute **pas** l'état hérité. -Cette portée est dite dans le doc-comment de `generate` et dans le manuel (AC 13). +⚠️ **Portée de l'invariant — énoncée par une RÈGLE, pas par une liste.** *(Réécrite en validation +P3 : trois passes ont chacune trouvé des chemins que la liste précédente omettait — quatre en P3. +Une énumération de formes est ouverte par nature ; `CLAUDE.md` § « Inventorier les sites NON +RÉSOLUS ».)* + +**La règle** : l'égalité vaut **si et seulement si** le compte débiteurs n'est mouvementé **que** par +les écritures de **vente**, de **règlement client** et d'**avoir** que Kesh passe pour des factures +**validées**, toutes **datées au plus tard à la date d'arrêté** (`as_of`), et **n'a jamais changé** +dans les réglages de facturation. Tout autre mouvement l'en écarte. *Exemples*, non exhaustifs, tous +vérifiés atteignables : solde d'ouverture ; écriture saisie au journal ; rapprochement bancaire hors +facture — manuel, ventilé, par règle (`post_manual`, `post_split`, `accept_one_split`, +`accept_one_rule`) ; règlement d'une facture **fournisseur** par compte interne imputé au compte +débiteurs (compensation) ; changement du compte débiteurs par défaut (et l'avoir qui, depuis, crédite +le nouveau compte — **défaut produit, #473**) ; règlement client imputé au compte débiteurs lui-même +(**#474**) ; **données antérieures à la 0.12.1** — factures marquées payées sans écriture par +l'ancien `mark_as_paid` (avant 0.12.0), facture annulée par un avoir après règlement partiel +(**0.12.0 publiée**, `git show v0.12.0:…/credit_notes.rs:302`) — qu'elles viennent d'une +**restauration** ou d'une **mise à jour sur place**. + +Le test de l'AC 5 se place **dans** la règle : les quatre états **vivants**, et un `as_of` +postérieur ou égal à toutes les dates de pièces. ⛔ Il n'y ajoute aucun cas hors règle. Le +doc-comment de `generate` énonce la règle, pas la liste des exemples. **AC 6** — Doc-comment de `generate` (`:95-99` : « Montants = TTC dérivé ») et libellés : la balance âgée montre le **reste dû** ; l'écran (`AgedReceivablesView.svelte`) et son CSV gardent leur @@ -151,9 +158,13 @@ seul ; `partial` retiré de `statusOf` ; `amountDue={null}` remis. partiels et avoirs ; - `:1578`, légende de la capture : « encours débiteur **TTC** » → « reste dû » ; - `:1580-1581` : « La colonne « Non échu » **garantit** que le total général réconcilie avec le - solde du compte clients » — vrai **seulement** dans la portée de l'AC 5 : la phrase porte la - réserve (soldes d'ouverture, écritures manuelles ou rapprochements bancaires imputés directement - au compte débiteurs, sauvegardes antérieures à la 0.12.1) ; + solde du compte clients » — vrai **seulement** dans la règle de l'AC 5. La phrase devient, par la + règle et non par une liste : *« Le total général réconcilie avec le solde du compte clients tant + que ce compte n'est mouvementé que par les factures, leurs règlements et leurs avoirs. Toute autre + écriture qui le touche — solde de départ, écriture saisie au journal, rapprochement bancaire ou + règlement (y compris d'une facture fournisseur) imputé directement à ce compte — l'en écarte, de + même qu'un changement du compte clients dans les réglages de facturation, ou des données + antérieures à la version 0.12.1, restaurées ou mises à jour. »* ; - `:1587-1588`, note : « le **total dû TTC** de chaque facture, jamais le montant hors taxe » → « le **reste dû** de chaque facture, jamais le montant hors taxe ni le total facturé ». @@ -163,6 +174,13 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba ## Tasks / Subtasks +- [ ] **T0 — rectifier la portée de l'état hérité posée par 25-4-a** : quatre commentaires disent + l'état « avoir après règlement partiel » atteignable **seulement par l'import d'une + sauvegarde** — `errors.rs:221`, `invoice_settlements_write.rs:333`, + `tests/invoice_amount_due_parity.rs:139`, `tests/invoice_settlement.rs:980`. Faux : la **0.12.0 + publiée** accepte cet avoir, une installation **mise à jour sur place** le porte. Écrire « des + données antérieures à la 0.12.1, restaurées ou mises à jour ». *(Le manuel de 25-4-a dit + déjà « avant la version 0.12.1, ou restaurée » : juste.)* - [ ] **T1 — la grandeur jointe** (AC 1-2). - [ ] **T2 — balance âgée** (AC 3-6), dont l'invariant de concordance. - [ ] **T3 — échéancier, backend** (AC 7-9, AC 11). @@ -226,6 +244,19 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba ## Change Log +- **2026-09-27** — **validation P3 ciblée** (Opus, prompt `25-4-b1-validate-prompt-p3.md`, inventaire + de **tous** les chemins qui écrivent une ligne d'écriture) — **3 MEDIUM, 3 LOW** : **quatre chemins + oubliés** par la portée de l'AC 5 — règlement fournisseur par compte interne imputé au compte + débiteurs ; changement du compte débiteurs par défaut, que l'avoir suit alors que la vente ne le + suit pas ; données héritées plus larges (`mark_as_paid` sans écriture) et **pas seulement par + import** (la 0.12.0 publiée accepte l'avoir après règlement partiel) ; règlement client imputé au + compte débiteurs lui-même. LOW : deux handlers de rapprochement non cités ; `as_of` non borné. + ⛔ **Troisième passe consécutive à trouver des cas hors d'une liste** : la portée est **réécrite + par une règle**, avec des exemples non exhaustifs ; le test se place dans la règle, `as_of` + postérieur à toutes les pièces ; la réserve du manuel suit la même forme. **Deux défauts produit + sortis en issues** : #473 (l'avoir crédite le compte débiteurs des réglages, pas celui de la + vente), #474 (règlement client sur le compte débiteurs lui-même). **T0 ajoutée** : quatre + commentaires de 25-4-a bornaient à tort l'état hérité à l'import. - **2026-09-27** — **validation P2** (Haiku, prompt `25-4-b1-validate-prompt-p2.md`) — rapporte **0 finding**. ⚠️ **Non pris pour argent comptant** : la passe affirmait le CHANGELOG « à créer » (il existe) et n'a cité aucun des fichiers qu'on lui demandait de lire pour éprouver la portée de diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 8ebac8d1..316bb2f2 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -356,7 +356,7 @@ development_status: 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* 25-4-a-residuel-juste: done # 2026-09-27 REVUE CLOSE EN 1 PASSE ; PR #472 OUVERTE (closes #455 #456), attend « merge ». # IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. - 25-4-b1-residuel-aux-agregats: ready-for-dev # 2026-09-27 [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L corriges. Branche EMPILEE sur 25-4-a. + 25-4-b1-residuel-aux-agregats: ready-for-dev # 2026-09-27 [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L -> P2 Haiku 0 (repris : 1M) -> P3 ciblee Opus 3M/3L : portee de l'invariant REECRITE PAR UNE REGLE ; issues #473 #474 ouvertes. Branche EMPILEE sur 25-4-a. 25-4-b2-residuel-aux-rappels: backlog # [#416, closes] le rappel reclame le reste du (texte, QR, PDF montant initial / deja regle / reste), frais configurables masques a zero ; point ouvert : frais non comptabilises. 25-4-b-residuel-aux-agregats: split # 2026-09-27 DECOUPEE en b1 (agregats) / b2 (rappels) — six modules apres les arbitrages Q1. # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. From 5480b4f8e6a97a720ae2f3e2d345166d3c96650d Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:39:42 +0200 Subject: [PATCH 26/34] =?UTF-8?q?docs(25-4-b1):=20prompt=20de=20validation?= =?UTF-8?q?=20P4=20cibl=C3=A9e=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-validate-prompt-p4.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 _bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p4.md diff --git a/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p4.md b/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p4.md new file mode 100644 index 00000000..bd2491b1 --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p4.md @@ -0,0 +1,43 @@ +# Prompt — validation P4 CIBLÉE, Story 25-4-b1 (le résiduel aux agrégats) + +*Versionné le 2026-09-27. **Une lentille** (Sonnet), contexte frais. Passe **ciblée** sur la +remédiation de la P3 : la portée de l'AC 5 a été **réécrite par une règle** après trois passes qui +trouvaient chacune des cas hors de la liste précédente.* + +Dépôt `/home/gcorbaz/devel/kesh`, branche `story/25-4-b-residuel-aux-agregats`. Fiche : +`_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md`. Diff à relire : +`git diff 72255481 HEAD -- _bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md`. +Issues sorties : `gh issue view 473`, `gh issue view 474`. + +## La lentille + +1. **La règle est-elle juste ?** Trop large (elle exclut un cas où l'égalité tient) ou trop étroite + (un mouvement qu'elle autorise rompt l'égalité) ? Éprouve-la contre : la **dévalidation** d'une + facture (l'écriture de vente disparaît), l'**annulation d'un règlement** (contre-passation + + suppression de la ligne), l'**annulation d'un rapprochement** de facture, une facture **soldée** + (`paid_at`) — hors du périmètre de la balance âgée mais ses mouvements restent au grand livre et + s'annulent-ils exactement ? —, un **reste dû négatif** sur une facture validée. +2. **« n'a jamais changé dans les réglages »** : formulation testable ? Le test peut-il la tenir ? +3. **Le test prescrit** (quatre états vivants, `as_of` ≥ toutes les pièces) : prouve-t-il la règle ou + passe-t-il par construction ? Quelle mutation de la requête de la balance âgée le ferait rougir ? +4. **T0** : les quatre sites cités existent-ils aux lignes dites, et la correction prescrite est-elle + juste (`git show v0.12.0:crates/kesh-db/src/repositories/credit_notes.rs`) ? En existe-t-il un + cinquième (greppe la **valeur** : `keshbackup`, `sauvegarde antérieure`, `import d'une sauvegarde` + dans `crates/`, `docs/`, les fiches 25-4-*) ? +5. **La réserve du manuel** : exacte, lisible par un utilisateur, cohérente avec la règle ? +6. **Propagation** : le mot « quatre » (cas), les références aux handlers, #473/#474 — cohérents + partout (fiche, fiche mère, `sprint-status.yaml`) ? + +## Ce que tu rends + +- **Findings** : sévérité, endroit, **preuve** (commande et résultat, code lu), correction. +- ⛔ **La liste des axes réellement exercés ET de ceux qui ne l'ont pas été.** + +## Interdits + +⛔ N'écris aucun fichier du dépôt ; aucune commande qui écrit dans le dépôt ou dans une base — +`scripts/prepare-release.sh`, `scripts/regen-test-schema.sh`, `scripts/install-hooks.sh`, +`scripts/test-fast.sh`, `scripts/mem-guard.sh`, `make`, `latexmk`, tout `git commit`/`push`/`add`/ +`stash`/`reset`/`rebase`/`checkout`/`switch`/`worktree`, `sqlx migrate`, `cargo test`/`nextest`, +`npm run`, `npx playwright`. Autorisés : lecture, `grep`, `git log`/`show`/`diff`, `gh issue view`, +`cargo check`. From 5824c0485fd74e17e81dc0c099de5706e81be8be Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:48:33 +0200 Subject: [PATCH 27/34] =?UTF-8?q?docs(25-4-b1):=20validation=20P4=20?= =?UTF-8?q?=E2=80=94=20cinqui=C3=A8me=20site=20de=20l'=C3=A9tat=20h=C3=A9r?= =?UTF-8?q?it=C3=A9,=20contre-passations=20dans=20la=20r=C3=A8gle=20(refs?= =?UTF-8?q?=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-a-residuel-juste.md | 7 ++++ .../25-4-b1-residuel-aux-agregats.md | 33 ++++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md index 0f17fadb..a258d620 100644 --- a/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md +++ b/_bmad-output/implementation-artifacts/25-4-a-residuel-juste.md @@ -456,6 +456,13 @@ Claude Opus 5.5. ## Change Log +- **2026-09-27** — ⚠️ **Rectification après livraison** (relevée en validation de 25-4-b1, P3-P4) : + l'AC 13 dit l'état « avoir après règlement partiel » atteignable **« que par l'import d'une + sauvegarde antérieure »**. C'est **faux par omission** : la **0.12.0 publiée** accepte cet avoir + (`git show v0.12.0:crates/kesh-db/src/repositories/credit_notes.rs:302`), si bien qu'une + installation **mise à jour sur place** peut le porter. Lire : « des données antérieures à la + 0.12.1, restaurées ou mises à jour ». Les quatre commentaires de code qui reprenaient la + formulation sont rectifiés par 25-4-b1 (T0) ; le manuel disait déjà juste. - **2026-09-27** — **revue de code P1** (Sonnet, diff `ab9b9448..9bf2bb2b`, prompt `25-4-a-review-prompt-p1.md`) — **0 au-dessus de LOW, 1 LOW** : le compte rendu attribuait la formule « réglée, même en partie » aux deux limites du manuel, la seconde dit « déjà encaissée, diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md index 7c5fd788..9d5316a5 100644 --- a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -81,7 +81,10 @@ RÉSOLUS ».)* **La règle** : l'égalité vaut **si et seulement si** le compte débiteurs n'est mouvementé **que** par les écritures de **vente**, de **règlement client** et d'**avoir** que Kesh passe pour des factures **validées**, toutes **datées au plus tard à la date d'arrêté** (`as_of`), et **n'a jamais changé** -dans les réglages de facturation. Tout autre mouvement l'en écarte. *Exemples*, non exhaustifs, tous +dans les réglages de facturation. Les **contre-passations** de ces mouvements (annulation d'un +règlement, d'un rapprochement de facture) restent **dans** la règle : elles inversent exactement +les mêmes comptes et montants, pendant que le reste dû remonte d'autant. Tout autre mouvement l'en +écarte. *Exemples*, non exhaustifs, tous vérifiés atteignables : solde d'ouverture ; écriture saisie au journal ; rapprochement bancaire hors facture — manuel, ventilé, par règle (`post_manual`, `post_split`, `accept_one_split`, `accept_one_rule`) ; règlement d'une facture **fournisseur** par compte interne imputé au compte @@ -149,7 +152,8 @@ corrigé. | **E2E** `invoices_echeancier.spec.ts` | le cas de la 24-3 (`:147-158`) cesse de **saisir** le montant : il vérifie qu'il est **pré-rempli** au reste dû, sur une facture partiellement réglée | ⛔ **Chaque facture de test porte une TVA non nulle** (leçon de 25-4-a). ⛔ Mutations à exécuter et -consigner : TTC remis dans une tranche de la balance âgée ; `amount_due` retiré de `list_for_export` +consigner : TTC remis dans une tranche de la balance âgée — **et TTC remis dans le seul total** (doit +faire rougir `aged_total_matches_receivable_ledger`) ; `amount_due` retiré de `list_for_export` seul ; `partial` retiré de `statusOf` ; `amountDue={null}` remis. **AC 13** — Textes (`docs/manual/fr/user-manual.tex`, PDF **contrôlé aplati**), § Balance âgée @@ -160,10 +164,11 @@ seul ; `partial` retiré de `statusOf` ; `amountDue={null}` remis. - `:1580-1581` : « La colonne « Non échu » **garantit** que le total général réconcilie avec le solde du compte clients » — vrai **seulement** dans la règle de l'AC 5. La phrase devient, par la règle et non par une liste : *« Le total général réconcilie avec le solde du compte clients tant - que ce compte n'est mouvementé que par les factures, leurs règlements et leurs avoirs. Toute autre - écriture qui le touche — solde de départ, écriture saisie au journal, rapprochement bancaire ou - règlement (y compris d'une facture fournisseur) imputé directement à ce compte — l'en écarte, de - même qu'un changement du compte clients dans les réglages de facturation, ou des données + que ce compte n'est mouvementé que par les factures, leurs règlements et leurs avoirs (y compris + leurs annulations). Toute autre écriture qui le touche l'en écarte : solde de départ, écriture + saisie au journal, virement bancaire affecté à ce compte sans passer par une facture, paiement + d'une facture fournisseur compensé sur ce compte, ou règlement dont la contrepartie est ce compte + lui-même. De même un changement du compte clients dans les réglages de facturation, ou des données antérieures à la version 0.12.1, restaurées ou mises à jour. »* ; - `:1587-1588`, note : « le **total dû TTC** de chaque facture, jamais le montant hors taxe » → « le **reste dû** de chaque facture, jamais le montant hors taxe ni le total facturé ». @@ -174,10 +179,12 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba ## Tasks / Subtasks -- [ ] **T0 — rectifier la portée de l'état hérité posée par 25-4-a** : quatre commentaires disent +- [ ] **T0 — rectifier la portée de l'état hérité posée par 25-4-a** : **cinq** sites disent l'état « avoir après règlement partiel » atteignable **seulement par l'import d'une - sauvegarde** — `errors.rs:221`, `invoice_settlements_write.rs:333`, - `tests/invoice_amount_due_parity.rs:139`, `tests/invoice_settlement.rs:980`. Faux : la **0.12.0 + sauvegarde** — quatre commentaires, `errors.rs:221`, `invoice_settlements_write.rs:333`, + `tests/invoice_amount_due_parity.rs:139`, `tests/invoice_settlement.rs:980`, et **leur source**, + la fiche `25-4-a-residuel-juste.md` (AC 13, `:180`), rectifiée par une note datée plutôt que + réécrite (story livrée). Faux : la **0.12.0 publiée** accepte cet avoir, une installation **mise à jour sur place** le porte. Écrire « des données antérieures à la 0.12.1, restaurées ou mises à jour ». *(Le manuel de 25-4-a dit déjà « avant la version 0.12.1, ou restaurée » : juste.)* @@ -244,6 +251,14 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba ## Change Log +- **2026-09-27** — **validation P4 ciblée** (Sonnet, prompt `25-4-b1-validate-prompt-p4.md`) — **1 + MEDIUM, 3 LOW**. MEDIUM : un **cinquième** site de la formulation fausse — la fiche de 25-4-a + elle-même (AC 13, `:180`), source des quatre commentaires ; ajouté à T0, rectifiée par une note + datée. LOW : la règle ne disait pas que les contre-passations (annulation de règlement ou de + rapprochement) restent dans sa portée — ajouté ; mutation dédiée au test de l'AC 5 ; réserve du + manuel reformulée pour ne plus pouvoir se lire comme visant les règlements ordinaires. Éprouvée + contre la dévalidation, l'annulation de règlement et de rapprochement, la facture soldée et le + reste dû négatif : **la règle tient**. - **2026-09-27** — **validation P3 ciblée** (Opus, prompt `25-4-b1-validate-prompt-p3.md`, inventaire de **tous** les chemins qui écrivent une ligne d'écriture) — **3 MEDIUM, 3 LOW** : **quatre chemins oubliés** par la portée de l'AC 5 — règlement fournisseur par compte interne imputé au compte From 3287d33f6f83587299d4b8367e228e411cbc4104 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:48:51 +0200 Subject: [PATCH 28/34] =?UTF-8?q?docs(25-4-b1):=20prompt=20de=20validation?= =?UTF-8?q?=20P5=20cibl=C3=A9e=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-validate-prompt-p5.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 _bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p5.md diff --git a/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p5.md b/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p5.md new file mode 100644 index 00000000..9394a364 --- /dev/null +++ b/_bmad-output/implementation-artifacts/25-4-b1-validate-prompt-p5.md @@ -0,0 +1,34 @@ +# Prompt — validation P5 CIBLÉE, Story 25-4-b1 + +*Versionné le 2026-09-27. **Une lentille** (Haiku), contexte frais. Passe ciblée sur la seule +remédiation de la P4.* + +Dépôt `/home/gcorbaz/devel/kesh`, branche `story/25-4-b-residuel-aux-agregats`. Diff **unique** à +relire : `git diff 5480b4f8 5824c048` (deux fiches : `25-4-b1-residuel-aux-agregats.md`, +`25-4-a-residuel-juste.md`). + +## Ce que tu vérifies + +1. **T0** liste cinq sites : chacun existe-t-il à la ligne dite, avec la formulation dite ? Commande + obligatoire, à recopier dans ton rapport : `grep -rnF "sauvegarde antérieure" crates/ _bmad-output/implementation-artifacts/25-4-a-residuel-juste.md` + et `grep -rnF "keshbackup" crates/`. +2. **La phrase ajoutée à la règle** (contre-passations dans la portée) : juste ? Lis + `invoice_settlements_write.rs` (`cancel_settlement_in_tx`) avant de répondre. +3. **La réserve du manuel reformulée** : chacun de ses cas correspond-il à un cas réel de la règle ? + En manque-t-il un par rapport à la règle ? +4. **La note datée dans la fiche 25-4-a** : exacte (`git show v0.12.0:crates/kesh-db/src/repositories/credit_notes.rs | grep -n "paid_at.is_some"`) ? + +⛔ Une fiche `ready-for-dev` décrit du code **à écrire** : ne signale pas que le code ne le fait pas +encore. ⛔ Pour chaque finding, recopie la **sortie** de la commande qui le prouve. + +## Ce que tu rends + +Findings (sévérité, endroit, preuve recopiée, correction) et **la liste des axes exercés et non +exercés**. + +## Interdits + +⛔ Aucune écriture dans le dépôt ; aucune commande mutante — `scripts/*.sh`, `make`, `latexmk`, +`git commit`/`push`/`add`/`stash`/`reset`/`rebase`/`checkout`/`switch`/`worktree`, `sqlx migrate`, +`cargo test`/`nextest`, `npm run`, `npx playwright`. Autorisés : lecture, `grep`, `git show`/`diff`, +`gh issue view`. From 8c5eebf02178f2a01cb37c3172ccc9439471ed71 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 01:52:42 +0200 Subject: [PATCH 29/34] docs(25-4-b1): validation close en 5 passes (refs #416) Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-residuel-aux-agregats.md | 8 ++++++++ _bmad-output/implementation-artifacts/sprint-status.yaml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md index 9d5316a5..3f48e94b 100644 --- a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -251,6 +251,14 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba ## Change Log +- **2026-09-27** — **validation P5 ciblée** (Haiku, diff `5480b4f8..5824c048`, prompt + `25-4-b1-validate-prompt-p5.md`) — **0 finding**, preuves recopiées (grep des cinq sites de T0, + garde de la 0.12.0) et **concordantes avec les greps de l'orchestrateur**. ⛔ **Boucle close en 5 + passes** : `2M/3L → 0 (+1M orchestrateur) → 3M/3L → 1M/3L → 0`, rotation Sonnet → Haiku → Opus → + Sonnet → Haiku, trois passes ciblées ; toutes les corrections sur la spec, aucune sur du code. + ⚠️ **Signal de découpage à remonter à Guy** : quatre passes consécutives au niveau MEDIUM + (§ *Règle de splitting préventif*, critère de sévérité). Toutes portaient sur **une seule + phrase** — la portée de l'invariant de l'AC 5 —, close par le passage d'une liste à une règle. - **2026-09-27** — **validation P4 ciblée** (Sonnet, prompt `25-4-b1-validate-prompt-p4.md`) — **1 MEDIUM, 3 LOW**. MEDIUM : un **cinquième** site de la formulation fausse — la fiche de 25-4-a elle-même (AC 13, `:180`), source des quatre commentaires ; ajouté à T0, rectifiée par une note diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 316bb2f2..ef897371 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -356,7 +356,7 @@ development_status: 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* 25-4-a-residuel-juste: done # 2026-09-27 REVUE CLOSE EN 1 PASSE ; PR #472 OUVERTE (closes #455 #456), attend « merge ». # IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. - 25-4-b1-residuel-aux-agregats: ready-for-dev # 2026-09-27 [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L -> P2 Haiku 0 (repris : 1M) -> P3 ciblee Opus 3M/3L : portee de l'invariant REECRITE PAR UNE REGLE ; issues #473 #474 ouvertes. Branche EMPILEE sur 25-4-a. + 25-4-b1-residuel-aux-agregats: ready-for-dev # 2026-09-27 [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L -> P2 Haiku 0 (repris : 1M) -> P3 ciblee Opus 3M/3L : portee de l'invariant REECRITE PAR UNE REGLE ; issues #473 #474 ouvertes -> P4 1M -> P5 0 : VALIDEE en 5 passes. Signal MED->MED a arbitrer par Guy avant dev. Branche EMPILEE sur 25-4-a. 25-4-b2-residuel-aux-rappels: backlog # [#416, closes] le rappel reclame le reste du (texte, QR, PDF montant initial / deja regle / reste), frais configurables masques a zero ; point ouvert : frais non comptabilises. 25-4-b-residuel-aux-agregats: split # 2026-09-27 DECOUPEE en b1 (agregats) / b2 (rappels) — six modules apres les arbitrages Q1. # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. From bd06e7896fe41ada59dcc634b8b9fa434075e626 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 12:07:41 +0200 Subject: [PATCH 30/34] =?UTF-8?q?docs(25-4-b1):=20pas=20de=20d=C3=A9coupag?= =?UTF-8?q?e=20(Guy),=20dev=20lanc=C3=A9=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../implementation-artifacts/25-4-b1-residuel-aux-agregats.md | 3 ++- _bmad-output/implementation-artifacts/sprint-status.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md index 3f48e94b..3c3ca769 100644 --- a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -1,6 +1,6 @@ # Story 25.4-b1 : Le résiduel aux agrégats — balance âgée et échéancier -Status: ready-for-dev +Status: in-progress **Issue : [#416]** — cette story en livre la partie **agrégats** ; la partie **rappels** est la 25-4-b2. ⛔ **La PR de b1 porte `refs #416`, pas `closes`** : l'issue ne se ferme qu'avec b2. @@ -251,6 +251,7 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba ## Change Log +- **2026-09-27** — Signal MED→MED arbitré par Guy : **pas de découpage** (« continue ainsi »). Dev lancé. - **2026-09-27** — **validation P5 ciblée** (Haiku, diff `5480b4f8..5824c048`, prompt `25-4-b1-validate-prompt-p5.md`) — **0 finding**, preuves recopiées (grep des cinq sites de T0, garde de la 0.12.0) et **concordantes avec les greps de l'orchestrateur**. ⛔ **Boucle close en 5 diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index ef897371..61d48fca 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -356,7 +356,7 @@ development_status: 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* 25-4-a-residuel-juste: done # 2026-09-27 REVUE CLOSE EN 1 PASSE ; PR #472 OUVERTE (closes #455 #456), attend « merge ». # IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. - 25-4-b1-residuel-aux-agregats: ready-for-dev # 2026-09-27 [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L -> P2 Haiku 0 (repris : 1M) -> P3 ciblee Opus 3M/3L : portee de l'invariant REECRITE PAR UNE REGLE ; issues #473 #474 ouvertes -> P4 1M -> P5 0 : VALIDEE en 5 passes. Signal MED->MED a arbitrer par Guy avant dev. Branche EMPILEE sur 25-4-a. + 25-4-b1-residuel-aux-agregats: in-progress # 2026-09-27 [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L -> P2 Haiku 0 (repris : 1M) -> P3 ciblee Opus 3M/3L : portee de l'invariant REECRITE PAR UNE REGLE ; issues #473 #474 ouvertes -> P4 1M -> P5 0 : VALIDEE en 5 passes. Signal MED->MED : pas de decoupage (Guy, 2026-09-27). Branche EMPILEE sur 25-4-a. 25-4-b2-residuel-aux-rappels: backlog # [#416, closes] le rappel reclame le reste du (texte, QR, PDF montant initial / deja regle / reste), frais configurables masques a zero ; point ouvert : frais non comptabilises. 25-4-b-residuel-aux-agregats: split # 2026-09-27 DECOUPEE en b1 (agregats) / b2 (rappels) — six modules apres les arbitrages Q1. # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. From 87cbc8b4b6971b8f9f81026a52d51be8a94cfedf Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 15:14:19 +0200 Subject: [PATCH 31/34] =?UTF-8?q?feat(25-4-b1):=20r=C3=A9siduel=20aux=20ag?= =?UTF-8?q?r=C3=A9gats=20=E2=80=94=20balance=20=C3=A2g=C3=A9e=20et=20?= =?UTF-8?q?=C3=A9ch=C3=A9ancier=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implémentation T0-T6 de la Story 25-4-b1. Gates backend (2491/2491) et frontend (834/834) verts avant l'interruption ; gate E2E (T7) en attente. Commit de sauvegarde après un crash de la station : l'intégrité des fichiers a été vérifiée (git fsck, fins de fichiers, fmt, clippy, check). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 + .../25-4-b1-residuel-aux-agregats.md | 95 ++++++++- crates/kesh-api/src/routes/invoices.rs | 24 ++- .../kesh-api/tests/invoice_echeancier_e2e.rs | 103 +++++++++- crates/kesh-db/src/errors.rs | 7 +- .../src/repositories/invoice_settlements.rs | 28 +++ .../repositories/invoice_settlements_write.rs | 5 +- crates/kesh-db/src/repositories/invoices.rs | 45 +++-- .../tests/invoice_amount_due_parity.rs | 93 ++++++++- crates/kesh-db/tests/invoice_settlement.rs | 5 +- crates/kesh-i18n/locales/de-CH/messages.ftl | 2 + crates/kesh-i18n/locales/en-CH/messages.ftl | 2 + crates/kesh-i18n/locales/fr-CH/messages.ftl | 2 + crates/kesh-i18n/locales/it-CH/messages.ftl | 2 + crates/kesh-report/src/aged_receivables.rs | 59 ++++-- crates/kesh-report/tests/aged_receivables.rs | 183 +++++++++++++++++- docs/manual/fr/user-manual.pdf | Bin 292120 -> 293148 bytes docs/manual/fr/user-manual.tex | 14 +- .../invoices/SettleInvoiceDialog.svelte | 5 +- .../features/invoices/invoice-helpers.test.ts | 23 ++- .../lib/features/invoices/invoice-helpers.ts | 25 +++ .../lib/features/invoices/invoices.types.ts | 7 + frontend/src/lib/shared/i18n-keys.test.ts | 5 +- .../routes/(app)/invoices/[id]/+page.svelte | 20 +- .../(app)/invoices/due-dates/+page.svelte | 32 +-- .../invoices/due-dates/due-dates-page.test.ts | 144 ++++++++++++++ .../tests/e2e/invoices_echeancier.spec.ts | 87 +++++++-- 27 files changed, 912 insertions(+), 107 deletions(-) create mode 100644 frontend/src/routes/(app)/invoices/due-dates/due-dates-page.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d6e160a..8eff70ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ Le contenu est rédigé en français à destination des **fiduciaires, PME, ind - **Un avoir pouvait viser une facture déjà réglée en partie ([#456](https://github.com/guycorbaz/kesh/issues/456)), et le compte du client devenait créditeur sans que rien ne le signale.** L'avoir était refusé sur une facture **payée**, mais pas sur une facture réglée **en partie** — qui, depuis le règlement partiel, n'est « payée » qu'une fois soldée. Comme l'avoir annule tout le montant de la facture, le client se retrouvait avec un crédit du montant déjà encaissé, que Kesh ne sait ni montrer, ni rembourser. L'avoir est désormais refusé dès qu'un règlement existe, avec un message qui dit quoi faire : annuler d'abord le règlement. *(Autoriser l'avoir sur une facture encaissée, avec le remboursement ou l'imputation qu'il appelle, est suivi par [#471](https://github.com/guycorbaz/kesh/issues/471).)* +- **La balance âgée et l'échéancier comptaient le montant facturé, et non ce que le client doit encore ([#416](https://github.com/guycorbaz/kesh/issues/416)).** Depuis le règlement partiel, une facture de 1'000.— réglée à 900.— y pesait toujours 1'000.—. ⚠️ La balance âgée est l'outil qui dit **qui relancer et pour combien** : elle surévaluait chaque créance de tout ce qui avait déjà été encaissé, sans que rien ne le signale, et ne concordait plus avec le compte clients du grand livre. Elle répartit désormais le **reste dû**, dans la tranche de l'échéance de la facture — un acompte ne rajeunit pas la créance. L'échéancier montre une colonne **« Reste dû »** à côté du total, le statut **« Partiellement payée »**, des totaux au reste dû, et son dialogue de règlement se **pré-remplit** au reste dû ; l'export CSV porte la même colonne. Le manuel dit à quelles conditions le total de la balance âgée égale le solde du compte clients. + ### Changed - **Le bouton *Créer un avoir* s'affiche aussi sur une facture payée**, et c'est voulu. Il était masqué sur une facture payée mais pas sur une facture réglée en partie : l'écran ne couvrait que la moitié de la règle. Il reste désormais visible, et Kesh explique le refus dans le dialogue — comme le bouton *Dévalider*. diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md index 3c3ca769..2c4b9234 100644 --- a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -179,7 +179,7 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba ## Tasks / Subtasks -- [ ] **T0 — rectifier la portée de l'état hérité posée par 25-4-a** : **cinq** sites disent +- [x] **T0 — rectifier la portée de l'état hérité posée par 25-4-a** : **cinq** sites disent l'état « avoir après règlement partiel » atteignable **seulement par l'import d'une sauvegarde** — quatre commentaires, `errors.rs:221`, `invoice_settlements_write.rs:333`, `tests/invoice_amount_due_parity.rs:139`, `tests/invoice_settlement.rs:980`, et **leur source**, @@ -188,12 +188,12 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba publiée** accepte cet avoir, une installation **mise à jour sur place** le porte. Écrire « des données antérieures à la 0.12.1, restaurées ou mises à jour ». *(Le manuel de 25-4-a dit déjà « avant la version 0.12.1, ou restaurée » : juste.)* -- [ ] **T1 — la grandeur jointe** (AC 1-2). -- [ ] **T2 — balance âgée** (AC 3-6), dont l'invariant de concordance. -- [ ] **T3 — échéancier, backend** (AC 7-9, AC 11). -- [ ] **T4 — échéancier, frontend** (AC 10), i18n. -- [ ] **T5 — tests et mutations** (AC 12). -- [ ] **T6 — textes** (AC 13). +- [x] **T1 — la grandeur jointe** (AC 1-2). +- [x] **T2 — balance âgée** (AC 3-6), dont l'invariant de concordance. +- [x] **T3 — échéancier, backend** (AC 7-9, AC 11). +- [x] **T4 — échéancier, frontend** (AC 10), i18n. +- [x] **T5 — tests et mutations** (AC 12). +- [x] **T6 — textes** (AC 13). - [ ] **T7 — gates** : backend complet (base remise à zéro), frontend complet, **E2E complet**. ## Dev Notes @@ -243,12 +243,93 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba ### Agent Model Used +Claude Opus 5.5. + ### Debug Log References +- La mutation « reste dû retiré du seul export » a d'abord **échoué à compiler** (argument nommé + `due` devenu inutilisé dans le `format!`) : mutation invalide, refaite en gardant l'argument + (`({due}) * 0 + COALESCE(lt.ttc, 0)`) — elle est alors tuée. +- Le reste dû arrive de l'API à l'échelle du calcul SQL (« 68.1000 ») ; `SettleInvoiceDialog` + le recopiait tel quel dans le champ montant — **aussi sur la fiche**, depuis la 24-2. Le + pré-remplissage passe désormais par `Big(amountDue).toFixed(2)`. +- ⚠️ **Relevé, non corrigé** : dans `invoice_echeancier_e2e.rs`, trois tests préexistants + déstructurent `seed_base` en `(company_id, admin_id)` alors qu'il rend `(admin_id, company_id)` + — ils passent parce que les deux identifiants valent 1 dans une base neuve. Les tests de 25-4-a, + qui avaient copié ce patron, sont rectifiés ici. + ### Completion Notes List +- **T0** : les quatre commentaires de 25-4-a qui bornaient l'état hérité à l'import disent + désormais « données antérieures à la 0.12.1, restaurées ou mises à jour sur place » ; la fiche + de 25-4-a porte sa note datée (validation P4). +- **La grandeur jointe** (AC 1-2) : `amount_due_derived_joins()` (les trois tables dérivées ; une + fonction, `concat!` ne sachant pas assembler des constantes), `INVOICE_AMOUNT_DUE_DERIVED_SQL`, + `INVOICE_AMOUNT_SETTLED_DERIVED_SQL` ; la parité de 25-4-a étendue — reste dû joint = scalaire, + facture par facture, reste dû négatif compris. +- **Balance âgée** (AC 3-6) : les cinq tranches et le total somment le reste dû, tranches sur + `due_date` ; doc-comments (module et `generate`) énoncent la **règle** de concordance et + l'absence de filtre des règlements par `as_of`. +- **Échéancier, serveur** (AC 7-9, 11) : résumé au reste dû ; `InvoiceListItem` porte + `amount_settled` / `amount_due`, projetés par **les deux** SELECT (liste et export) ; `total_ttc` + y vient désormais de `lt.ttc` (permis par les Dev Notes) ; DTO camelCase ; export CSV : colonne + « Reste dû » (clé ×4) et statut « Partiellement payée ». +- **Échéancier, écran** (AC 10) : `paymentStatusOf` partagé par la fiche et l'échéancier ; + colonne « Reste dû » (clé ×4, `sitesTotal` 1750 → 1751 recompté) ; dialogue pré-rempli, garde + client de trop-perçu rendue active ; commentaires qui disaient « TTC (montant dû) » corrigés. +- **Textes** (AC 13) : les **quatre** passages de la § Balance âgée (dont la réserve, par la + règle) et la § Échéancier ; PDF régénéré et **contrôlé aplati** (0 `??`, six phrases de contrôle + présentes, trois formules « TTC » absentes) ; CHANGELOG *Fixed*. Grep du symptôme sur le dépôt : + seule la ligne v0.7.0 publiée du README, légitime. + +**Tests ajoutés** (recomptés, branche de 25-4-a → arbre de travail) : `invoice_amount_due_parity` +4 → 6, `kesh-report/tests/aged_receivables` 4 → 7, `invoice_echeancier_e2e` 13 → 15, Vitest +`invoice-helpers.test.ts` 12 → 16, `due-dates-page.test.ts` 0 → 4 (nouveau). E2E +`invoices_echeancier.spec.ts` : le parcours de la 24-3 **vérifie** le montant pré-rempli au lieu +de le saisir, et un cas « réglée en partie » est ajouté. + +**Mutations** (observées, fichiers restaurés) : + +| Mutation | Tests rouges | +|---|---| +| TTC dans une tranche de la balance âgée | `aged_uses_amount_due_not_ttc` | +| TTC dans le seul total | `aged_total_matches_receivable_ledger`, `aged_uses_amount_due_not_ttc` | +| reste dû retiré du seul export | `list_items_carry_amount_due` | +| TTC dans le résumé de l'échéancier | `due_dates_summary_totals_are_amount_due` | +| `partial` retiré de `paymentStatusOf` | 2 Vitest (helper, page) | +| `amountDue={null}` remis | Vitest « dialogue pré-rempli » | + +### Gates + +- Backend : base remise à zéro, `scripts/test-fast.sh` **2491 / 2491** (2484 + 7). +- Frontend : `check` 0 erreur (27 avertissements), `lint-i18n-ownership` PASS, `test:unit` + **834 / 834** (826 + 8), build OK. +- E2E : EN_ATTENTE + ### File List +| Fichier | Nature | +|---|---| +| `crates/kesh-db/src/repositories/invoice_settlements.rs` | grandeur jointe | +| `crates/kesh-db/src/repositories/invoices.rs` | résumé, `InvoiceListItem`, deux SELECT | +| `crates/kesh-report/src/aged_receivables.rs` | requête, doc | +| `crates/kesh-api/src/routes/invoices.rs` | DTO, export CSV | +| `crates/kesh-i18n/locales/{fr,de,it,en}-CH/messages.ftl` | `echeancier-csv-header-amount-due`, `due-dates-column-amount-due` | +| `crates/kesh-db/src/errors.rs`, `invoice_settlements_write.rs` | T0 | +| `crates/kesh-db/tests/invoice_amount_due_parity.rs` | parité étendue, résumé, listes ; T0 | +| `crates/kesh-db/tests/invoice_settlement.rs` | T0 | +| `crates/kesh-report/tests/aged_receivables.rs` | 3 tests | +| `crates/kesh-api/tests/invoice_echeancier_e2e.rs` | 2 e2e ; ordre de `seed_base` rectifié (tests 25-4-a) | +| `frontend/src/lib/features/invoices/invoice-helpers.ts` / `.test.ts` | `paymentStatusOf` | +| `frontend/src/lib/features/invoices/invoices.types.ts` | `amountSettled`, `amountDue` | +| `frontend/src/lib/features/invoices/SettleInvoiceDialog.svelte` | pré-remplissage au centime | +| `frontend/src/routes/(app)/invoices/[id]/+page.svelte` | statut partagé | +| `frontend/src/routes/(app)/invoices/due-dates/+page.svelte` / `due-dates-page.test.ts` | colonne, statut, dialogue | +| `frontend/src/lib/shared/i18n-keys.test.ts` | `sitesTotal` 1751 | +| `frontend/tests/e2e/invoices_echeancier.spec.ts` | pré-remplissage, cas partiel | +| `docs/manual/fr/user-manual.tex` / `.pdf` | balance âgée, échéancier | +| `CHANGELOG.md` | *Fixed* | + ## Change Log - **2026-09-27** — Signal MED→MED arbitré par Guy : **pas de découpage** (« continue ainsi »). Dev lancé. diff --git a/crates/kesh-api/src/routes/invoices.rs b/crates/kesh-api/src/routes/invoices.rs index 913684d7..603d4c33 100644 --- a/crates/kesh-api/src/routes/invoices.rs +++ b/crates/kesh-api/src/routes/invoices.rs @@ -343,6 +343,11 @@ pub struct InvoiceListItemResponse { pub total_amount: Decimal, /// TTC canonique (#246, Story 21-2a) — colonne SQL calculée de la liste. pub total_ttc: Decimal, + /// Story 25-4-b1 (#416) — total réglé et **reste dû**, **toujours + /// calculés** dans une liste (forme jointe) : jamais `null` ici, à la + /// différence de la fiche où `None` veut dire « non calculé ». + pub amount_settled: Decimal, + pub amount_due: Decimal, pub paid_at: Option, /// Story 21-6a (D10) — suspension des rappels, alimente le badge /// « suspendu » de la liste des factures et le filtre `paused`. @@ -379,6 +384,8 @@ impl From for InvoiceListItemResponse { payment_terms: i.payment_terms, total_amount: i.total_amount, total_ttc: i.total_ttc, + amount_settled: i.amount_settled, + amount_due: i.amount_due, paid_at: i.paid_at, dunning_paused_at: i.dunning_paused_at, dunning_paused_note: i.dunning_paused_note, @@ -1332,12 +1339,13 @@ pub async fn cancel_invoice_settlement_handler( } /// Clés FTL des en-têtes CSV (locale = `companies.accounting_language`). -const CSV_HEADER_KEYS: [&str; 7] = [ +const CSV_HEADER_KEYS: [&str; 8] = [ "echeancier-csv-header-number", "echeancier-csv-header-date", "echeancier-csv-header-due-date", "echeancier-csv-header-contact", "echeancier-csv-header-total", + "echeancier-csv-header-amount-due", "echeancier-csv-header-payment-status", "echeancier-csv-header-paid-at", ]; @@ -1347,12 +1355,13 @@ const CSV_HEADER_KEYS: [&str; 7] = [ // interdit la recopie. Comportement identique ; ses tests unitaires, qui // n'existaient pas, ont été écrits à l'extraction (`util.rs`). -const CSV_HEADER_FALLBACKS: [&str; 7] = [ +const CSV_HEADER_FALLBACKS: [&str; 8] = [ "Numéro", "Date", "Date d'échéance", "Client", "Total", + "Reste dû", "Statut paiement", "Date paiement", ]; @@ -1440,8 +1449,12 @@ pub async fn export_due_dates_csv_handler( for inv in rows { // B3 (review pass 1 G2 B) : utilise le helper centralisé. + // Story 25-4-b1 (#416) : « partiellement payée » — même règle que la + // fiche : pas de `paid_at`, mais un règlement. let payment_status_key = if inv.paid_at.is_some() { "payment-status-paid" + } else if inv.amount_settled > Decimal::ZERO { + "payment-status-partial" } else if is_invoice_overdue(&inv.status, inv.paid_at, inv.due_date, today) { "payment-status-overdue" } else { @@ -1460,9 +1473,12 @@ pub async fn export_due_dates_csv_handler( format_date(&inv.date), inv.due_date.as_ref().map(format_date).unwrap_or_default(), csv_sanitize(inv.contact_name.clone()), - // #246 (Story 21-2a) : colonne Total = TTC (montant dû), - // cohérente avec les KPI du summary — pas le HT comptable. + // #246 (Story 21-2a) : colonne Total = TTC émis — pas le HT + // comptable. ⚠️ Ce n'est PAS le montant dû depuis la 24-2 : la + // colonne suivante le porte (Story 25-4-b1), cohérente avec les + // KPI du résumé. format_money(&inv.total_ttc), + format_money(&inv.amount_due), // B19 (review pass 2 G2 B) : défense en profondeur — la valeur // vient d'un fichier FTL contrôlé, mais une compromission // (clé locale altérée) ne doit pas ouvrir un vecteur d'injection. diff --git a/crates/kesh-api/tests/invoice_echeancier_e2e.rs b/crates/kesh-api/tests/invoice_echeancier_e2e.rs index 99cffc3f..b568b036 100644 --- a/crates/kesh-api/tests/invoice_echeancier_e2e.rs +++ b/crates/kesh-api/tests/invoice_echeancier_e2e.rs @@ -756,7 +756,7 @@ fn montant(v: &serde_json::Value, champ: &str) -> rust_decimal::Decimal { /// — ouverte aux clés API en lecture — rend un reste dû **nul**, et non la TVA. #[sqlx::test(migrations = "../kesh-db/test-schema")] async fn get_credited_invoice_reports_zero_amount_due(pool: MySqlPool) { - let (company_id, admin_id) = seed_base(&pool).await; + let (admin_id, company_id) = seed_base(&pool).await; let contact_id = seed_contact(&pool, company_id, admin_id).await; let (id, _v) = create_validated_invoice( &pool, @@ -795,7 +795,7 @@ async fn get_credited_invoice_reports_zero_amount_due(pool: MySqlPool) { /// son code et un message qui dit quoi faire. #[sqlx::test(migrations = "../kesh-db/test-schema")] async fn credit_note_on_partially_settled_invoice_is_409(pool: MySqlPool) { - let (company_id, admin_id) = seed_base(&pool).await; + let (admin_id, company_id) = seed_base(&pool).await; let contact_id = seed_contact(&pool, company_id, admin_id).await; let (id, _v) = create_validated_invoice( &pool, @@ -841,3 +841,102 @@ async fn credit_note_on_partially_settled_invoice_is_409(pool: MySqlPool) { assert_eq!(v["error"]["details"]["invoiceId"], id); assert!(v["error"]["details"]["settlementId"].as_i64().is_some()); } + +// --- Story 25-4-b1 (#416) — l'échéancier porte le reste dû, à la frontière HTTP -- + +/// Une facture de 108.10 (100.— à 8,1 %) réglée de 40.— en espèces, par HTTP. +async fn partially_settled_invoice( + pool: &MySqlPool, + app: &TestApp, + token: &str, + admin_id: i64, + company_id: i64, +) -> i64 { + let contact_id = seed_contact(pool, company_id, admin_id).await; + let (id, _v) = create_validated_invoice( + pool, + company_id, + contact_id, + admin_id, + NaiveDate::from_ymd_opt(2026, 4, 1).unwrap(), + NaiveDate::from_ymd_opt(2026, 4, 30).unwrap(), + dec!(100.00), + ) + .await; + let caisse = caisse_id(pool, company_id).await; + let settle = app + .client + .post(app.url(&format!("/api/v1/invoices/{id}/settlements"))) + .header("Authorization", format!("Bearer {token}")) + .json(&json!({ + "settlementType": "internal_account", + "accountId": caisse, + "amount": "40.00", + "settledOn": "2026-04-15" + })) + .send() + .await + .unwrap(); + assert_eq!(settle.status(), 200); + id +} + +/// AC 8-9 — la liste de l'échéancier rend `amountSettled` et `amountDue` par +/// ligne, et le résumé somme le reste dû. +#[sqlx::test(migrations = "../kesh-db/test-schema")] +async fn due_dates_list_carries_amount_due(pool: MySqlPool) { + let (admin_id, company_id) = seed_base(&pool).await; + let app = spawn_app(pool.clone()).await; + let token = login(&app).await; + let id = partially_settled_invoice(&pool, &app, &token, admin_id, company_id).await; + + let resp = app + .client + .get(app.url("/api/v1/invoices/due-dates")) + .header("Authorization", format!("Bearer {token}")) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + let v: serde_json::Value = resp.json().await.unwrap(); + let item = v["items"] + .as_array() + .unwrap() + .iter() + .find(|i| i["id"] == id) + .unwrap_or_else(|| panic!("facture {id} absente : {v:?}")); + assert_eq!(montant(item, "totalTtc"), dec!(108.10)); + assert_eq!(montant(item, "amountSettled"), dec!(40.00)); + assert_eq!(montant(item, "amountDue"), dec!(68.10)); + assert_eq!( + montant(&v["summary"], "unpaidTotal"), + dec!(68.10), + "résumé : {v:?}" + ); +} + +/// AC 11 — l'export CSV porte une colonne « Reste dû » et le statut « partiellement +/// payée ». +#[sqlx::test(migrations = "../kesh-db/test-schema")] +async fn due_dates_csv_has_amount_due_and_partial_status(pool: MySqlPool) { + let (admin_id, company_id) = seed_base(&pool).await; + let app = spawn_app(pool.clone()).await; + let token = login(&app).await; + let _id = partially_settled_invoice(&pool, &app, &token, admin_id, company_id).await; + + let resp = app + .client + .get(app.url("/api/v1/invoices/due-dates/export.csv")) + .header("Authorization", format!("Bearer {token}")) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + let text = String::from_utf8_lossy(&resp.bytes().await.unwrap()).to_string(); + let mut lines = text.trim_start_matches('\u{feff}').lines(); + let header = lines.next().unwrap(); + assert!(header.contains(";Total;Reste dû;"), "en-tête : {header}"); + let row = lines.next().expect("une ligne"); + assert!(row.contains(";108.10;68.10;"), "ligne : {row}"); + assert!(row.contains("Partiellement payée"), "statut : {row}"); +} diff --git a/crates/kesh-db/src/errors.rs b/crates/kesh-db/src/errors.rs index 402d0b39..c50f64a6 100644 --- a/crates/kesh-db/src/errors.rs +++ b/crates/kesh-db/src/errors.rs @@ -217,8 +217,11 @@ pub enum SettlementCancelBlocker { /// production que de l'avoir (`credit_notes.rs`). /// /// ⚠️ **État hérité depuis la Story 25-4-a (#456)** : un avoir est refusé - /// sur une facture réglée, même en partie. L'état n'est plus atteignable - /// que par l'**import d'une sauvegarde antérieure** — d'où ce motif, gardé. + /// sur une facture réglée, même en partie. L'état n'est plus produit par + /// l'application, mais des **données antérieures à la 0.12.1** peuvent le + /// porter — la 0.12.0 publiée acceptait cet avoir —, qu'elles soient + /// **restaurées** d'une sauvegarde ou **mises à jour sur place**. D'où ce + /// motif, gardé. InvoiceCredited, /// Tête **fournisseur** (Story 25-3-a-2) : la facture n'est pas `paid` — il /// n'y a pas de règlement à annuler. ⚠️ Coupe court par construction : une diff --git a/crates/kesh-db/src/repositories/invoice_settlements.rs b/crates/kesh-db/src/repositories/invoice_settlements.rs index 0261a9b0..8dd1a248 100644 --- a/crates/kesh-db/src/repositories/invoice_settlements.rs +++ b/crates/kesh-db/src/repositories/invoice_settlements.rs @@ -80,6 +80,34 @@ pub const INVOICE_CREDITED_DERIVED_JOIN_SQL: &str = concat!( WHERE cn.status = 'issued' GROUP BY cn.invoice_id) cnt ON cnt.invoice_id = i.id" ); +/// Les trois tables dérivées du **reste dû sous forme jointe** — TTC (`lt`), +/// avoir émis (`cnt`), réglé (`st`) — à placer après `FROM invoices i …` +/// (Story 25-4-b1, #416). **Prérequis : alias `i` sur `invoices`.** +/// +/// ⛔ Forme des **listes et agrégats** : la forme scalaire +/// ([`amount_due`]) y serait réévaluée par ligne — un N+1 déguisé (24-2, D3). +/// Les deux formes sont tenues d'accord par `tests/invoice_amount_due_parity.rs`. +/// +/// Une fonction et non une constante : les trois jointures sont elles-mêmes des +/// constantes, que `concat!` ne sait pas assembler. +pub fn amount_due_derived_joins() -> String { + format!( + "{} {INVOICE_CREDITED_DERIVED_JOIN_SQL} {INVOICE_SETTLED_DERIVED_JOIN_SQL}", + crate::repositories::invoices::INVOICE_TTC_DERIVED_JOIN_SQL + ) +} + +/// Le **reste dû** d'une ligne, sur les tables de +/// [`amount_due_derived_joins`] : `TTC − avoir émis − Σ règlements`, trois +/// termes TTC. Miroir exact de [`amount_due`]. ⛔ Ne pas le réécrire à la main +/// dans une requête : c'est ainsi que les agrégats ont sommé le TTC pendant +/// un mois après la 24-2 (#416). +pub const INVOICE_AMOUNT_DUE_DERIVED_SQL: &str = + "(COALESCE(lt.ttc, 0) - COALESCE(cnt.credited, 0) - COALESCE(st.settled, 0))"; + +/// Le **total réglé** d'une ligne, sur les tables de [`amount_due_derived_joins`]. +pub const INVOICE_AMOUNT_SETTLED_DERIVED_SQL: &str = "COALESCE(st.settled, 0)"; + /// Enregistre un règlement dans la transaction courante. /// /// ⚠️ **Ne pose PAS `paid_at`** : c'est à l'appelant de le faire, et seulement diff --git a/crates/kesh-db/src/repositories/invoice_settlements_write.rs b/crates/kesh-db/src/repositories/invoice_settlements_write.rs index 415b747c..02d4185d 100644 --- a/crates/kesh-db/src/repositories/invoice_settlements_write.rs +++ b/crates/kesh-db/src/repositories/invoice_settlements_write.rs @@ -329,8 +329,9 @@ pub async fn settlement_cancel_blocker_unlinking( // dévalidation refuse une facture réglée, et `cancelled` ne naît en // production que de l'avoir. Hors `validated`, c'est donc un avoir. // ⚠️ Depuis la Story 25-4-a (#456), l'avoir est refusé sur une facture - // réglée : ce rang ne se déclenche plus que sur un état HÉRITÉ, ramené par - // l'import d'une sauvegarde antérieure. + // réglée : ce rang ne se déclenche plus que sur un état HÉRITÉ — des données + // antérieures à la 0.12.1 (la 0.12.0 acceptait cet avoir), restaurées ou + // mises à jour sur place. if status != "validated" { return Ok(Some((SettlementCancelBlocker::InvoiceCredited, None, None))); } diff --git a/crates/kesh-db/src/repositories/invoices.rs b/crates/kesh-db/src/repositories/invoices.rs index 526bbc5a..e131e78b 100644 --- a/crates/kesh-db/src/repositories/invoices.rs +++ b/crates/kesh-db/src/repositories/invoices.rs @@ -32,6 +32,9 @@ use crate::entities::audit_log::NewAuditLogEntry; use crate::entities::invoice::{Invoice, InvoiceLine, InvoiceUpdate, NewInvoice, NewInvoiceLine}; use crate::errors::{DbError, UnvalidationBlocker, map_db_error}; use crate::repositories::audit_log; +use crate::repositories::invoice_settlements::{ + INVOICE_AMOUNT_DUE_DERIVED_SQL, INVOICE_AMOUNT_SETTLED_DERIVED_SQL, amount_due_derived_joins, +}; use crate::repositories::journal_entries; use crate::util::search::{escape_boolean_ft, escape_like}; @@ -254,9 +257,16 @@ pub struct InvoiceListItem { pub due_date: Option, pub payment_terms: Option, pub total_amount: Decimal, - /// TTC canonique (#246) — colonne SQL calculée (`INVOICE_TTC_SUBQUERY_SQL`), - /// la projection ne charge pas les lignes. + /// TTC canonique (#246) — colonne SQL calculée (table dérivée `lt` depuis + /// la Story 25-4-b1 ; `invoice_ttc_parity.rs` la tient égale à la forme + /// corrélée), la projection ne charge pas les lignes. pub total_ttc: Decimal, + /// Story 25-4-b1 (#416) — total déjà réglé, forme jointe. + pub amount_settled: Decimal, + /// Story 25-4-b1 (#416) — **reste dû** : TTC − avoir émis − Σ règlements, + /// forme jointe (`INVOICE_AMOUNT_DUE_DERIVED_SQL`). Peut être négatif + /// (trop-perçu hérité) : jamais écrêté. + pub amount_due: Decimal, pub paid_at: Option, /// Story 21-6a (D10) — suspension des rappels, exposée en liste (badge). /// @@ -816,10 +826,14 @@ pub async fn list_by_company_paginated( let mut items_qb: QueryBuilder = QueryBuilder::new(format!( "SELECT i.id, i.company_id, i.contact_id, c.name AS contact_name, \ i.invoice_number, i.status, i.date, i.due_date, i.payment_terms, \ - i.total_amount, {INVOICE_TTC_SUBQUERY_SQL} AS total_ttc, \ + i.total_amount, COALESCE(lt.ttc, 0) AS total_ttc, \ + {settled} AS amount_settled, {due} AS amount_due, \ i.paid_at, i.dunning_paused_at, i.dunning_paused_note, \ i.version, i.created_at, i.updated_at \ - FROM invoices i INNER JOIN contacts c ON c.id = i.contact_id", + FROM invoices i INNER JOIN contacts c ON c.id = i.contact_id {joins}", + settled = INVOICE_AMOUNT_SETTLED_DERIVED_SQL, + due = INVOICE_AMOUNT_DUE_DERIVED_SQL, + joins = amount_due_derived_joins(), )); push_where_clauses(&mut items_qb, company_id, &query); items_qb.push(" ORDER BY "); @@ -887,16 +901,19 @@ pub async fn due_dates_summary( let mut qb: QueryBuilder = QueryBuilder::new(format!( // CAST AS SIGNED pour forcer BIGINT : MariaDB SUM(CASE…) retourne // DECIMAL par défaut, incompatible avec Rust i64. - // #246 (Story 21-2a) : totaux en TTC via la table dérivée `lt` - // (INVOICE_TTC_DERIVED_JOIN_SQL) — les KPI de l'échéancier sont des - // montants dus, pas des HT comptables. + // Story 25-4-b1 (#416) : les KPI de l'échéancier sont des RESTES DUS — + // TTC − avoir émis − Σ règlements, forme jointe — et non plus le TTC + // émis (#246, 21-2a), qui comptait une facture de 1 000.— réglée à + // 900.— pour 1 000.—. "SELECT \ COUNT(*) AS unpaid_count, \ - COALESCE(SUM(COALESCE(lt.ttc, 0)), CAST(0 AS DECIMAL(19,4))) AS unpaid_total, \ + COALESCE(SUM({due}), CAST(0 AS DECIMAL(19,4))) AS unpaid_total, \ CAST(COALESCE(SUM(CASE WHEN i.due_date < UTC_DATE() THEN 1 ELSE 0 END), 0) AS SIGNED) AS overdue_count, \ - COALESCE(SUM(CASE WHEN i.due_date < UTC_DATE() THEN COALESCE(lt.ttc, 0) ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS overdue_total \ + COALESCE(SUM(CASE WHEN i.due_date < UTC_DATE() THEN {due} ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS overdue_total \ FROM invoices i INNER JOIN contacts c ON c.id = i.contact_id \ - {INVOICE_TTC_DERIVED_JOIN_SQL}", + {joins}", + due = INVOICE_AMOUNT_DUE_DERIVED_SQL, + joins = amount_due_derived_joins(), )); qb.push(" WHERE i.company_id = "); qb.push_bind(company_id); @@ -2456,10 +2473,14 @@ pub async fn list_for_export( let mut items_qb: QueryBuilder = QueryBuilder::new(format!( "SELECT i.id, i.company_id, i.contact_id, c.name AS contact_name, \ i.invoice_number, i.status, i.date, i.due_date, i.payment_terms, \ - i.total_amount, {INVOICE_TTC_SUBQUERY_SQL} AS total_ttc, \ + i.total_amount, COALESCE(lt.ttc, 0) AS total_ttc, \ + {settled} AS amount_settled, {due} AS amount_due, \ i.paid_at, i.dunning_paused_at, i.dunning_paused_note, \ i.version, i.created_at, i.updated_at \ - FROM invoices i INNER JOIN contacts c ON c.id = i.contact_id", + FROM invoices i INNER JOIN contacts c ON c.id = i.contact_id {joins}", + settled = INVOICE_AMOUNT_SETTLED_DERIVED_SQL, + due = INVOICE_AMOUNT_DUE_DERIVED_SQL, + joins = amount_due_derived_joins(), )); push_where_clauses(&mut items_qb, company_id, query); // P13 (review pass 3 A) : défense en profondeur — l'export ne doit diff --git a/crates/kesh-db/tests/invoice_amount_due_parity.rs b/crates/kesh-db/tests/invoice_amount_due_parity.rs index 79523b62..bea59d82 100644 --- a/crates/kesh-db/tests/invoice_amount_due_parity.rs +++ b/crates/kesh-db/tests/invoice_amount_due_parity.rs @@ -19,8 +19,9 @@ use chrono::NaiveDate; use kesh_db::entities::contact::{ContactType, NewContact}; use kesh_db::entities::{NewCreditNote, NewInvoice, NewInvoiceLine, SettlementChoice}; use kesh_db::repositories::invoice_settlements::{ - INVOICE_CREDITED_DERIVED_JOIN_SQL, INVOICE_CREDITED_SUBQUERY_SQL, - INVOICE_SETTLED_DERIVED_JOIN_SQL, INVOICE_SETTLED_SUBQUERY_SQL, amount_due, + INVOICE_AMOUNT_DUE_DERIVED_SQL, INVOICE_CREDITED_DERIVED_JOIN_SQL, + INVOICE_CREDITED_SUBQUERY_SQL, INVOICE_SETTLED_DERIVED_JOIN_SQL, INVOICE_SETTLED_SUBQUERY_SQL, + amount_due, amount_due_derived_joins, }; use kesh_db::repositories::{contacts, credit_notes, invoice_settlements_write, invoices}; use kesh_db::test_fixtures::{SeededCompany, seed_accounting_company}; @@ -136,7 +137,8 @@ async fn credit( } /// L'état hérité **règlement puis avoir**, que 25-4-a rend inatteignable par -/// l'application mais qu'un `.keshbackup` v0.12.0 peut ramener. +/// l'application, mais que des données antérieures à la 0.12.1 peuvent porter — +/// la 0.12.0 publiée acceptait cet avoir —, restaurées ou mises à jour sur place. /// /// Gabarit « détacher, créditer, rattacher » (AC 13) : les deux écritures /// viennent des **vrais chemins** ; seul le rattachement de la ligne de @@ -308,4 +310,89 @@ async fn settled_and_credited_forms_are_at_parity(pool: MySqlPool) { Decimal::ZERO, "un brouillon n'éteint rien" ); + + // Story 25-4-b1 (AC 2) — le reste dû sous forme JOINTE, celle des listes et + // agrégats, égale la forme scalaire facture par facture. + let joined: Vec<(i64, Decimal)> = sqlx::query_as(&format!( + "SELECT i.id, {INVOICE_AMOUNT_DUE_DERIVED_SQL} FROM invoices i {} \ + WHERE i.company_id = ? ORDER BY i.id", + amount_due_derived_joins() + )) + .bind(seeded.company_id) + .fetch_all(&pool) + .await + .expect("reste dû joint"); + assert_eq!(joined.len(), rows.len()); + for (id, due) in &joined { + assert_eq!( + *due, + amount_due(&pool, *id).await.unwrap(), + "reste dû joint ≠ scalaire, facture {id}" + ); + } + // Anti-vacuité : le jeu porte des restes dus distincts, dont un négatif. + assert!( + joined.iter().any(|(_, d)| *d < Decimal::ZERO), + "état hérité : reste dû négatif" + ); + assert!(joined.iter().any(|(_, d)| *d > Decimal::ZERO)); +} + +// ─── Story 25-4-b1 (#416) — l'échéancier porte le reste dû ──────────────────── + +fn unpaid_query() -> invoices::InvoiceListQuery { + invoices::InvoiceListQuery { + status: Some("validated".into()), + payment_status: Some(invoices::PaymentStatusFilter::Unpaid), + limit: 100, + ..Default::default() + } +} + +/// AC 7 — les totaux du résumé somment le reste dû : 108.10 réglé 40 pèse 68.10. +#[sqlx::test(migrations = "./test-schema")] +async fn due_dates_summary_totals_are_amount_due(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let contact = make_contact(&pool, &seeded).await; + let partial = validated(&pool, &seeded, contact, &[(dec!(8.10), dec!(100.00))]).await; + settle(&pool, &seeded, partial, dec!(40.00)).await; + let _open = validated(&pool, &seeded, contact, &[(dec!(2.60), dec!(50.00))]).await; + + let summary = invoices::due_dates_summary(&pool, seeded.company_id, &unpaid_query()) + .await + .unwrap(); + assert_eq!(summary.unpaid_count, 2); + // 68.10 + 51.30 + assert_eq!( + summary.unpaid_total, + dec!(119.40), + "reste dû, pas 159.40 de TTC" + ); +} + +/// AC 8 — les DEUX SELECT qui désérialisent `InvoiceListItem` portent le réglé +/// et le reste dû : la liste paginée ET l'export. +#[sqlx::test(migrations = "./test-schema")] +async fn list_items_carry_amount_due(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let contact = make_contact(&pool, &seeded).await; + let partial = validated(&pool, &seeded, contact, &[(dec!(8.10), dec!(100.00))]).await; + settle(&pool, &seeded, partial, dec!(40.00)).await; + let open = validated(&pool, &seeded, contact, &[(dec!(8.10), dec!(10.00))]).await; + + let page = invoices::list_by_company_paginated(&pool, seeded.company_id, unpaid_query()) + .await + .unwrap(); + let (export, _) = invoices::list_for_export(&pool, seeded.company_id, &unpaid_query(), 100) + .await + .unwrap(); + for (surface, items) in [("liste", &page.items), ("export", &export)] { + let p = items.iter().find(|i| i.id == partial).expect(surface); + assert_eq!(p.total_ttc, dec!(108.10), "{surface} : TTC"); + assert_eq!(p.amount_settled, dec!(40.00), "{surface} : réglé"); + assert_eq!(p.amount_due, dec!(68.10), "{surface} : reste dû"); + let o = items.iter().find(|i| i.id == open).expect(surface); + assert_eq!(o.amount_settled, Decimal::ZERO, "{surface}"); + assert_eq!(o.amount_due, dec!(10.81), "{surface}"); + } } diff --git a/crates/kesh-db/tests/invoice_settlement.rs b/crates/kesh-db/tests/invoice_settlement.rs index f14d1f21..ba67d009 100644 --- a/crates/kesh-db/tests/invoice_settlement.rs +++ b/crates/kesh-db/tests/invoice_settlement.rs @@ -976,8 +976,9 @@ async fn monter(pool: &MySqlPool, motifs: &[SettlementCancelBlocker]) -> (Seeded if motifs.contains(&SettlementCancelBlocker::InvoiceCredited) { // ⚠️ **État HÉRITÉ, plus atteignable par l'application** depuis la // Story 25-4-a (#456) : un avoir est désormais refusé sur une facture - // réglée, même en partie. Il reste possible par l'**import d'une - // sauvegarde antérieure** — c'est ce qui justifie de garder le motif + // réglée, même en partie. Des **données antérieures à la 0.12.1** peuvent + // encore le porter — la 0.12.0 publiée acceptait cet avoir —, restaurées + // ou mises à jour sur place : c'est ce qui justifie de garder le motif // `InvoiceCredited` et ce montage. // // Gabarit « détacher, créditer, rattacher » : les deux écritures diff --git a/crates/kesh-i18n/locales/de-CH/messages.ftl b/crates/kesh-i18n/locales/de-CH/messages.ftl index 6c151ed9..883ca037 100644 --- a/crates/kesh-i18n/locales/de-CH/messages.ftl +++ b/crates/kesh-i18n/locales/de-CH/messages.ftl @@ -649,6 +649,7 @@ due-dates-column-date = Datum due-dates-column-due-date = Fälligkeit due-dates-column-contact = Kunde due-dates-column-total = Total +due-dates-column-amount-due = Offener Betrag due-dates-column-payment-status = Status due-dates-column-paid-at = Bezahlt am due-dates-export-button = CSV exportieren @@ -728,6 +729,7 @@ echeancier-csv-header-date = Datum echeancier-csv-header-due-date = Fälligkeitsdatum echeancier-csv-header-contact = Kunde echeancier-csv-header-total = Total +echeancier-csv-header-amount-due = Offener Betrag echeancier-csv-header-payment-status = Zahlungsstatus echeancier-csv-header-paid-at = Zahlungsdatum echeancier-export-error-too-large = Zu viele Ergebnisse (> { $limit }). Bitte die Filter verfeinern (z. B. Datumsbereich oder Zahlungsstatus), bevor der Export erneut gestartet wird. diff --git a/crates/kesh-i18n/locales/en-CH/messages.ftl b/crates/kesh-i18n/locales/en-CH/messages.ftl index 92ffd928..7842a8f6 100644 --- a/crates/kesh-i18n/locales/en-CH/messages.ftl +++ b/crates/kesh-i18n/locales/en-CH/messages.ftl @@ -649,6 +649,7 @@ due-dates-column-date = Date due-dates-column-due-date = Due date due-dates-column-contact = Customer due-dates-column-total = Total +due-dates-column-amount-due = Amount due due-dates-column-payment-status = Status due-dates-column-paid-at = Paid on due-dates-export-button = Export CSV @@ -728,6 +729,7 @@ echeancier-csv-header-date = Date echeancier-csv-header-due-date = Due date echeancier-csv-header-contact = Customer echeancier-csv-header-total = Total +echeancier-csv-header-amount-due = Amount due echeancier-csv-header-payment-status = Payment status echeancier-csv-header-paid-at = Paid on echeancier-export-error-too-large = Too many results (> { $limit }). Please refine your filters (e.g. date range or payment status) before exporting. diff --git a/crates/kesh-i18n/locales/fr-CH/messages.ftl b/crates/kesh-i18n/locales/fr-CH/messages.ftl index 9e47a49e..efec23fe 100644 --- a/crates/kesh-i18n/locales/fr-CH/messages.ftl +++ b/crates/kesh-i18n/locales/fr-CH/messages.ftl @@ -682,6 +682,7 @@ due-dates-column-date = Date due-dates-column-due-date = Échéance due-dates-column-contact = Client due-dates-column-total = Total +due-dates-column-amount-due = Reste dû due-dates-column-payment-status = Statut due-dates-column-paid-at = Payée le due-dates-export-button = Exporter CSV @@ -764,6 +765,7 @@ echeancier-csv-header-date = Date echeancier-csv-header-due-date = Date d'échéance echeancier-csv-header-contact = Client echeancier-csv-header-total = Total +echeancier-csv-header-amount-due = Reste dû echeancier-csv-header-payment-status = Statut paiement echeancier-csv-header-paid-at = Date paiement echeancier-export-error-too-large = Trop de résultats (> { $limit }). Veuillez affiner vos filtres (par ex. plage de dates ou statut de paiement) avant de relancer l'export. diff --git a/crates/kesh-i18n/locales/it-CH/messages.ftl b/crates/kesh-i18n/locales/it-CH/messages.ftl index 06f35274..627734b4 100644 --- a/crates/kesh-i18n/locales/it-CH/messages.ftl +++ b/crates/kesh-i18n/locales/it-CH/messages.ftl @@ -649,6 +649,7 @@ due-dates-column-date = Data due-dates-column-due-date = Scadenza due-dates-column-contact = Cliente due-dates-column-total = Totale +due-dates-column-amount-due = Saldo dovuto due-dates-column-payment-status = Stato due-dates-column-paid-at = Pagata il due-dates-export-button = Esporta CSV @@ -728,6 +729,7 @@ echeancier-csv-header-date = Data echeancier-csv-header-due-date = Scadenza echeancier-csv-header-contact = Cliente echeancier-csv-header-total = Totale +echeancier-csv-header-amount-due = Saldo dovuto echeancier-csv-header-payment-status = Stato pagamento echeancier-csv-header-paid-at = Data pagamento echeancier-export-error-too-large = Troppi risultati (> { $limit }). Affinare i filtri (intervallo date o stato di pagamento) prima di esportare. diff --git a/crates/kesh-report/src/aged_receivables.rs b/crates/kesh-report/src/aged_receivables.rs index 96f70093..aadf7478 100644 --- a/crates/kesh-report/src/aged_receivables.rs +++ b/crates/kesh-report/src/aged_receivables.rs @@ -1,16 +1,26 @@ //! Balance âgée des créances clients (Story 21-7, #231, §E items 23/25). //! -//! Répartit l'encours débiteur **TTC** (postes ouverts = factures validées non -//! payées) par contact et par tranche d'ancienneté relative à une date de +//! Répartit l'encours débiteur — le **reste dû** de chaque facture validée non +//! soldée, après règlements partiels et avoirs (Story 25-4-b1, #416) — par contact et par tranche d'ancienneté relative à une date de //! référence `as_of` : //! //! `Non échu | 1-30 | 31-60 | 61-90 | 90+` jours de retard (`days = as_of − due_date`). //! //! Invariants : -//! - **TTC dérivé** via `INVOICE_TTC_DERIVED_JOIN_SQL` (#246, 21-2) — jamais -//! `total_amount` (qui est le HT). L'arrondi TVA est fait PAR LIGNE dans la -//! table dérivée (DC7), asservi au helper Rust `invoice_total_ttc` par un test -//! de parité (`tests/aged_receivables.rs`). +//! - **Reste dû dérivé** (`INVOICE_AMOUNT_DUE_DERIVED_SQL` sur +//! `amount_due_derived_joins`, Story 25-4-b1) : TTC − avoir émis − Σ +//! règlements, jamais le TTC seul ni `total_amount` (le HT). Le TTC y est +//! arrondi PAR LIGNE (DC7), asservi au helper Rust `invoice_total_ttc` +//! (`tests/aged_receivables.rs`) ; le reste dû joint, à `amount_due` +//! (`kesh-db/tests/invoice_amount_due_parity.rs`). +//! - ⛔ **Concordance avec le grand livre** — le total égale le solde du compte +//! débiteurs **si et seulement si** ce compte n'est mouvementé que par les +//! écritures de vente, de règlement client et d'avoir de factures validées +//! (contre-passations comprises), datées au plus tard à `as_of`, sans que le +//! compte débiteurs ait changé dans les réglages. Tout autre mouvement — solde +//! d'ouverture, écriture au journal, rapprochement hors facture, compensation +//! fournisseur, données antérieures à la 0.12.1 — l'en écarte, et la balance +//! âgée ne le montre pas (règle, non liste : `tests/aged_receivables.rs`). //! - **Factures suspendues INCLUSES** (D10) : le prédicat postes ouverts //! n'exclut PAS `dunning_paused_at` — une facture suspendue reste dans la //! balance âgée (elle ne sort que de la liste « à rappeler »). @@ -18,7 +28,9 @@ //! - Totaux généraux **sommés en Rust** (patron `balance_sheet`), pas en SQL. use chrono::NaiveDate; -use kesh_db::repositories::invoices::INVOICE_TTC_DERIVED_JOIN_SQL; +use kesh_db::repositories::invoice_settlements::{ + INVOICE_AMOUNT_DUE_DERIVED_SQL, amount_due_derived_joins, +}; use rust_decimal::Decimal; use serde::Serialize; use sqlx::MySqlPool; @@ -95,8 +107,9 @@ struct AgedRowSql { /// /// Une seule requête agrégée groupée par contact. Buckets calculés via /// `DATEDIFF(as_of, due_date)` ; `due_date IS NULL` → « Non échu ». Montants = -/// TTC dérivé (`INVOICE_TTC_DERIVED_JOIN_SQL`, alias `lt`). Scoping multi-tenant -/// obligatoire (`i.company_id = ?`). +/// **reste dû** dérivé (Story 25-4-b1) ; ⚠️ il ne filtre pas les règlements +/// par date : `as_of` borne les tranches, pas les pièces — la route le fixe à +/// aujourd'hui. Scoping multi-tenant obligatoire (`i.company_id = ?`). pub async fn generate( pool: &MySqlPool, company_id: i64, @@ -106,28 +119,36 @@ pub async fn generate( // Non échu : due_date NULL OU DATEDIFF <= 0 (échéance >= as_of) // 1-30 / 31-60 / 61-90 : DATEDIFF dans [lo, hi] // 90+ : DATEDIFF >= 91 (strictement plus de 90 jours — pas de recouvrement) - // Le TTC par facture vient de la table dérivée `lt`. HAVING total <> 0 - // écarte une facture legacy sans lignes (TTC 0) qui polluerait la liste. + // HAVING total <> 0 écarte une facture legacy sans lignes (reste dû 0) + // qui polluerait la liste. ⚠️ Un reste dû NÉGATIF (trop-perçu hérité) + // n'est pas écrêté : il apparaît. + // ⛔ Story 25-4-b1 (#416) : chaque tranche somme le RESTE DÛ, jamais le + // TTC — une facture de 1 000.— réglée à 900.— pèse 100.—. Les tranches + // restent assises sur `due_date` : un règlement partiel ne rajeunit pas la + // créance restante. Forme JOINTE (`amount_due_derived_joins`) : la forme + // scalaire serait réévaluée par ligne et par CASE. + let due = INVOICE_AMOUNT_DUE_DERIVED_SQL; let sql = format!( "SELECT c.id AS contact_id, c.name AS contact_name, \ COALESCE(SUM(CASE WHEN i.due_date IS NULL OR DATEDIFF(?, i.due_date) <= 0 \ - THEN COALESCE(lt.ttc, 0) ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS not_due, \ + THEN {due} ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS not_due, \ COALESCE(SUM(CASE WHEN DATEDIFF(?, i.due_date) BETWEEN 1 AND 30 \ - THEN COALESCE(lt.ttc, 0) ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS d1_30, \ + THEN {due} ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS d1_30, \ COALESCE(SUM(CASE WHEN DATEDIFF(?, i.due_date) BETWEEN 31 AND 60 \ - THEN COALESCE(lt.ttc, 0) ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS d31_60, \ + THEN {due} ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS d31_60, \ COALESCE(SUM(CASE WHEN DATEDIFF(?, i.due_date) BETWEEN 61 AND 90 \ - THEN COALESCE(lt.ttc, 0) ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS d61_90, \ + THEN {due} ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS d61_90, \ COALESCE(SUM(CASE WHEN DATEDIFF(?, i.due_date) >= 91 \ - THEN COALESCE(lt.ttc, 0) ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS d90p, \ - COALESCE(SUM(COALESCE(lt.ttc, 0)), CAST(0 AS DECIMAL(19,4))) AS total \ + THEN {due} ELSE 0 END), CAST(0 AS DECIMAL(19,4))) AS d90p, \ + COALESCE(SUM({due}), CAST(0 AS DECIMAL(19,4))) AS total \ FROM invoices i \ INNER JOIN contacts c ON c.id = i.contact_id \ - {INVOICE_TTC_DERIVED_JOIN_SQL} \ + {joins} \ WHERE i.company_id = ? AND i.status = 'validated' AND i.paid_at IS NULL \ GROUP BY c.id, c.name \ HAVING total <> 0 \ - ORDER BY c.name, c.id" + ORDER BY c.name, c.id", + joins = amount_due_derived_joins() ); let sql_rows = sqlx::query_as::<_, AgedRowSql>(&sql) diff --git a/crates/kesh-report/tests/aged_receivables.rs b/crates/kesh-report/tests/aged_receivables.rs index 23e18016..8c02301e 100644 --- a/crates/kesh-report/tests/aged_receivables.rs +++ b/crates/kesh-report/tests/aged_receivables.rs @@ -11,9 +11,9 @@ use chrono::{Duration, NaiveDate}; use kesh_core::accounting::vat::invoice_total_ttc; use kesh_db::entities::contact::{ContactType, NewContact, Salutation}; -use kesh_db::entities::{NewInvoice, NewInvoiceLine}; +use kesh_db::entities::{NewCreditNote, NewInvoice, NewInvoiceLine, SettlementChoice}; use kesh_db::repositories::invoices::ValidatedInvoice; -use kesh_db::repositories::{contacts, invoices}; +use kesh_db::repositories::{contacts, credit_notes, invoice_settlements_write, invoices}; use kesh_db::test_fixtures::{SeededCompany, seed_accounting_company}; use kesh_report::aged_receivables::generate; use rust_decimal::Decimal; @@ -314,3 +314,182 @@ async fn aged_empty_when_no_open_items(pool: MySqlPool) { assert_eq!(report.totals.not_due, Decimal::ZERO); assert_eq!(report.as_of, as_of()); } + +// ─── Story 25-4-b1 (#416) — la balance âgée somme le RESTE DÛ ───────────────── + +async fn settle( + pool: &MySqlPool, + seeded: &SeededCompany, + invoice_id: i64, + amount: Decimal, + on: NaiveDate, +) { + invoice_settlements_write::settle_invoice( + pool, + seeded.admin_user_id, + seeded.company_id, + invoice_id, + SettlementChoice::InternalAccount { + account_id: seeded.accounts["1000"], + }, + amount, + on, + ) + .await + .expect("règlement"); +} + +/// Solde du compte débiteurs au grand livre, pour la société. +async fn receivable_balance(pool: &MySqlPool, seeded: &SeededCompany) -> Decimal { + sqlx::query_scalar( + "SELECT COALESCE(SUM(l.debit) - SUM(l.credit), 0) FROM journal_entry_lines l \ + INNER JOIN journal_entries e ON e.id = l.entry_id \ + WHERE e.company_id = ? AND l.account_id = ?", + ) + .bind(seeded.company_id) + .bind(seeded.accounts["1100"]) + .fetch_one(pool) + .await + .expect("solde 1100") +} + +/// AC 3 — une facture de 1 081.— (1 000.— à 8,1 %) réglée de 900.— pèse **181.—** +/// dans sa tranche, pas 1 081.—. +#[sqlx::test(migrations = "../kesh-db/test-schema")] +async fn aged_uses_amount_due_not_ttc(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let alpha = mk_contact(&pool, &seeded, "Alpha SA").await; + let v = mk_validated( + &pool, + &seeded, + alpha, + Some(days_before(45)), + dec!(8.10), + dec!(1000.00), + ) + .await; + settle(&pool, &seeded, v.invoice.id, dec!(900.00), days_before(40)).await; + + let r = generate(&pool, seeded.company_id, as_of()).await.unwrap(); + assert_eq!(r.rows.len(), 1); + assert_eq!( + r.rows[0].buckets.days_31_to_60, + dec!(181.00), + "reste dû, pas le TTC" + ); + assert_eq!(r.rows[0].buckets.total, dec!(181.00)); + assert_eq!(r.totals.total, dec!(181.00)); +} + +/// AC 3 — ⛔ un règlement partiel ne rajeunit pas la créance : la tranche suit +/// l'échéance de la facture, pas la date du règlement. +#[sqlx::test(migrations = "../kesh-db/test-schema")] +async fn aged_partial_payment_keeps_original_bucket(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let alpha = mk_contact(&pool, &seeded, "Alpha SA").await; + let v = mk_validated( + &pool, + &seeded, + alpha, + Some(days_before(100)), + dec!(8.10), + dec!(100.00), + ) + .await; + // Réglé en partie HIER : la créance restante reste à plus de 90 jours. + settle(&pool, &seeded, v.invoice.id, dec!(8.10), days_before(1)).await; + + let r = generate(&pool, seeded.company_id, as_of()).await.unwrap(); + let b = &r.rows[0].buckets; + assert_eq!(b.days_over_90, dec!(100.00)); + assert_eq!( + b.not_due + b.days_1_to_30 + b.days_31_to_60 + b.days_61_to_90, + Decimal::ZERO + ); +} + +/// AC 5 — ⛔ le total de la balance âgée égale le solde du compte débiteurs au +/// grand livre, **dans la règle** : compte mû seulement par les ventes, +/// règlements clients et avoirs de factures validées, datés au plus tard à +/// `as_of`. Quatre états vivants : sans règlement, réglée en partie, soldée, +/// créditée. +#[sqlx::test(migrations = "../kesh-db/test-schema")] +async fn aged_total_matches_receivable_ledger(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let alpha = mk_contact(&pool, &seeded, "Alpha SA").await; + let beta = mk_contact(&pool, &seeded, "Beta SA").await; + + let _open = mk_validated( + &pool, + &seeded, + alpha, + Some(days_before(20)), + dec!(8.10), + dec!(500.00), + ) + .await; + let partial = mk_validated( + &pool, + &seeded, + alpha, + Some(days_before(70)), + dec!(2.60), + dec!(333.33), + ) + .await; + settle( + &pool, + &seeded, + partial.invoice.id, + dec!(100.00), + days_before(10), + ) + .await; + let paid = mk_validated( + &pool, + &seeded, + beta, + Some(days_before(30)), + dec!(8.10), + dec!(200.00), + ) + .await; + settle( + &pool, + &seeded, + paid.invoice.id, + dec!(216.20), + days_before(5), + ) + .await; + let credited = mk_validated( + &pool, + &seeded, + beta, + Some(days_before(15)), + dec!(8.10), + dec!(50.00), + ) + .await; + credit_notes::create_credit_note( + &pool, + NewCreditNote { + company_id: seeded.company_id, + invoice_id: credited.invoice.id, + date: days_before(2), + }, + seeded.admin_user_id, + ) + .await + .expect("avoir"); + + let r = generate(&pool, seeded.company_id, as_of()).await.unwrap(); + let ledger = receivable_balance(&pool, &seeded).await; + // 540.50 + (341.996… arrondi par ligne : 333.33 + 8.67 = 342.00) − 100 = 782.50 + assert_eq!( + ledger, + dec!(782.50), + "anti-vacuité : le grand livre porte les quatre états" + ); + assert_eq!(r.totals.total, ledger, "balance âgée = compte débiteurs"); +} diff --git a/docs/manual/fr/user-manual.pdf b/docs/manual/fr/user-manual.pdf index 139a7469d9ca487daeca37fabb66e16230e00dcb..44a073f25e99b04fe9f64b4366920f3fdbf2dd15 100644 GIT binary patch delta 97801 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 Cs$et# delta 96866 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

Cuaqg7RjFq?1)sEh0NWR+Z6K)n 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 z

9q8jT!N&`KHPNH88Z;d^o-u-fbK(qLwP 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 diff --git a/docs/manual/fr/user-manual.tex b/docs/manual/fr/user-manual.tex index 25dde525..a10c56b3 100644 --- a/docs/manual/fr/user-manual.tex +++ b/docs/manual/fr/user-manual.tex @@ -936,6 +936,12 @@ \subsection{Échéancier des factures} \item Factures payées (historique). \end{itemize} +Chaque ligne montre le \textbf{total} de la facture (TTC) et son \textbf{reste dû}, qui tient compte des +règlements partiels ; une facture réglée en partie porte le statut «~Partiellement payée~». Les +totaux du haut de page additionnent les restes dus. Le bouton \emph{Régler} ouvre le dialogue de +règlement \textbf{pré-rempli au reste dû} --- modifiable pour un nouvel acompte. L'export CSV porte +la même colonne «~Reste dû~». + Pour relancer les factures en retard, Kesh dispose d'un \textbf{écran de rappels} dédié — voir \S\ref{sec:relances-user}. Une facture suspendue de rappels reste visible dans l'échéancier (rien ne se cache). \subsection{Enregistrer et annuler un règlement}\label{sec:reglement-client} @@ -1573,18 +1579,18 @@ \subsection{Grand livre}\label{sec:grand-livre} \subsection{Balance âgée des créances}\label{sec:balance-agee} -Depuis la version \textbf{0.7}, l'onglet \emph{Rapports → Balance âgée} répartit l'\textbf{encours débiteur} (le total dû, TVA comprise) par \textbf{client} et par \textbf{tranche d'ancienneté}, arrêté à ce jour. C'est l'outil de pilotage du recouvrement : il montre d'un coup d'œil quelles créances vieillissent et lesquelles prioriser. +Depuis la version \textbf{0.7}, l'onglet \emph{Rapports → Balance âgée} répartit l'\textbf{encours débiteur} --- le \textbf{reste dû} de chaque facture, TVA comprise, après ses règlements partiels et son avoir éventuel --- par \textbf{client} et par \textbf{tranche d'ancienneté}, arrêté à ce jour. C'est l'outil de pilotage du recouvrement : il montre d'un coup d'œil quelles créances vieillissent et lesquelles prioriser. -\keshscreenshot{balance-agee.png}{Balance âgée : encours débiteur TTC par client et par tranche de retard, avec total général.} % TODO capture: onglet Rapports/Balance âgée +\keshscreenshot{balance-agee.png}{Balance âgée : reste dû par client et par tranche de retard, avec total général.} % TODO capture: onglet Rapports/Balance âgée \begin{itemize} - \item \textbf{Colonnes} : \emph{Non échu} \textbar{} \emph{1-30} \textbar{} \emph{31-60} \textbar{} \emph{61-90} \textbar{} \emph{90+} jours de retard. La colonne « Non échu » garantit que le \textbf{total général réconcilie} avec le solde du compte clients (débiteurs) du bilan. + \item \textbf{Colonnes} : \emph{Non échu} \textbar{} \emph{1-30} \textbar{} \emph{31-60} \textbar{} \emph{61-90} \textbar{} \emph{90+} jours de retard. Avec la colonne « Non échu », le \textbf{total général réconcilie} avec le solde du compte clients (débiteurs) du bilan \emph{tant que ce compte n'est mouvementé que par les factures, leurs règlements et leurs avoirs} (y compris leurs annulations). Toute autre écriture qui le touche l'en écarte : solde de départ, écriture saisie au journal, virement bancaire affecté à ce compte sans passer par une facture, paiement d'une facture fournisseur compensé sur ce compte, ou règlement dont la contrepartie est ce compte lui-même. De même un changement du compte clients dans les réglages de facturation, ou des données antérieures à la version 0.12.1, restaurées ou mises à jour. \item \textbf{Détail} : une ligne par client, un total par tranche, et un total général. Chaque client renvoie d'un clic vers \textbf{ses factures}. \item \textbf{Export CSV} : réservé aux rôles \emph{Comptable} et \emph{Administrateur} (le rapport lui-même est consultable par tous les rôles). \end{itemize} \begin{keshnote} -Les factures dont les rappels sont \textbf{suspendus restent comptées} dans la balance âgée (elles ne sortent que de la liste « à rappeler »). Le montant réparti est le \textbf{total dû TTC} de chaque facture, jamais le montant hors taxe. +Les factures dont les rappels sont \textbf{suspendus restent comptées} dans la balance âgée (elles ne sortent que de la liste « à rappeler »). Le montant réparti est le \textbf{reste dû} de chaque facture, jamais le montant hors taxe ni le total facturé : une facture de 1\,081.--- réglée de 900.--- y pèse 181.---, dans la tranche de \emph{son échéance} --- un règlement partiel ne rajeunit pas la créance. \end{keshnote} \subsection{Journal des écritures} diff --git a/frontend/src/lib/features/invoices/SettleInvoiceDialog.svelte b/frontend/src/lib/features/invoices/SettleInvoiceDialog.svelte index bbf3949a..218ed501 100644 --- a/frontend/src/lib/features/invoices/SettleInvoiceDialog.svelte +++ b/frontend/src/lib/features/invoices/SettleInvoiceDialog.svelte @@ -13,6 +13,7 @@ le parent gère l'appel API et ses erreurs. --> @@ -431,6 +427,9 @@ toggleSort('TotalAmount')}> {i18nMsg('due-dates-column-total', 'Total')} + + {i18nMsg('due-dates-column-amount-due', 'Reste dû')} + {i18nMsg('due-dates-column-payment-status', 'Statut')} @@ -449,8 +448,13 @@ {inv.invoiceNumber ?? '—'} {inv.contactName} - + {formatInvoiceTotal(inv.totalTtc)} + + {formatInvoiceTotal(inv.amountDue)} + @@ -490,10 +494,8 @@ {/if} {#if markTarget} - + { @@ -504,7 +506,7 @@ } }} invoiceDate={markTarget.date} - amountDue={null} + amountDue={markTarget.amountDue} accounts={settleAccounts} bankAccounts={settleBankAccounts} submitting={markSubmitting} diff --git a/frontend/src/routes/(app)/invoices/due-dates/due-dates-page.test.ts b/frontend/src/routes/(app)/invoices/due-dates/due-dates-page.test.ts new file mode 100644 index 00000000..5e923260 --- /dev/null +++ b/frontend/src/routes/(app)/invoices/due-dates/due-dates-page.test.ts @@ -0,0 +1,144 @@ +/** + * L'échéancier porte le reste dû — Story 25-4-b1 (#416). + * + * Ce que ni le test Rust ni l'E2E ne prouvent seuls : que la page **affiche** + * le reste dû de la ligne, rend le statut « partiellement payée », et **passe** + * ce reste dû au dialogue de règlement, qui le pré-remplit. + * + * ⚠️ Chaque test nomme la MUTATION qu'il attrape. Patron : + * `invoices/[id]/invoice-settlements-page.test.ts` — mocks hoistés AVANT + * l'import du composant. + */ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { render, fireEvent, cleanup, waitFor } from "@testing-library/svelte"; +import type { + DueDateItem, + DueDatesResponse, +} from "$lib/features/invoices/invoices.types"; + +vi.mock("$app/environment", () => ({ browser: true })); +vi.mock("$app/navigation", () => ({ goto: vi.fn() })); +vi.mock("$app/state", () => ({ + page: { + params: {}, + url: new URL("http://localhost/invoices/due-dates"), + }, +})); +vi.mock("$lib/shared/utils/i18n.svelte", () => ({ + i18nMsg: ( + _k: string, + fallback: string, + args?: Record, + ) => + args + ? fallback.replace(/\{\s*\$(\w+)\s*\}/g, (_, n) => String(args[n] ?? "")) + : fallback, +})); +vi.mock("$lib/shared/utils/notify", () => ({ + notifyError: vi.fn(), + notifySuccess: vi.fn(), + notifyWarning: vi.fn(), +})); +vi.mock("$lib/app/stores/auth.svelte", () => ({ + authState: { currentUser: { role: "Comptable", username: "c", userId: "1" } }, +})); + +const listDueDatesMock = vi.fn(); +vi.mock("$lib/features/invoices/invoices.api", () => ({ + listDueDates: (q: unknown) => listDueDatesMock(q), + settleInvoice: vi.fn(), + exportDueDatesCsv: vi.fn(), +})); +vi.mock("$lib/features/accounts/accounts.api", () => ({ + fetchAccounts: vi.fn(async () => []), +})); +vi.mock("$lib/features/bank-accounts/bank-accounts.api", () => ({ + listBankAccounts: vi.fn(async () => []), +})); +vi.mock("$lib/features/contacts/contacts.api", () => ({ + getContact: vi.fn(), + listContacts: vi.fn(async () => ({ items: [], total: 0, offset: 0, limit: 20 })), +})); + +import Page from "./+page.svelte"; + +function item(partial: Partial = {}): DueDateItem { + return { + id: 5, + companyId: 1, + contactId: 1, + contactName: "Client SA", + invoiceNumber: "F-2026-005", + status: "validated", + date: "2026-03-01", + dueDate: "2026-03-31", + paymentTerms: null, + totalAmount: "100.00", + totalTtc: "108.10", + amountSettled: "40.00", + amountDue: "68.10", + paidAt: null, + dunningPausedAt: null, + dunningPausedNote: null, + isOverdue: false, + version: 3, + createdAt: "2026-03-01T00:00:00", + updatedAt: "2026-03-05T00:00:00", + ...partial, + }; +} + +function response(items: DueDateItem[]): DueDatesResponse { + return { + items, + total: items.length, + offset: 0, + limit: 20, + summary: { + unpaidCount: items.length, + unpaidTotal: "68.10", + overdueCount: 0, + overdueTotal: "0.00", + }, + }; +} + +beforeEach(() => { + listDueDatesMock.mockResolvedValue(response([item()])); +}); +afterEach(() => { + cleanup(); + vi.clearAllMocks(); +}); + +describe("échéancier — le reste dû (Story 25-4-b1)", () => { + it("la ligne affiche le reste dû, pas le seul TTC (mutation : colonne retirée)", async () => { + const { findByTestId } = render(Page); + const cell = await findByTestId("due-dates-amount-due"); + expect(cell.textContent).toContain("68.10"); + }); + + it("une facture réglée en partie est « partiellement payée » (mutation : `partial` retiré du statut)", async () => { + const { findByText } = render(Page); + expect(await findByText("Partiellement payée")).toBeTruthy(); + }); + + it("une facture sans règlement reste « impayée »", async () => { + listDueDatesMock.mockResolvedValue( + response([item({ amountSettled: "0.00", amountDue: "108.10" })]), + ); + const { findByText, queryByText } = render(Page); + expect(await findByText("Impayée")).toBeTruthy(); + expect(queryByText("Partiellement payée")).toBeNull(); + }); + + it("le dialogue de règlement est pré-rempli au reste dû (mutation : `amountDue={null}` remis)", async () => { + const { findByText, container } = render(Page); + await fireEvent.click(await findByText("Régler")); + await waitFor(() => { + const input = document.getElementById("settle-amount") as HTMLInputElement | null; + expect(input?.value).toBe("68.10"); + }); + expect(container).toBeTruthy(); + }); +}); diff --git a/frontend/tests/e2e/invoices_echeancier.spec.ts b/frontend/tests/e2e/invoices_echeancier.spec.ts index 2ae0e5d7..3c8f4e54 100644 --- a/frontend/tests/e2e/invoices_echeancier.spec.ts +++ b/frontend/tests/e2e/invoices_echeancier.spec.ts @@ -144,18 +144,12 @@ test.describe('Échéancier factures — Story 5.4', () => { // Le premier compte proposé suffit : ce cas porte sur le PARCOURS, pas sur // le choix du compte — celui-ci est couvert par les tests Rust. await page.getByTestId('settle-account').selectOption({ index: 1 }); - // ⚠️ **Le montant doit être SAISI ici, et c'est délibéré.** L'échéancier - // ne connaît pas encore le résiduel (colonnes reportées à une issue - // séparée), donc le dialogue reçoit `amountDue = null` — « non calculé » - // — et laisse le champ vide plutôt que de pré-remplir. - // - // ⛔ Y pré-remplir le TTC de la ligne serait FAUX : sur une facture - // déjà partiellement réglée, TTC ≠ résiduel, et l'utilisateur serait - // conduit vers un trop-perçu que le serveur refuse. Un champ vide dit la - // vérité ; un champ pré-rempli d'un mauvais chiffre ment. - // - // 100.00 HT à 8.10 % ⇒ 108.10 TTC (cf. `createAndValidateInvoice`). - await page.getByLabel(/Montant|Amount|Importo|Betrag/i).fill('108.10'); + // Story 25-4-b1 (#416) — le montant n'est plus SAISI : l'échéancier porte + // le reste dû, et le dialogue le pré-remplit. Sans règlement, le reste dû + // est le TTC : 100.00 HT à 8.10 % ⇒ 108.10 (cf. `createAndValidateInvoice`). + // Le cas d'une facture PARTIELLEMENT réglée, où reste dû ≠ TTC, est le + // test suivant. + await expect(page.getByLabel(/Montant|Amount|Importo|Betrag/i)).toHaveValue("108.10"); await page.getByTestId('settle-confirm').click(); // Après reload, la facture en retard disparaît du filtre "Impayées". @@ -197,8 +191,67 @@ test.describe('Échéancier factures — Story 5.4', () => { expect(futureId).toBeGreaterThan(0); }); - // Story 21-2a (#246) — la colonne Total de l'échéancier affiche le TTC. - test('la colonne Total affiche le TTC (montant dû), pas le HT', async ({ page }) => { + // Story 25-4-b1 (#416) — une facture réglée en partie montre son reste dû, + // son statut « partiellement payée », et le dialogue se pré-remplit au reste + // dû — pas au TTC, qui conduirait à un trop-perçu refusé. + test('facture réglée en partie : reste dû, statut, dialogue pré-rempli', async ({ page }) => { + await login(page); + const contactName = uniq('EchPartiel'); + const contactId = await createContactViaApi(page, contactName); + const id = await createAndValidateInvoice( + page, + contactId, + daysFromToday(-10), + daysFromToday(20), + '100.00', + ); + + const ctx = await authedApiContext(page); + try { + // Un compte de liquidités (classe 10) actif et imputable — le numéro + // exact dépend du plan comptable : ne pas le figer. + const accounts = await ctx.get('/api/v1/accounts?includeArchived=false'); + expect(accounts.ok(), `comptes: ${accounts.status()}`).toBeTruthy(); + const cash = ( + (await accounts.json()) as { + id: number; + number: string; + accountType: string; + active: boolean; + postable: boolean; + }[] + ).find((a) => a.active && a.postable && a.accountType === 'Asset' && a.number.startsWith('10')); + expect(cash, 'un compte de liquidités imputable').toBeTruthy(); + const settled = await ctx.post(`/api/v1/invoices/${id}/settlements`, { + data: { + settlementType: 'internal_account', + accountId: cash!.id, + amount: '40.00', + settledOn: daysFromToday(0), + }, + }); + expect(settled.ok(), `règlement: ${settled.status()}`).toBeTruthy(); + } finally { + await disposeContextSafe(ctx); + } + + await page.goto('/invoices/due-dates'); + const row = page.locator('tbody tr', { hasText: contactName }); + await expect(row).toBeVisible({ timeout: 5000 }); + // 108.10 − 40.00 = 68.10 + await expect(row.getByTestId('due-dates-amount-due')).toHaveText(/68\.10/); + await expect( + row.getByText(/Partiellement payée|Partially paid|Parzialmente pagata|Teilweise bezahlt/i), + ).toBeVisible(); + + await row.getByRole('button', { name: /Régler|Settle|Pagare|Erfassen/i }).click(); + await expect(page.getByRole('dialog')).toBeVisible(); + await expect(page.getByLabel(/Montant|Amount|Importo|Betrag/i)).toHaveValue("68.10"); + }); + + // Story 21-2a (#246) — la colonne Total de l'échéancier affiche le TTC émis ; + // le reste dû a sa propre colonne depuis la Story 25-4-b1. + test('la colonne Total affiche le TTC, pas le HT', async ({ page }) => { await login(page); const contactName = uniq('EchTtc'); const contactId = await createContactViaApi(page, contactName); @@ -209,7 +262,11 @@ test.describe('Échéancier factures — Story 5.4', () => { const row = page.locator('tbody tr', { hasText: contactName }).first(); await expect(row).toBeVisible({ timeout: 5000 }); // Le TTC formaté suisse est affiché ; le HT nu ne l'est pas. - await expect(row.getByText('108.10')).toBeVisible(); + // ⚠️ Story 25-4-b1 : sans règlement, la colonne « Reste dû » affiche le + // même 108.10 — viser la cellule Total (5ᵉ colonne) et non le texte seul, + // qui désignerait désormais deux cellules. + await expect(row.locator('td').nth(4)).toHaveText(/108\.10/); + await expect(row.getByTestId('due-dates-amount-due')).toHaveText(/108\.10/); await expect(row.getByText(/^100\.00$/)).toHaveCount(0); }); From e1713da128f1690ab4749d41a16972ae2231a405 Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 15:49:43 +0200 Subject: [PATCH 32/34] docs(25-4-b1): gate E2E tenu, story en review (refs #416) Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-residuel-aux-agregats.md | 15 ++++++++++++--- .../implementation-artifacts/sprint-status.yaml | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md index 2c4b9234..15abe3d4 100644 --- a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -1,6 +1,6 @@ # Story 25.4-b1 : Le résiduel aux agrégats — balance âgée et échéancier -Status: in-progress +Status: review **Issue : [#416]** — cette story en livre la partie **agrégats** ; la partie **rappels** est la 25-4-b2. ⛔ **La PR de b1 porte `refs #416`, pas `closes`** : l'issue ne se ferme qu'avec b2. @@ -194,7 +194,7 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba - [x] **T4 — échéancier, frontend** (AC 10), i18n. - [x] **T5 — tests et mutations** (AC 12). - [x] **T6 — textes** (AC 13). -- [ ] **T7 — gates** : backend complet (base remise à zéro), frontend complet, **E2E complet**. +- [x] **T7 — gates** : backend complet (base remise à zéro), frontend complet, **E2E complet**. ## Dev Notes @@ -304,7 +304,13 @@ de le saisir, et un cas « réglée en partie » est ajouté. - Backend : base remise à zéro, `scripts/test-fast.sh` **2491 / 2491** (2484 + 7). - Frontend : `check` 0 erreur (27 avertissements), `lint-i18n-ownership` PASS, `test:unit` **834 / 834** (826 + 8), build OK. -- E2E : EN_ATTENTE +- E2E : sur `kesh_e2e` reconstruite, **225 passed / 8 failed / 19 skipped** — les 7 KF-029 (#97) + et `product-revenue-account.spec.ts:133`, victime de pollution déjà répertoriée + (`docs/testing.md` § « Les échecs attendus ») et **verte rejouée seule** (deux fois, 4/4). + ⚠️ Un **premier run** avait rendu 214/19 : les 7 KF-029 et **12 échecs hors liste**, tous sur le + même symptôme — `/login` rendant la page SvelteKit « Erreur 500 — Internal Error » avant le + formulaire, sans aucune erreur côté backend. **Rejoués seuls, 12/12 verts** ; non reproduit au + run suivant. Symptôme non documenté, non tracé à ce jour. ### File List @@ -332,6 +338,9 @@ de le saisir, et un cas « réglée en partie » est ajouté. ## Change Log +- **2026-09-27** — **T7 tenu, story en `review`.** Commit de sauvegarde `87cbc8b4` après un crash de + la station (intégrité vérifiée : `git fsck`, fins de fichiers, fmt, clippy, `check`). Gate E2E : + cf. § *Gates* — premier run pollué (12 hors liste, verts seuls), second conforme à la baseline. - **2026-09-27** — Signal MED→MED arbitré par Guy : **pas de découpage** (« continue ainsi »). Dev lancé. - **2026-09-27** — **validation P5 ciblée** (Haiku, diff `5480b4f8..5824c048`, prompt `25-4-b1-validate-prompt-p5.md`) — **0 finding**, preuves recopiées (grep des cinq sites de T0, diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 61d48fca..4f22f332 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -356,7 +356,7 @@ development_status: 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* 25-4-a-residuel-juste: done # 2026-09-27 REVUE CLOSE EN 1 PASSE ; PR #472 OUVERTE (closes #455 #456), attend « merge ». # IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. - 25-4-b1-residuel-aux-agregats: in-progress # 2026-09-27 [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L -> P2 Haiku 0 (repris : 1M) -> P3 ciblee Opus 3M/3L : portee de l'invariant REECRITE PAR UNE REGLE ; issues #473 #474 ouvertes -> P4 1M -> P5 0 : VALIDEE en 5 passes. Signal MED->MED : pas de decoupage (Guy, 2026-09-27). Branche EMPILEE sur 25-4-a. + 25-4-b1-residuel-aux-agregats: review # 2026-09-27 DEV FAIT, gates verts (E2E 225/8 conforme) ; revue de code a lancer. # [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L -> P2 Haiku 0 (repris : 1M) -> P3 ciblee Opus 3M/3L : portee de l'invariant REECRITE PAR UNE REGLE ; issues #473 #474 ouvertes -> P4 1M -> P5 0 : VALIDEE en 5 passes. Signal MED->MED : pas de decoupage (Guy, 2026-09-27). Branche EMPILEE sur 25-4-a. 25-4-b2-residuel-aux-rappels: backlog # [#416, closes] le rappel reclame le reste du (texte, QR, PDF montant initial / deja regle / reste), frais configurables masques a zero ; point ouvert : frais non comptabilises. 25-4-b-residuel-aux-agregats: split # 2026-09-27 DECOUPEE en b1 (agregats) / b2 (rappels) — six modules apres les arbitrages Q1. # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. From 03951962876338d90a660050fd38c1c647c163fa Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 16:26:33 +0200 Subject: [PATCH 33/34] =?UTF-8?q?fix(25-4-b1):=20revue=20P1=20=E2=80=94=20?= =?UTF-8?q?la=20balance=20=C3=A2g=C3=A9e=20ne=20perd=20plus=20un=20client?= =?UTF-8?q?=20compens=C3=A9=20(refs=20#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le HAVING porte sur les factures à reste dû non nul, plus sur le total du contact : un trop-perçu hérité compensant une facture ouverte faisait sortir le contact entier. Commentaire de get_invoice rectifié ; tests du règlement annulé sur la balance âgée et l'échéancier. Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-residuel-aux-agregats.md | 44 ++++++- .../implementation-artifacts/deferred-work.md | 5 + crates/kesh-api/src/routes/invoices.rs | 7 +- .../tests/invoice_amount_due_parity.rs | 47 +++++++ crates/kesh-report/src/aged_receivables.rs | 12 +- crates/kesh-report/tests/aged_receivables.rs | 120 ++++++++++++++++++ 6 files changed, 227 insertions(+), 8 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md index 15abe3d4..18ba5ab1 100644 --- a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -63,9 +63,13 @@ chaque facture du jeu, l'expression jointe de l'AC 1 = `amount_due` scalaire. rajeunit pas la créance restante**. **AC 4** — Le périmètre des factures ne change pas : `status = 'validated' AND paid_at IS NULL` -(une facture soldée a `paid_at`, 24-2 D4). Le `HAVING total <> 0` reste, et porte désormais sur le -reste dû. ⚠️ Un reste dû **négatif** (trop-perçu hérité) n'est **pas écrêté** : il apparaît, en +(une facture soldée a `paid_at`, 24-2 D4). ~~Le `HAVING total <> 0` reste, et porte désormais sur le +reste dû.~~ Le `HAVING` garde un contact dès qu'**une** de ses factures a un reste dû non nul. +⚠️ Un reste dû **négatif** (trop-perçu hérité) n'est **pas écrêté** : il apparaît, en négatif — le doc-comment d'`amount_due` l'exige. +*(Rectifié le 2026-09-27, passe 1 de revue de code : la phrase barrée contredisait la suivante. Sur +le total du contact, un trop-perçu hérité compensant une facture ouverte faisait sortir le contact +entier — et le négatif n'apparaissait pas.)* **AC 5** — ⛔ **Invariant de concordance** (test) : sur une société dont le compte débiteurs n'est mû que par des factures, des règlements et des avoirs — factures sans règlement, partiellement réglée, @@ -196,6 +200,23 @@ CHANGELOG `[0.12.1]` *Fixed*. README : rien, sauf si la ligne v0.12.1 cite la ba - [x] **T6 — textes** (AC 13). - [x] **T7 — gates** : backend complet (base remise à zéro), frontend complet, **E2E complet**. +### Review Findings + +*Passe 1 de `bmad-code-review` (2026-09-27) — Blind Hunter, Edge Case Hunter, Acceptance Auditor, tous +Sonnet, sur `bd06e789..87cbc8b4`. Brut : 4 + 4 + 2 findings ; après dédoublonnage et tri : 3 patch, +2 defer, 3 écartés.* + +- [x] [Review][Patch] **MEDIUM — `HAVING total <> 0` fait disparaître un client dont les restes dus se compensent** [crates/kesh-report/src/aged_receivables.rs:149] — la grandeur sommée admet désormais le négatif ; une facture ouverte de 100.— et un trop-perçu hérité de −100.— chez le même contact donnent `total = 0` et la ligne entière sort du résultat, contrairement à ce que promet la spec (« il apparaît, en négatif »). Atteignable seulement par données héritées (les trois chemins d'écriture refusent le trop-perçu ou annulent la facture — tracé par l'Edge Case Hunter). Convergé Blind + Edge. +- [x] [Review][Patch] **MEDIUM — commentaire devenu faux : « la fiche est la seule surface où le résiduel est calculé »** [crates/kesh-api/src/routes/invoices.rs:638] — la story ajoute l'échéancier (liste, export, résumé) et la balance âgée. Symptôme grepé (`seule surface`) : aucun autre site. +- [x] [Review][Patch] **LOW — aucun test des agrégats après annulation d'un règlement** [crates/kesh-db/tests/invoice_amount_due_parity.rs] — `cancel_settlement` n'est exercé que sur la fiche ; ni l'échéancier ni la balance âgée ne sont vérifiés après retrait d'un règlement. +- [x] [Review][Defer] **`as_of` borne les tranches, pas les règlements** [crates/kesh-report/src/aged_receivables.rs:109] — deferred : limite écrite dans le doc-comment de `generate`, la route fixe `as_of` à aujourd'hui ; ne mord que si un appelant passait une date passée. +- [x] [Review][Defer] **trois tests préexistants déstructurent `seed_base` dans le mauvais ordre** [crates/kesh-api/tests/invoice_echeancier_e2e.rs] — deferred, pre-existing : déjà relevé au Debug Log, verts parce que les deux identifiants valent 1. + +Écartés : pré-remplissage négatif du dialogue de règlement (Blind + Edge — tranché hors périmètre par +la spec, et la garde `n <= 0` bloque la soumission) ; deux remarques LOW de l'Acceptance Auditor +(piège de décompte `i18nMsg(` dans un commentaire, parité d'export couverte par un autre fichier que +celui nommé) — constats sans défaut. + ## Dev Notes ### Ce qu'il ne faut pas faire @@ -338,6 +359,25 @@ de le saisir, et un cas « réglée en partie » est ajouté. ## Change Log +- **2026-09-27** — **Revue de code, passe 1** (Blind Hunter, Edge Case Hunter, Acceptance Auditor — + Sonnet ×3, `bd06e789..87cbc8b4`) — **2 MEDIUM, 1 LOW à corriger**, 2 différés, 3 écartés (§ *Review + Findings*). Corrigés : + - le `HAVING` de la balance âgée porte sur le **nombre de factures à reste dû non nul**, plus sur le + total du contact (`aged_receivables.rs`) ; l'AC 4 est rectifié, sa phrase contradictoire barrée. + Test `aged_compensating_legacy_overpayment_keeps_the_contact` — ⛔ **mutation vérifiée** : avec + l'ancien `HAVING total <> 0`, il rougit seul (« Alpha ne doit pas disparaître »), les 8 autres + restent verts ; + - le commentaire de `get_invoice` (`routes/invoices.rs`) ne prétend plus que la fiche est la seule + surface calculant le résiduel ; symptôme `seule surface` grepé sur `crates/`, `frontend/src` et le + manuel : aucun autre site ; + - règlement annulé : `aged_cancelled_settlement_no_longer_counts` et + `cancelled_settlement_leaves_the_aggregates` (liste, export, résumé), chacun avec un témoin avant + annulation. + Tests, recomptés (`#[sqlx::test]`) de `87cbc8b4` à ce commit : `aged_receivables.rs` 7 → 9, + `invoice_amount_due_parity.rs` 6 → 7, soit **+3** ; de `bd06e789` : +10 backend. Manuel : rien ne + décrivait la sélection des contacts, pas de régénération. **Gate ciblé** : fmt et clippy workspace + verts, `binary(aged_receivables) | binary(invoice_amount_due_parity)` **16/16** ; gate complet au + dernier commit de la boucle. - **2026-09-27** — **T7 tenu, story en `review`.** Commit de sauvegarde `87cbc8b4` après un crash de la station (intégrité vérifiée : `git fsck`, fins de fichiers, fmt, clippy, `check`). Gate E2E : cf. § *Gates* — premier run pollué (12 hors liste, verts seuls), second conforme à la baseline. diff --git a/_bmad-output/implementation-artifacts/deferred-work.md b/_bmad-output/implementation-artifacts/deferred-work.md index edfa4982..4876d9b9 100644 --- a/_bmad-output/implementation-artifacts/deferred-work.md +++ b/_bmad-output/implementation-artifacts/deferred-work.md @@ -139,3 +139,8 @@ Pass 1 Opus 4.8 × 3 reviewers (Blind Hunter + Edge Case Hunter + Acceptance Aud ## Deferred from: code review of 25-5-a-export-souverainete (2026-09-26) - L'export de souveraineté lit ses trente tables sans instantané commun (défaut antérieur, aggravé par la 25-5-a) — tracé par **#465**, source de vérité. + +## Deferred from: code review of 25-4-b1-residuel-aux-agregats (2026-09-27) + +- **La balance âgée déduit tous les règlements, quelle que soit `as_of`** (`crates/kesh-report/src/aged_receivables.rs`, `generate`). Depuis la 25-4-b1 le montant sommé est le reste dû, qui soustrait les règlements et avoirs **existants** sans comparer leur date à `as_of` : une balance « au jour J » passé serait sous-évaluée. **Différé** : la limite est écrite dans le doc-comment de `generate`, et la seule route appelante fixe `as_of` à aujourd'hui. À reprendre si une balance âgée historique (audit, clôture) est demandée. +- **Trois tests préexistants de `invoice_echeancier_e2e.rs` déstructurent `seed_base` en `(company_id, admin_id)`** alors qu'elle rend `(admin_id, company_id)`. Ils passent parce que les deux valent 1 sur une base neuve. **Différé** : préexistant, relevé au Debug Log de la 25-4-b1 ; les deux tests hérités de la 25-4-a ont été rectifiés. diff --git a/crates/kesh-api/src/routes/invoices.rs b/crates/kesh-api/src/routes/invoices.rs index 603d4c33..c23166bb 100644 --- a/crates/kesh-api/src/routes/invoices.rs +++ b/crates/kesh-api/src/routes/invoices.rs @@ -635,8 +635,11 @@ pub async fn get_invoice( invoices::find_by_id_with_lines(&state.pool, current_user.company_id, id) .await? .ok_or(AppError::Database(DbError::NotFound))?; - // Story 24-2 (#371) — la fiche est la seule surface où le résiduel est - // calculé. ⚠️ Une requête de plus, sur UNE facture : pas de N+1 possible ici. + // Story 24-2 (#371) — la fiche calcule le résiduel par la forme SCALAIRE. + // ⚠️ Une requête de plus, sur UNE facture : pas de N+1 possible ici. Les + // agrégats (échéancier : liste, export, résumé ; balance âgée) le calculent + // par la forme JOINTE `amount_due_derived_joins` depuis la Story 25-4-b1 — + // les deux sont tenues à parité par `invoice_amount_due_parity.rs`. let settled = kesh_db::repositories::invoice_settlements::amount_settled(&state.pool, id) .await .map_err(AppError::Database)?; diff --git a/crates/kesh-db/tests/invoice_amount_due_parity.rs b/crates/kesh-db/tests/invoice_amount_due_parity.rs index bea59d82..885fa1f1 100644 --- a/crates/kesh-db/tests/invoice_amount_due_parity.rs +++ b/crates/kesh-db/tests/invoice_amount_due_parity.rs @@ -396,3 +396,50 @@ async fn list_items_carry_amount_due(pool: MySqlPool) { assert_eq!(o.amount_due, dec!(10.81), "{surface}"); } } + +/// Revue de code 25-4-b1, passe 1 — un règlement ANNULÉ ne compte plus sur +/// aucune surface agrégée : liste, export et résumé repèsent le TTC entier. +#[sqlx::test(migrations = "./test-schema")] +async fn cancelled_settlement_leaves_the_aggregates(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let contact = make_contact(&pool, &seeded).await; + let inv = validated(&pool, &seeded, contact, &[(dec!(8.10), dec!(100.00))]).await; + let entry = settle(&pool, &seeded, inv, dec!(40.00)).await; + // Témoin : avant l'annulation, le résumé porte le reste dû. + let avant = invoices::due_dates_summary(&pool, seeded.company_id, &unpaid_query()) + .await + .unwrap(); + assert_eq!(avant.unpaid_total, dec!(68.10)); + + let settlement_id: i64 = + sqlx::query_scalar("SELECT id FROM invoice_settlements WHERE journal_entry_id = ?") + .bind(entry) + .fetch_one(&pool) + .await + .unwrap(); + invoice_settlements_write::cancel_settlement( + &pool, + seeded.admin_user_id, + seeded.company_id, + inv, + settlement_id, + ) + .await + .expect("annulation du règlement"); + + let summary = invoices::due_dates_summary(&pool, seeded.company_id, &unpaid_query()) + .await + .unwrap(); + assert_eq!(summary.unpaid_total, dec!(108.10), "résumé"); + let page = invoices::list_by_company_paginated(&pool, seeded.company_id, unpaid_query()) + .await + .unwrap(); + let (export, _) = invoices::list_for_export(&pool, seeded.company_id, &unpaid_query(), 100) + .await + .unwrap(); + for (surface, items) in [("liste", &page.items), ("export", &export)] { + let i = items.iter().find(|i| i.id == inv).expect(surface); + assert_eq!(i.amount_settled, Decimal::ZERO, "{surface} : réglé"); + assert_eq!(i.amount_due, dec!(108.10), "{surface} : reste dû"); + } +} diff --git a/crates/kesh-report/src/aged_receivables.rs b/crates/kesh-report/src/aged_receivables.rs index aadf7478..6498d722 100644 --- a/crates/kesh-report/src/aged_receivables.rs +++ b/crates/kesh-report/src/aged_receivables.rs @@ -119,9 +119,13 @@ pub async fn generate( // Non échu : due_date NULL OU DATEDIFF <= 0 (échéance >= as_of) // 1-30 / 31-60 / 61-90 : DATEDIFF dans [lo, hi] // 90+ : DATEDIFF >= 91 (strictement plus de 90 jours — pas de recouvrement) - // HAVING total <> 0 écarte une facture legacy sans lignes (reste dû 0) - // qui polluerait la liste. ⚠️ Un reste dû NÉGATIF (trop-perçu hérité) - // n'est pas écrêté : il apparaît. + // Le HAVING garde un contact dès qu'UNE de ses factures a un reste dû non + // nul ; il écarte ainsi une facture legacy sans lignes (reste dû 0) qui + // polluerait la liste. ⚠️ Un reste dû NÉGATIF (trop-perçu hérité) n'est + // pas écrêté : il apparaît. ⛔ Ne PAS revenir à `HAVING total <> 0` : la + // grandeur admet le négatif, et un trop-perçu hérité compensant une facture + // ouverte du même contact ferait disparaître la ligne entière — la relance + // due avec (revue de code 25-4-b1, passe 1). // ⛔ Story 25-4-b1 (#416) : chaque tranche somme le RESTE DÛ, jamais le // TTC — une facture de 1 000.— réglée à 900.— pèse 100.—. Les tranches // restent assises sur `due_date` : un règlement partiel ne rajeunit pas la @@ -146,7 +150,7 @@ pub async fn generate( {joins} \ WHERE i.company_id = ? AND i.status = 'validated' AND i.paid_at IS NULL \ GROUP BY c.id, c.name \ - HAVING total <> 0 \ + HAVING SUM(CASE WHEN {due} <> 0 THEN 1 ELSE 0 END) > 0 \ ORDER BY c.name, c.id", joins = amount_due_derived_joins() ); diff --git a/crates/kesh-report/tests/aged_receivables.rs b/crates/kesh-report/tests/aged_receivables.rs index 8c02301e..013ff4fe 100644 --- a/crates/kesh-report/tests/aged_receivables.rs +++ b/crates/kesh-report/tests/aged_receivables.rs @@ -493,3 +493,123 @@ async fn aged_total_matches_receivable_ledger(pool: MySqlPool) { ); assert_eq!(r.totals.total, ledger, "balance âgée = compte débiteurs"); } + +/// AC 4 — ⛔ un trop-perçu hérité qui COMPENSE une facture ouverte du même +/// contact ne fait pas disparaître le contact : la relance de la facture +/// ouverte reste visible, le négatif apparaît dans sa tranche. +/// +/// L'état est inatteignable par l'application (le trop-perçu est refusé à +/// l'écriture) ; il est fabriqué comme le porteraient des données héritées : +/// un règlement réel, passé sur une facture « parking » d'un autre contact +/// qui peut l'absorber, puis rattaché en SQL à la facture cible. +/// +/// Revue de code 25-4-b1, passe 1 : avec `HAVING total <> 0`, Alpha +/// (108.10 − 108.10 = 0) sortait du résultat. +#[sqlx::test(migrations = "../kesh-db/test-schema")] +async fn aged_compensating_legacy_overpayment_keeps_the_contact(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let alpha = mk_contact(&pool, &seeded, "Alpha SA").await; + let beta = mk_contact(&pool, &seeded, "Beta SA").await; + + // Alpha : 108.10 ouverte, en retard de 20 jours… + let _open = mk_validated( + &pool, + &seeded, + alpha, + Some(days_before(20)), + dec!(8.10), + dec!(100.00), + ) + .await; + // … et 54.05 qui portera un règlement hérité de 162.15 → reste −108.10. + let target = mk_validated( + &pool, + &seeded, + alpha, + Some(days_before(70)), + dec!(8.10), + dec!(50.00), + ) + .await; + // Parking : 216.20 chez Beta, réglée en partie (162.15) — paid_at reste NULL. + let parking = mk_validated( + &pool, + &seeded, + beta, + Some(days_before(10)), + dec!(8.10), + dec!(200.00), + ) + .await; + settle( + &pool, + &seeded, + parking.invoice.id, + dec!(162.15), + days_before(5), + ) + .await; + sqlx::query("UPDATE invoice_settlements SET invoice_id = ? WHERE invoice_id = ?") + .bind(target.invoice.id) + .bind(parking.invoice.id) + .execute(&pool) + .await + .expect("rattacher le règlement hérité"); + + let r = generate(&pool, seeded.company_id, as_of()).await.unwrap(); + let a = r + .rows + .iter() + .find(|row| row.contact_id == alpha) + .expect("Alpha ne doit pas disparaître : sa facture ouverte est due"); + assert_eq!(a.buckets.days_1_to_30, dec!(108.10), "relance due visible"); + assert_eq!(a.buckets.days_61_to_90, dec!(-108.10), "négatif non écrêté"); + assert_eq!(a.buckets.total, Decimal::ZERO); + let b = r + .rows + .iter() + .find(|row| row.contact_id == beta) + .expect("Beta"); + assert_eq!(b.buckets.total, dec!(216.20), "parking : tout redevient dû"); +} + +/// Revue de code 25-4-b1, passe 1 — un règlement ANNULÉ ne compte plus : la +/// facture repèse son TTC entier dans sa tranche. +#[sqlx::test(migrations = "../kesh-db/test-schema")] +async fn aged_cancelled_settlement_no_longer_counts(pool: MySqlPool) { + let seeded = seed_accounting_company(&pool).await.unwrap(); + let alpha = mk_contact(&pool, &seeded, "Alpha SA").await; + let v = mk_validated( + &pool, + &seeded, + alpha, + Some(days_before(45)), + dec!(8.10), + dec!(1000.00), + ) + .await; + settle(&pool, &seeded, v.invoice.id, dec!(900.00), days_before(40)).await; + // Témoin : avant l'annulation, la tranche porte le reste dû. + let avant = generate(&pool, seeded.company_id, as_of()).await.unwrap(); + assert_eq!(avant.rows[0].buckets.days_31_to_60, dec!(181.00)); + + let settlement_id: i64 = + sqlx::query_scalar("SELECT id FROM invoice_settlements WHERE invoice_id = ?") + .bind(v.invoice.id) + .fetch_one(&pool) + .await + .unwrap(); + invoice_settlements_write::cancel_settlement( + &pool, + seeded.admin_user_id, + seeded.company_id, + v.invoice.id, + settlement_id, + ) + .await + .expect("annulation du règlement"); + + let r = generate(&pool, seeded.company_id, as_of()).await.unwrap(); + assert_eq!(r.rows[0].buckets.days_31_to_60, dec!(1081.00)); + assert_eq!(r.totals.total, dec!(1081.00)); +} From 87676355321300c96aefb6a2c44963dca69d670d Mon Sep 17 00:00:00 2001 From: Guy Corbaz Date: Sun, 27 Sep 2026 16:47:55 +0200 Subject: [PATCH 34/34] docs(25-4-b1): revue close en 2 passes, gates verts, story done (refs #416) Co-Authored-By: Claude Opus 5.5 --- .../25-4-b1-residuel-aux-agregats.md | 37 +++++++++++++++++-- .../sprint-status.yaml | 2 +- .../tests/invoice_amount_due_parity.rs | 5 ++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md index 18ba5ab1..b75a5ff8 100644 --- a/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md +++ b/_bmad-output/implementation-artifacts/25-4-b1-residuel-aux-agregats.md @@ -1,6 +1,6 @@ # Story 25.4-b1 : Le résiduel aux agrégats — balance âgée et échéancier -Status: review +Status: done **Issue : [#416]** — cette story en livre la partie **agrégats** ; la partie **rappels** est la 25-4-b2. ⛔ **La PR de b1 porte `refs #416`, pas `closes`** : l'issue ne se ferme qu'avec b2. @@ -212,7 +212,25 @@ Sonnet, sur `bd06e789..87cbc8b4`. Brut : 4 + 4 + 2 findings ; après dédoublonn - [x] [Review][Defer] **`as_of` borne les tranches, pas les règlements** [crates/kesh-report/src/aged_receivables.rs:109] — deferred : limite écrite dans le doc-comment de `generate`, la route fixe `as_of` à aujourd'hui ; ne mord que si un appelant passait une date passée. - [x] [Review][Defer] **trois tests préexistants déstructurent `seed_base` dans le mauvais ordre** [crates/kesh-api/tests/invoice_echeancier_e2e.rs] — deferred, pre-existing : déjà relevé au Debug Log, verts parce que les deux identifiants valent 1. -Écartés : pré-remplissage négatif du dialogue de règlement (Blind + Edge — tranché hors périmètre par +*Passe 2 (2026-09-27) — les trois lentilles, Haiku ×3, sur le diff aplati `bd06e789..03951962`, +protocole complet (la passe 1 avait changé une règle métier). Brut : 8 + 0 + 0 ; après vérification +`grep -nF` : **0 au-dessus de LOW**.* + +- [x] [Review][Patch] **LOW — assertion d'anti-vacuité sans message** [crates/kesh-db/tests/invoice_amount_due_parity.rs:338] + +Écartés en passe 2 — **quatre faux positifs Haiku réfutés par `grep -nF`** : « CRITICAL » compteur +i18n +1 pour deux clés (la clé d'en-tête CSV n'est lue que côté backend — aucun `i18nMsg` du frontend +ne l'appelle, `grep -rnF echeancier-csv-header-amount-due frontend/src` vide) ; `id="settle-amount"` +prétendu absent (`SettleInvoiceDialog.svelte:213`) ; ordre de `seed_base` prétendu douteux (elle rend +`(seeded.admin_user_id, seeded.company_id)`, `invoice_echeancier_e2e.rs:113`) ; arrondi prétendu perdu +par `toFixed(2)` (tous les termes sont à deux décimales en amont, « 68.1000 » n'est que l'échelle +SQL). Plus : affirmation de T0 sur la 0.12.0 (fait établi par la spec) et trois remarques sans +défaut. ⚠️ L'Edge Case Hunter rendait 0 finding en **8 appels d'outils**, sans déclarer le manuel, les +locales ni le frontend : axes repris par l'orchestrateur — `{due}` jamais NULL (trois `COALESCE`, +`invoice_settlements.rs:106`), deux clés présentes dans les 4 locales, PDF aplati conforme au `.tex` +sur les dix passages « reste dû ». Rien trouvé. + +Écartés en passe 1 : pré-remplissage négatif du dialogue de règlement (Blind + Edge — tranché hors périmètre par la spec, et la garde `n <= 0` bloque la soumission) ; deux remarques LOW de l'Acceptance Auditor (piège de décompte `i18nMsg(` dans un commentaire, parité d'export couverte par un autre fichier que celui nommé) — constats sans défaut. @@ -325,7 +343,11 @@ de le saisir, et un cas « réglée en partie » est ajouté. - Backend : base remise à zéro, `scripts/test-fast.sh` **2491 / 2491** (2484 + 7). - Frontend : `check` 0 erreur (27 avertissements), `lint-i18n-ownership` PASS, `test:unit` **834 / 834** (826 + 8), build OK. -- E2E : sur `kesh_e2e` reconstruite, **225 passed / 8 failed / 19 skipped** — les 7 KF-029 (#97) +- **Après la boucle de revue** (dernier commit) : base remise à zéro, `scripts/test-fast.sh` + **2494 / 2494** (2484 + 10) ; frontend `check` 0 erreur, `lint-i18n-ownership` PASS, `test:unit` + **834 / 834**, build OK ; E2E sur binaire recompilé et `kesh_e2e` reconstruite **225 / 8 / 19**, + les mêmes huit que ci-dessous. +- E2E (avant revue) : sur `kesh_e2e` reconstruite, **225 passed / 8 failed / 19 skipped** — les 7 KF-029 (#97) et `product-revenue-account.spec.ts:133`, victime de pollution déjà répertoriée (`docs/testing.md` § « Les échecs attendus ») et **verte rejouée seule** (deux fois, 4/4). ⚠️ Un **premier run** avait rendu 214/19 : les 7 KF-029 et **12 échecs hors liste**, tous sur le @@ -359,6 +381,15 @@ de le saisir, et un cas « réglée en partie » est ajouté. ## Change Log +- **2026-09-27** — **Revue de code CLOSE en 2 passes, story `done`.** Tendance : passe 1 `2M/1L` + (Sonnet ×3) → passe 2 **0 au-dessus de LOW** (Haiku ×3, protocole complet, diff aplati ; 1 LOW + corrigé dans un test, 4 faux positifs réfutés par `grep -nF`). La dernière remédiation ne touche + aucune ligne de production. Gates complets verts au dernier commit (§ *Gates*). + ⚠️ **Un gate complet perdu, et pourquoi** : il a rougi sur `aged_compensating_…` avec le message de + la mutation, `git diff HEAD` vide. La mutation avait été défaite par `mv` d'une copie `cp` datée + d'AVANT son build : source juste mais plus ancienne que le binaire, cargo n'a pas recompilé. + `touch` puis gate rejoué : vert. Le gate ciblé et clippy de la passe 1 avaient été lancés AVANT la + mutation, ils restent valables. - **2026-09-27** — **Revue de code, passe 1** (Blind Hunter, Edge Case Hunter, Acceptance Auditor — Sonnet ×3, `bd06e789..87cbc8b4`) — **2 MEDIUM, 1 LOW à corriger**, 2 différés, 3 écartés (§ *Review Findings*). Corrigés : diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 4f22f332..439e699d 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -356,7 +356,7 @@ development_status: 25-3-b-annuler-rapprochement: done # 2026-09-25 DONE — revue close en passe 2 (Sonnet 1 MED -> Haiku 0) ; gates : backend 2463/2463, frontend 777/777, E2E sans regression. Non poussee. — SPEC VALIDEE (boucle close en passe 4 : 2 HIGH -> 2 MED -> 2 MED -> 3 LOW ; pas de decoupage, arbitrage de Guy). Q1-Q3 tranchees. — [#418] closes — annuler un rapprochement bancaire. ⛔ NE JAMAIS compter sur le `ON DELETE SET NULL` de `matched_entry_id` : supprimer l'ecriture ferait disparaitre le lien EN SILENCE, le mode d'echec meme que la story repare. ⚠️ Le chemin d'ACCEPTATION n'a aucune fonction `kesh-db` a mirer : il est ecrit en SQL brut a **cinq** sites de `routes/reconciliation.rs`, dont **un seul** porte le verrou optimiste. Quand la transaction porte une facture, appelle le geste de la 25-3-a. Derriere elle. 25-3-annuler-reglement-et-rapprochement: split # [#414] [#418] — **l'Epic 24 a livre l'encaissement et le rapprochement ; il n'a PAS livre le moyen de les DEFAIRE.** Une erreur d'imputation est aujourd'hui incorrigible autrement qu'a la main — et le gel de la 24-4b interdit precisement la main. ⚠️ *C'est le prix d'une story qui ferme une porte sans ouvrir la suivante : le gel est juste, et il rend cette story-ci necessaire.* 25-4-a-residuel-juste: done # 2026-09-27 REVUE CLOSE EN 1 PASSE ; PR #472 OUVERTE (closes #455 #456), attend « merge ». # IMPLEMENTEE — 7/7 mutations tuees ; gates 2484/2484, 826/826, E2E 222/10/19 sans regression ; prochaine etape : code review. # 2026-09-26 [#455] [#456] — l'avoir soustrait HT d'un TTC (masque a l'ecran, mais rendu par GET /invoices/{id} aux cles API : amountDue = la TVA sur une facture creditee) ; un avoir accepte sur une facture partiellement reglee rend la creance creditrice. CREEE ; validation : P1 Sonnet 1H/3M/1L -> P2 Haiku 1M retenu -> P3 ciblee Opus 2M/5L -> P4 ciblee Sonnet 0 > LOW (1M reclasse LOW) : VALIDEE en 4 passes. Arbitrage 2026-09-27 : refus maintenu, levee tracee par #471. - 25-4-b1-residuel-aux-agregats: review # 2026-09-27 DEV FAIT, gates verts (E2E 225/8 conforme) ; revue de code a lancer. # [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L -> P2 Haiku 0 (repris : 1M) -> P3 ciblee Opus 3M/3L : portee de l'invariant REECRITE PAR UNE REGLE ; issues #473 #474 ouvertes -> P4 1M -> P5 0 : VALIDEE en 5 passes. Signal MED->MED : pas de decoupage (Guy, 2026-09-27). Branche EMPILEE sur 25-4-a. + 25-4-b1-residuel-aux-agregats: done # 2026-09-27 revue CLOSE en 2 passes (2M/1L -> 0 >LOW), gates complets verts (2494, 834, E2E 225/8). # [#416, refs] balance agee + echeancier (totaux, colonne reste du, CSV, statut partiel, dialogue pre-rempli) + invariant balance agee = grand livre. CREEE ; validation P1 Sonnet 2M/3L -> P2 Haiku 0 (repris : 1M) -> P3 ciblee Opus 3M/3L : portee de l'invariant REECRITE PAR UNE REGLE ; issues #473 #474 ouvertes -> P4 1M -> P5 0 : VALIDEE en 5 passes. Signal MED->MED : pas de decoupage (Guy, 2026-09-27). Branche EMPILEE sur 25-4-a. 25-4-b2-residuel-aux-rappels: backlog # [#416, closes] le rappel reclame le reste du (texte, QR, PDF montant initial / deja regle / reste), frais configurables masques a zero ; point ouvert : frais non comptabilises. 25-4-b-residuel-aux-agregats: split # 2026-09-27 DECOUPEE en b1 (agregats) / b2 (rappels) — six modules apres les arbitrages Q1. # [#416] + montant des RELANCES (TTC complet, trouve a l'inventaire) — Q1 TRANCHEE 2026-09-27 : la QR du rappel porte le RESTE DU (= TTC sans reglement), reste a payer sur le PDF et dans le texte ; point ouvert : frais de rappel non comptabilises vs trop-percu. 25-4-c-residuel-au-rapprochement: backlog # [#420] — DEUX sites : filtre SQL des candidats (HAVING total_ttc) avant le score, puis score et re-score a l'acceptation. diff --git a/crates/kesh-db/tests/invoice_amount_due_parity.rs b/crates/kesh-db/tests/invoice_amount_due_parity.rs index 885fa1f1..835fb88c 100644 --- a/crates/kesh-db/tests/invoice_amount_due_parity.rs +++ b/crates/kesh-db/tests/invoice_amount_due_parity.rs @@ -335,7 +335,10 @@ async fn settled_and_credited_forms_are_at_parity(pool: MySqlPool) { joined.iter().any(|(_, d)| *d < Decimal::ZERO), "état hérité : reste dû négatif" ); - assert!(joined.iter().any(|(_, d)| *d > Decimal::ZERO)); + assert!( + joined.iter().any(|(_, d)| *d > Decimal::ZERO), + "anti-vacuité : au moins un reste dû positif" + ); } // ─── Story 25-4-b1 (#416) — l'échéancier porte le reste dû ────────────────────