---
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

**CLI shortcuts:** `bin/arcodange payments state` · `bin/arcodange payments timeline` · `bin/arcodange payments by-month`

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.
