--- name: dolibarr-payments-state description: Track Arcodange cash receipts and payment-state per invoice via the Dolibarr API. Three workflows — (1) per-invoice payment reconciliation for KissMetrics: total TTC vs sum of payments, with PARTIAL / UNPAID / OK / OVERPAID classification (replaces relying on the `paye=1` flag alone); (2) full payment timeline sorted by date with cumulative balance, suitable for cohort-review deferred-cycle tracking (compare actual cash receipts against the contractual schedule); (3) monthly cash-receipt aggregation, scoped to KissMetrics by default or `--all-clients` for global accounting. Surfaces credit notes (AVOIRs as negative payments) and the bank-account catalogue (QONTO, WISE EURO, G.RADUREAU CCA). Use when the user asks "état des paiements KM", "is invoice X paid", "qui doit combien", "suivi des paiements deferred", "deferred 9-month cycle", "cash receipts par mois", "réconciliation factures vs paiements", or any cohort-review billing-cycle check. Depends on the `dolibarr` skill for connection. SKIP for the legal-mention audit (handled by `dolibarr-invoice-audit`), for write operations (the API is read-only — payments are recorded by the Dolibarr UI), and for full bank-statement reconciliation (which would require parsing bank exports outside Dolibarr — out of scope here, V3 candidate). requires: bins: ["curl", "jq", "python3"] auth: true --- # dolibarr-payments-state — KissMetrics cash-receipt tracking This skill answers "**who owes Arcodange what, and when did they pay?**" against the live Dolibarr. It's the natural follow-up to [dolibarr-invoice-audit](../dolibarr-invoice-audit/SKILL.md): that one validates that a given invoice was emitted correctly; this one validates the money landed. Depends on the [dolibarr](../dolibarr/SKILL.md) base skill for the connection, the `.env`, and the `voir_tous` permission flags. The existing **Factures → Voir toutes** flag from V1 is sufficient — payments are nested under invoices (`/invoices/{id}/payments`), and there's no `/payments` list-all endpoint (501 Not Implemented). ## Why a dedicated skill? The `paye` flag on `/invoices/{id}` is a single boolean. The cohort review needs the actual reconciliation: how much was paid, when, by what means, on which bank account. Three things the flag can't tell you: 1. **Partial payments.** An invoice with one of three deferred installments paid still has `paye=0` but `sum(payments) > 0`. 2. **Credit notes.** An AVOIR shows up in the payments list as a negative amount tied to the canceled invoice's pair — the timeline is the easy way to see the chain. 3. **Cash-receipt timing.** For deferred-cycle agreements, the **date** a payment hit the bank matters more than whether the invoice is fully paid. ## Prerequisites - [`dolibarr/.env`](../dolibarr/.env) populated, mode 600. - `ai_agent` permissions from [dolibarr/README.md](../dolibarr/README.md). The existing Factures `voir_tous` covers payments. ## Workflow 1 — Per-invoice payment state (reconciliation) ```bash ./scripts/km-payment-state.sh # all KM invoices ./scripts/km-payment-state.sh --since 2026-02-01 # window echo "exit: $?" # 0 if every invoice reconciles, 1 otherwise ``` Live output (captured at [examples/km-payment-state.txt](examples/km-payment-state.txt)): ``` id ref date HT paid balance state payments ---------------------------------------------------------------------------------------------------------------------------------- 12 FAC002-CL0001002 2026-02-24 5100.00 5100.00 0.00 OK 5100.00@2026-03-12(VIR) 13 FAC003-CL0001003 2026-02-24 2550.00 2550.00 0.00 OK 2550.00@2026-04-20(VIR) 2 FAC001-CL00001 2026-01-30 510.00 510.00 0.00 OK 510.00@2026-02-05(VIR) 11 FAC001-CL0001001 2026-01-30 510.00 510.00 0.00 OK 510.00@2026-02-05(VIR) 10 AVC001-CL0001001 2026-01-30 -510.00 -510.00 0.00 OK -510.00@2026-02-05(VIR) ---------------------------------------------------------------------------------------------------------------------------------- TOTAL 8160.00 8160.00 0.00 # 5 invoice(s), 0 not fully reconciled ``` **state column legend:** - `OK` — `|balance| < 0.005` (reconciled to the cent). - `UNPAID` — no payments recorded at all. - `PARTIAL` — payments < TTC. - `OVERPAID` — payments > TTC. Worth investigating (overpayment, duplicate posting, currency drift). Reconciles against **TTC** (the contractual payable), not HT, because that's what the customer actually sends. For KM under 259-1° CGI (TVA=0), HT == TTC, so the distinction doesn't matter; for any future French customer with TVA > 0, it would. ## Workflow 2 — Payment timeline (cohort deferred-cycle tracking) ```bash ./scripts/km-payment-timeline.sh # all-time ./scripts/km-payment-timeline.sh --year 2026 # one year ./scripts/km-payment-timeline.sh --since 2026-02-01 --until 2026-04-30 ``` Live output (captured at [examples/km-payment-timeline.txt](examples/km-payment-timeline.txt)): ``` date invoice amount type ref bank_line running -------------------------------------------------------------------------------------------------------------- 2026-02-05 AVC001-CL0001001 -510.00 VIR REC003-CL00001 bl=7 -510.00 2026-02-05 FAC001-CL0001001 510.00 VIR REC002-####002 bl=6 0.00 2026-02-05 FAC001-CL00001 510.00 VIR REC001-####001 bl=5 510.00 2026-03-12 FAC002-CL0001002 5100.00 VIR REC003-CL00002 bl=19 5610.00 2026-04-20 FAC003-CL0001003 2550.00 VIR REC001-CL0001001 bl=24 8160.00 -------------------------------------------------------------------------------------------------------------- # 5 payment(s), net cash receipts: 8160.00 # Bank accounts known to this Dolibarr (label / IBAN-leading): # id=1 ref=QON1 label=QONTO country=FR iban=FR761695800001... # id=2 ref=WIS2 label=WISE EURO country=BE iban=BE58967543094979 # id=3 ref=CCA1 label=G.RADUREAU Compte Courant Asso country=FR iban=- ``` **bank_line note.** The `fk_bank_line` value on a payment is the row id in the bank-line ledger, not the bank-account id. Dolibarr's API doesn't expose `/bankaccounts/{id}/lines` to `ai_agent` in V1, so we surface the bank-account catalogue at the bottom of the timeline and leave the per-payment lookup as a manual / V3 task. For KissMetrics specifically, the IBAN on the FAC002 PDF (`BE58 9675 4309 4979`) matches **WIS2 (WISE EURO)** — all KM receipts land there. **Deferred-cycle interpretation** (cohort review context): - The contract's deferred-payment schedule (9 months for KM) defines an *expected* cash-receipt timeline. - This workflow gives the *actual* one. `diff` between them surfaces overdue cycles or unexpected early payments. ## Workflow 3 — Monthly cash receipts ```bash ./scripts/payments-by-month.sh # KM only (default) ./scripts/payments-by-month.sh --year 2026 ./scripts/payments-by-month.sh --all-clients # global, for accounting ``` Live output (captured at [examples/payments-by-month.txt](examples/payments-by-month.txt)): ``` # Monthly cash receipts — scope: KissMetrics (socid=1) month count amount cumulative -------------------------------------------------- 2026-02 3 510.00 510.00 2026-03 1 5100.00 5610.00 2026-04 1 2550.00 8160.00 -------------------------------------------------- TOTAL 5 8160.00 ``` The 2026-02 count of 3 (for a net 510 €) is the AVOIR + reissue cycle around `FAC001-CL00001` — three movements, one net economic event. This is normal and reflects the legal correctness of the credit-note + new-invoice approach Arcodange uses for corrections. Use `--all-clients` once Arcodange has more than KM on the books, for monthly accounting / TVA basis (next skill). ## Captured baselines [`examples/`](examples/) holds the live outputs of each script as of the V1 baseline (2026-05-28). To detect drift: ```bash ./scripts/km-payment-state.sh > /tmp/new.txt 2>&1 diff examples/km-payment-state.txt /tmp/new.txt && echo "unchanged" ``` When the actual data legitimately changes (new invoice, new payment), re-capture the baselines in the same commit. ## Adding a new check / column The scripts share a common shape: pull invoices → pull per-invoice payments → reconcile in Python (heredoc, argv-passing tmpfile pattern — the pipe + heredoc-stdin collision is real on macOS bash 3.2, see V1 commit history for the trap). Patterns to follow: - Sort by **TTC reconciliation**, not `paye` flag (workflow 1 demonstrates this). - The payment `date` is a `YYYY-MM-DD HH:MM:SS` **string**, not a unix epoch — different shape from `/invoices/{id}.date`. Don't `int()` it. - AVOIRs have negative amounts; treat them as first-class. Don't filter them out — that hides the chain. ## Out of scope - **Bank-statement reconciliation.** Matching Dolibarr payments against actual bank exports (Qonto / Wise CSV) is a separate concern; would live in a V3 `arcodange-bank-reco` skill. - **TVA basis calculation per month.** Needs the HT × TVA rate per period — next skill: `dolibarr-tva-reconciliation`. - **Writes.** Recording a payment is done in the Dolibarr UI (or via a script with a write-scoped API key — not in scope here). - **Multi-currency.** Everything is EUR today. If a future contract is USD or other, the cross-multicurrency fields (`multicurrency_total_ttc`) need to be added to the reconciliation.