fix(txid): normalize bank tx ids to fit Dolibarr's num_payment varchar(50) #37

Merged
arcodange merged 1 commits from arcodange/txid-varchar50-normalize into main 2026-07-11 18:05:58 +02:00
Owner

Problème (vérifié LIVE sur la sandbox, 2026-07-11)

Les ids de transaction Qonto font ~67 caractères (arcodange-1246-1-transaction-019f14c5-e254-7ac9-9e9f-307ed9-d55f44) alors que Dolibarr stocke num_payment en varchar(50) (llx_paiement.num_paiement, llx_paiementfourn.num_paiement). Un POST /supplierinvoices/{id}/payments avec l'id complet échoue en HTTP 400 « value too long for type character varying(50) ». Les ids Wise (numériques courts, ex. 2159468139) passent.

Parade appliquée en live : stocker le suffixe UUID (strip du préfixe <org>-<n>-<n>-transaction-, reste ~37 chars, globalement unique) — 4 règlements fournisseurs enregistrés ainsi sur la sandbox (num ex. 019f14c5-e254-7ac9-9e9f-307ed9-d55f44). Cette PR fait de cette forme courte le format canonique, des deux côtés.

Côté écriture — dolibarr-sandbox-write/scripts/payment-record.sh

  • Normalise transaction_id avant POST : strip ^.*transaction- (pattern Qonto), les ids Wise passent inchangés.
  • Si le résultat dépasse encore 50 chars → erreur explicite citant varchar(50), avant tout POST — jamais de troncature silencieuse.
  • La normalisation est annoncée sur stderr ; la sortie JSON (transaction_id) émet le num normalisé réellement stocké (y compris sur le fallback de corrélation).

Côté lecture — arcodange-bank-reco/scripts/bank-match.sh (PASS 0, erp#28)

  • Les feed ids Qonto sont portés sous les deux formes (longue brute + courte canonique) et le num Dolibarr est comparé brut ET normalisé → un règlement stocké en forme courte (varchar(50)) ou longue (historique) matche dans tous les cas. Ids Wise intouchés.
  • Nouveau mode offline --fixtures DIR (aucun credential, aucun réseau) pour prouver le matcher sur fixtures — les pulls live sont inchangés hors de ce mode.

Docs

Les deux SKILL.md documentent le format canonique court + la contrainte varchar(50) (et l'exemple de payment-record.sh montre un id Qonto réel → num stocké court).

Vérifications faites (lecture seule — rien écrit vers sandbox/prod)

  • bash -n OK sur les deux scripts (aussi rejoué dans chaque runner).
  • arcodange-bank-reco/tests/run-tests.sh → PASS : fixture txid-normalize (feed long ↔ num court, feed long ↔ num long back-compat, Wise numérique — chaque paire à Δ+19j, hors fenêtre ±7j, donc seul le PASS 0 peut les apparier) → exit 0, 3×[tx-id], buckets propres ; fixture txid-no-num (num vide) → exit 1, 0 matched.
  • dolibarr-sandbox-write/tests/run-tests.sh → PASS : payment-record.sh exercé de bout en bout via un stub dol-write.sh injecté par le hook DOL_WRITE (long→court dans le body POST + JSON de sortie, Wise inchangé sans annonce, id >50 après normalisation refusé avant tout POST avec varchar(50) dans le message).
  • Sortie fixture-mode observée : les 3 paires matchées ↔[tx-id] avec Δ+19d, exit 0.
  • Non touché (travail parallèle) : test/scripts/admin/permissions.ts, .claude/skills/dolibarr-sandbox-checkpoint/scripts/*.

🤖 Generated with Claude Code

## Problème (vérifié LIVE sur la sandbox, 2026-07-11) Les ids de transaction **Qonto** font ~67 caractères (`arcodange-1246-1-transaction-019f14c5-e254-7ac9-9e9f-307ed9-d55f44`) alors que Dolibarr stocke `num_payment` en **varchar(50)** (`llx_paiement.num_paiement`, `llx_paiementfourn.num_paiement`). Un `POST /supplierinvoices/{id}/payments` avec l'id complet échoue en HTTP 400 « value too long for type character varying(50) ». Les ids **Wise** (numériques courts, ex. `2159468139`) passent. Parade appliquée en live : stocker le **suffixe UUID** (strip du préfixe `<org>-<n>-<n>-transaction-`, reste ~37 chars, globalement unique) — 4 règlements fournisseurs enregistrés ainsi sur la sandbox (num ex. `019f14c5-e254-7ac9-9e9f-307ed9-d55f44`). Cette PR fait de cette forme courte le **format canonique**, des deux côtés. ## Côté écriture — `dolibarr-sandbox-write/scripts/payment-record.sh` - Normalise `transaction_id` avant POST : strip `^.*transaction-` (pattern Qonto), les ids Wise passent inchangés. - Si le résultat dépasse encore 50 chars → **erreur explicite citant varchar(50), avant tout POST** — jamais de troncature silencieuse. - La normalisation est annoncée sur stderr ; la sortie JSON (`transaction_id`) émet le **num normalisé** réellement stocké (y compris sur le fallback de corrélation). ## Côté lecture — `arcodange-bank-reco/scripts/bank-match.sh` (PASS 0, erp#28) - Les feed ids Qonto sont portés sous **les deux formes** (longue brute + courte canonique) et le `num` Dolibarr est comparé **brut ET normalisé** → un règlement stocké en forme courte (varchar(50)) **ou** longue (historique) matche dans tous les cas. Ids Wise intouchés. - Nouveau mode offline `--fixtures DIR` (aucun credential, aucun réseau) pour prouver le matcher sur fixtures — les pulls live sont inchangés hors de ce mode. ## Docs Les deux `SKILL.md` documentent le format canonique court + la contrainte varchar(50) (et l'exemple de `payment-record.sh` montre un id Qonto réel → num stocké court). ## Vérifications faites (lecture seule — rien écrit vers sandbox/prod) - `bash -n` OK sur les deux scripts (aussi rejoué dans chaque runner). - **`arcodange-bank-reco/tests/run-tests.sh`** → PASS : fixture `txid-normalize` (feed long ↔ num court, feed long ↔ num long back-compat, Wise numérique — chaque paire à Δ+19j, hors fenêtre ±7j, donc seul le PASS 0 peut les apparier) → exit 0, 3×`[tx-id]`, buckets propres ; fixture `txid-no-num` (num vide) → exit 1, 0 matched. - **`dolibarr-sandbox-write/tests/run-tests.sh`** → PASS : `payment-record.sh` exercé de bout en bout via un stub `dol-write.sh` injecté par le hook `DOL_WRITE` (long→court dans le body POST + JSON de sortie, Wise inchangé sans annonce, id >50 après normalisation refusé **avant** tout POST avec varchar(50) dans le message). - Sortie fixture-mode observée : les 3 paires matchées `↔[tx-id]` avec `Δ+19d`, exit 0. - Non touché (travail parallèle) : `test/scripts/admin/permissions.ts`, `.claude/skills/dolibarr-sandbox-checkpoint/scripts/*`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
arcodange added 1 commit 2026-07-11 17:49:16 +02:00
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]>
arcodange merged commit 0d66d6a6dc into main 2026-07-11 18:05:58 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: arcodange-org/erp#37