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]>
This commit is contained in:
2026-07-11 17:48:35 +02:00
co-authored by Claude Fable 5
parent 3045d50626
commit f9d83037b6
21 changed files with 349 additions and 41 deletions
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Offline fixture tests for bank-match.sh — no credentials, no network, no writes.
# Proves the PASS 0 tx-id normalization (varchar(50) canonical short form):
# 1. txid-normalize — a long Qonto feed id (~67 chars) matches a règlement whose
# num was stored SHORT (UUID suffix, what payment-record.sh stores) AND one
# stored LONG (historical); a Wise numeric id matches unchanged. Every pair
# is ~19 days apart — far outside the ±7d window — so only the id-based
# PASS 0 can pair them. Expect exit 0, 3 × [tx-id].
# 2. txid-no-num — same bank movement but the payment has num="" → must NOT
# match (the id is proof; its absence isn't). Expect exit 1, 0 matched.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BM="${SCRIPT_DIR}/../scripts/bank-match.sh"
fail() { echo "FAIL: $*" >&2; exit 1; }
count() { grep -c "$1" <<<"$2" || true; }
bash -n "${BM}" || fail "bash -n bank-match.sh"
# --- Case 1: long-Qonto-id ↔ short-num (+ long-num back-compat + Wise) ---
OUT="$(bash "${BM}" --fixtures "${SCRIPT_DIR}/fixtures/txid-normalize" \
--since 2026-06-01 --until 2026-06-30)" \
|| fail "txid-normalize: expected exit 0, got $?"
[[ "$(count '↔\[tx-id\]' "${OUT}")" == 3 ]] || fail "txid-normalize: expected 3 [tx-id] matches
${OUT}"
grep -q 'FS-OVH-2606' <<<"${OUT}" || fail "txid-normalize: long feed id ↔ SHORT num (the varchar(50) form) did not match"
grep -q 'FS-SCW-2606' <<<"${OUT}" || fail "txid-normalize: long feed id ↔ LONG num (historical form) did not match"
grep -q 'FAC003-CL0001003' <<<"${OUT}" || fail "txid-normalize: Wise numeric id match broken"
grep -q '# 3 matched, 0 internal, 0 avoir-netted, 0 bank-known, 0 bank-UNKNOWN, 0 dol-only-API' <<<"${OUT}" \
|| fail "txid-normalize: unexpected bucket counts
${OUT}"
# --- Case 2: payment without num must not tx-id-match ---
rc=0
OUT2="$(bash "${BM}" --fixtures "${SCRIPT_DIR}/fixtures/txid-no-num" \
--since 2026-06-01 --until 2026-06-30)" || rc=$?
[[ "${rc}" == 1 ]] || fail "txid-no-num: expected exit 1, got ${rc}"
grep -q '# 0 matched' <<<"${OUT2}" || fail "txid-no-num: nothing should match
${OUT2}"
[[ "$(count '↔\[tx-id\]' "${OUT2}")" == 0 ]] || fail "txid-no-num: empty num must not produce a [tx-id] match"
echo "OK: bank-match fixture tests passed (3 tx-id matches incl. long↔short + long↔long + Wise; empty-num negative)"