62fdd2325b8ad775e39ef912bebbd9f30ad3b8aa
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
62fdd2325b |
feat(erp): les deux comptes bancaires tombent au centime — reprise FAC004/FAC006, réparation FAC009, cashbacks
Suite de la réconciliation ouverte par la question de l'opérateur. Après ce
passage, l'ERP et la banque disent le même chiffre sur les deux comptes :
Qonto 2 013,30 EUR et Wise 11 863,85 EUR, écart 0,00.
CE QUI A DÉBLOQUÉ LE RESTE. L'opérateur a établi que KissMetrics n'a JAMAIS reçu
la moindre facture — le client payait le forfait de 2 500 USD sans pièce. Rien
ne s'opposait donc à corriger des factures qu'on croyait entre ses mains.
FAC004 et FAC006 portaient toutes deux 2 145,92 EUR, le même montant repris tel
quel d'un cycle à l'autre, alors que la banque a reçu 2 147,00 EUR le 29/05 et
2 195,97 EUR le 25/06 : 2 500 USD à chaque fois, au taux du jour du paiement.
51,13 EUR d'encaissement manquaient aux livres. Leurs notes qualifiaient déjà
l'écart d'« écart de change » — défendable tant qu'on ne pouvait pas toucher aux
factures ; plus vrai maintenant qu'on le peut. Une facture, un règlement, une
ligne bancaire, le même chiffre.
RÉPARATION DE FAC009. `PUT /invoices/{id}/lines/{lid}` n'est PAS un PATCH : tout
champ absent du corps est remis à zéro. La correction appliquée ce matin ne
passait que subprice, pu_ht, qty et desc — `product_type` est donc retombé de 1
(service) à 0 (produit), faisant de FAC009 la seule ligne du registre typée
« produit » dans une société qui ne vend que des prestations et facture hors UE
sous l'art. 259-1° du CGI. Rétabli.
C'est le juge pré-gate qui l'a vu, sur la répétition de FAC004 et FAC006 — où le
même appel avait en plus effacé `desc` ENTIÈREMENT. Premier finding de la
journée qui apporte quelque chose de réel, et il aurait envoyé au client deux
factures sans désignation. Les charges de ligne sont désormais complètes.
test/deleteInvoicePayment.ts. Corriger une facture encaissée suppose de refaire
son règlement, et l'API REST ne sait pas le supprimer : `PUT
/invoices/{id}/payments` ne met à jour que son NUMÉRO. Deux verrous dans
l'interface, consignés dans le fichier : tant que la facture est marquée payée
le lien de suppression est simplement ABSENT — pas d'erreur, rien — il faut
d'abord la rouvrir ; et la confirmation est une boîte MODALE jQuery dont les
boutons n'ont ni name ni value, si bien qu'un sélecteur sur input[type=submit]
ne trouve rien et que la suppression n'a pas lieu, silencieusement. Sans
--payment, le script retrouve le règlement sur la fiche : coder en dur un
identifiant relevé en bac à sable est une façon commode de supprimer le mauvais.
ABONNEMENT ANTHROPIC D'AOÛT. 90,00 EUR du 19/08, reçu 2330-6710-2536 arrivé dans
books@ le 24/08. Second abonnement à usage interne, distinct du budget IA client.
CASHBACKS WISE — compte 768. Wise parle de « remise », mais le montant suit le
SOLDE et non les frais : 0,19 EUR sur ~510 EUR en mars, 4,97 EUR sur ~9 700 EUR
en août, soit ~0,5-0,6 % l'an, stable. C'est la rémunération d'un solde, donc un
produit financier. Le seul frais Wise de l'exercice est le forfait de 50 EUR du
26/01, sans rapport. Ni 763, qui vise les revenus de créances commerciales, ni
764, réservé aux valeurs mobilières de placement : 768.
RISTOURNE QONTO — compte 627 au crédit. L'opération est typée `qonto_fee` par la
banque elle-même, côté crédit : une remise sur un service reçu vient en
diminution de la charge, elle ne crée pas un produit.
PIÈGE CATALOGUÉ. bank-match.sh continuera d'afficher ces sept écritures en
BANK-ONLY : il ne rapproche que les RÈGLEMENTS de factures et ignore les
paiements divers. Les voir listées ne veut donc PAS dire qu'elles manquent —
c'est écrit dans known-patterns.json, sans quoi le prochain agent les
ressaisirait.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
||
|
|
3f0955a2d3 |
fix(bank-reco): l'annotation URSSAF contredisait la doctrine — 641, pas 645 ni 646 (#93)
Co-authored-by: Gabriel Radureau <[email protected]> |
||
|
|
81cc3df13c |
feat(erp): enregistrer les charges sociales — script + runbook rationalisés
Dolibarr n'expose AUCUNE API REST pour les charges sociales (/taxes, /socialcontributions, /chargesociales répondent tous « API not found »). Le module est actif, seule l'API manque : le pipeline de promotion, qui parle REST, ne peut pas porter cette opération. D'où un script UI, gardé par guard.ts. La sandbox a joué son rôle : quatre doublons y ont été créés pendant la découverte, sans conséquence, et les quatre pièges du formulaire sont désormais documentés au lieu d'être redécouverts. - recordSocialCharge.ts : idempotent (cherche la charge avant de créer), vérifie par LECTURE de la liste, type TNS par défaut, --dry-run. - RUNBOOK_charges_sociales.md : écrit pour être suivi par un agent moins performant ou un harness limité — la commande, les garanties, les quatre pièges, et ce que le script ne garantit PAS (ni juge, ni artefact de gate). Les quatre pièges, tous rencontrés : 1. La date visible est décorative : le backend ne lit que les champs CACHÉS echday/echmonth/echyear. Remplir le champ texte crée l'enregistrement avec une période aberrante (20/06/2000 observé) au lieu d'échouer. 2. Le bouton de soumission n'a pas d'attribut name — le cibler par value. 3. L'URL après soumission ne porte pas d'id : vérifier par l'URL fait conclure à un échec sur une création réussie. C'est ce qui a produit les doublons. 4. Les milliers portent une espace insécable (« 1 215,00 ») : une comparaison littérale casse au-delà de 999 € et l'idempotence saute en silence. Correction comptable : 645x → 646 dans known-patterns.json. Les cotisations d'un gérant TNS sont des cotisations personnelles du dirigeant (646), pas des cotisations patronales sur salaires (645) — Arcodange n'a aucun salarié. Appliqué en production : les trois échéances URSSAF 2026, toutes IMPAYÉES. Reste à vérifier dans le dictionnaire Dolibarr que le type « indépendants » porte bien le code comptable 646. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01VRShc4QhLLU73FLHx9vskh |
||
|
|
6b576931b6 |
fix(bank-reco): known-patterns.json — Mistral is annual, not monthly
- MISTRAL.AI: update note to reflect annual subscription (Le Chat Pro - Annual) from invoice MSTRL-API-814045-001, 2026-04-02, 143.90 HT / 172.68 TTC. Next expected ~2027-04. - CLAUDE.AI: document payment rail moved to personal card (fk_account=3, API-invisible) for May/June; reference issue #57. Both AI subscriptions are now recorded supplier invoices (post-replay). Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <[email protected]> |
||
|
|
f9d83037b6 |
fix(txid): normalize bank tx ids to fit Dolibarr's num_payment varchar(50)
Qonto transaction ids run ~67 chars (<org>-<n>-<n>-transaction-<uuid>) but Dolibarr stores num_payment in varchar(50) (llx_paiement.num_paiement, llx_paiementfourn.num_paiement) — POSTing a payment with the raw id fails HTTP 400 "value too long for type character varying(50)". Parade proven live on the sandbox (2026-07-11): store the UUID suffix (globally unique, ~37 chars). Wise ids (short numerics) are unaffected. Writer side — payment-record.sh strips everything through "transaction-" before POST, announces the normalization on stderr, REFUSES (never truncates) ids still >50 chars after normalization, and emits the normalized num in the output JSON. Reader side — bank-match.sh PASS 0 (exact tx-id, erp#28) now compares BOTH sides in raw AND canonical short form: Qonto feed ids are carried long+short, payment nums are normalized on compare — so nums stored short (the varchar(50) form) and historical long-form nums both keep matching. Wise ids untouched. Proven offline (no credentials, no network, no sandbox/prod writes): - arcodange-bank-reco/tests/run-tests.sh — new bank-match --fixtures offline mode: long feed id ↔ short num, long ↔ long (back-compat), Wise numeric, each Δ+19d outside the ±7d window so only PASS 0 can pair them (exit 0, 3×[tx-id]); plus the empty-num negative (exit 1, 0 matched). - dolibarr-sandbox-write/tests/run-tests.sh — payment-record via a stubbed dol-write.sh (DOL_WRITE hook): long→short in POST body + output JSON, Wise untouched, >50-after-normalization refused BEFORE any POST, citing varchar(50). Both SKILL.md document the canonical short form + the varchar(50) constraint. Co-Authored-By: Claude Fable 5 <[email protected]> |
||
|
|
4b17e5f22c |
feat(bank-match): exact tx-id match pass (consumes payment transaction_id)
The consumer side of erp#26/#27: now that a règlement stores its originating bank transaction id (transaction_id -> llx_bank.num_chq), bank-match uses it. New PASS 0 (exact), highest priority, before wire-ref and amt+date: - carry each feed movement's own id (Qonto transaction id; Wise activity + transfer resource id) as feed_ids, and each Dolibarr payment's num. - match when a payment's num equals a feed id. Tagged [tx-id]. - DATE-WINDOW-INDEPENDENT — the id is proof, so it pairs movements whose bank settlement and Dolibarr saisie are weeks apart (which amt+date would miss). Pass 0 runs before the ref index is built, so its matches are excluded from the later passes (no double-match). Fixture-proven: a payment dated 15d off the bank movement (outside the ±7d window) matches via [tx-id] when num carries the Qonto id, and correctly does NOT match when num is empty. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> |
||
|
|
246c7fc5a9 |
arcodange-bank-reco V7: avoir netting + fk_account context + wire-ref matching
Three improvements that reduce the V6.1 exit-1 signal from 10 to 1 on
the current Arcodange baseline. Every bucket now has a single, clear
purpose; the only entry counted as a failure is a genuine action item.
A. fk_account context on dolibarr-only
- Fetches /bankaccounts and tags each dolibarr-only with the account
ref + label (e.g. "CCA1 (G.RADUREAU Compte Courant Asso)").
- Splits dolibarr-only into "on API-tracked accounts" (QON*/WIS* — real
gaps) vs "not in API scope" (CCA1 / personal — expected gaps).
- Personal-account entries no longer count toward the failure verdict.
B. Avoir-cycle netting
- Pairs AVC entries of -X on socid S with FAC entries of +X on the
same socid within ±5d.
- Both surface in a dedicated AVOIR-NETTED bucket and are excluded from
dolibarr-only, since the bank only sees the net of the cycle.
- Resolves the V6.1 noise where AVC001-CL0001001 + FAC001-CL00001
appeared as fake gaps for a 510€ cancel-and-reissue dance.
C. Wire-reference strong matching (--enrich flag, opt-in)
- When --enrich is passed, bank-match.sh fetches /v1/transfers/{id}
per Wise TRANSFER and reads the wire `reference` field.
- References containing a FAC\d+(CL\d+)? pattern strong-match against
the corresponding Dolibarr customer invoice (annotated [wire-ref]
vs the loose [amt+date] kind).
- Verified on FAC002 5100€: KM's wire memo "FOR INVOICE FAC002CL0001002"
gives an unambiguous match independent of date drift.
Baseline (Jan-May 2026, --enrich on):
6 matched · 1 internal · 2 avoir-netted · 7 bank-known · 1 bank-UNKNOWN
0 dol-only-API · 7 dol-only-personal
→ exit-1 count = 1 (just the +2147€ KM Wise 2026-05-29 to record).
The CLI (bin/arcodange) gains --enrich on the match subcommand. The
SKILL.md has a new "V7 bucket structure" section explaining the seven
buckets and a before/after table showing the signal/noise improvement.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
||
|
|
4b6a5f7529 |
arcodange-bank-reco: add known-patterns.json catalog + bank-match annotation
V6.1 follow-up to the bank-reco V6 ship. Splits the BANK-ONLY bucket into "known patterns" (intentional gaps, documented and classified) vs "unknown" (real action items). What the catalog covers today: - FOUREZ Quentin → capital_deposit (apport en capital 1000 € initial, notaire FOUREZ centralisateur du dépôt). Maps to Dolibarr account 1013. - URSSAF → social_charges (account 645100) - MISTRAL.AI, CLAUDE.AI → ai_subscription (account 6262) - Wise *Plan, qonto_fee → bank_fee (account 627) - BALANCE_DEPOSIT / FEATURE_CHARGE on Wise → internal_topup (self-funding pair, often nets to zero) Effect on the V6 baseline (Jan-May 2026): - Before catalog: 8 BANK-ONLY mixed entries (noise + signal) - After catalog: 7 known + 1 UNKNOWN (just the +2147 € KM Wise payment 2026-05-29 that genuinely needs a Dolibarr entry) The catalog is JSON (not YAML — stdlib only, no dependency). Schema documented in SKILL.md. Pattern matches case-insensitive regex against both bank label AND operation type. Optional filters: bank, side, amount_min, amount_max. Exit code now reflects only the UNKNOWN bank-only and dolibarr-only counts — the verdict is no longer noisy because of intentional gaps. Edit known-patterns.json as new recurring patterns emerge. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> |
||
|
|
bd90266372 |
add arcodange-bank-reco — Qonto + Wise reconciliation against Dolibarr
V6 — the first cross-system skill (under arcodange-* not dolibarr-*).
Closes the loop between what Dolibarr says (ERP-internal) and what the
bank actually saw.
What ships:
- arcodange-bank-reco/scripts/bank-curl.sh unified read-only wrapper for Qonto + Wise
- arcodange-bank-reco/scripts/bank-probe.sh auth + discovery (org slug, profile id, balances)
- arcodange-bank-reco/scripts/qonto-transactions Qonto txn lister with pagination + filters
- arcodange-bank-reco/scripts/wise-transactions Wise activity lister with --enrich for wire refs
- arcodange-bank-reco/scripts/bank-match.sh 3-bucket reconciliation (matched/bank-only/dol-only)
with internal Wise↔Qonto consolidation detection
- arcodange-bank-reco/scripts/bank-balance.sh live balances + Dolibarr cumulative-by-fk_account
The headline bank-curl.sh is SCA-aware (Wise RSA dance) even though we
don't end up using it: the EU statement endpoint is region-blocked
("Funding transfers and retrieving balance statements via API are not
supported except for accounts based in the US, Canada, Australia, New
Zealand, Singapore, and Malaysia" per Wise docs). The wrapper supports
SCA so when/if Wise opens it, we're ready.
The pivot that unblocked Wise incoming: /v1/profiles/{pid}/activities
(documented at https://docs.wise.com/api-reference/activity/activitylist.md)
returns ALL movements in a unified HTML-tagged feed, no SCA required.
Parsing strips the HTML and recovers structured amount/sign/currency.
CLI integration:
- bin/arcodange bank {probe,qonto-transactions,wise-transactions,match,balance,curl}
- dolibarr/SKILL.md catalogue + Pointers updated
- dolibarr/README.md env schema extended with QONTO_*, WISE_*
Live baseline findings to raise with the cohort review (captured in
examples/bank-match-2026-01-to-05.txt):
- Wise 2026-05-29 +2147 EUR Kissmetrics NOT YET in Dolibarr
- Qonto bank-only: MISTRAL.AI 172.68, CLAUDE.AI 180, URSSAF 493, FOUREZ +1000
- 6 movements matched cleanly across Jan-May 2026
- Wise→Qonto 5000 EUR consolidation on 2026-03-13 auto-detected as internal
- Live balance: Qonto 4191.54 + Wise 5308.25 = 9499.79 EUR
V7 candidates noted in SKILL.md out-of-scope: reference-based matching
via the Wise --enrich wire refs (FOR INVOICE FAC***), multi-row Dolibarr
sub-payment aggregation, smarter avoir cycle handling.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|