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) <noreply@anthropic.com>
151 lines
10 KiB
Markdown
151 lines
10 KiB
Markdown
---
|
|
name: arcodange-bank-reco
|
|
description: Bank-side reconciliation for Arcodange — cross-check Dolibarr customer + supplier payments against the actual movements on Qonto (FR business account, full API access) and Wise (BUSINESS EUR balance, activity-list API). Five workflows — (1) probe / discover auth + IDs; (2) list Qonto transactions for a period; (3) list Wise activities including incoming KissMetrics payments (via /v1/profiles/{pid}/activities — bypasses the EU statement endpoint restriction); (4) match bank movements against Dolibarr payments in three buckets (matched, bank-only, dolibarr-only) with auto-detection of Wise↔Qonto internal consolidations; (5) live balances per account with Dolibarr cross-check per fk_account. Surfaces concrete findings — incoming KM payments not yet entered in Dolibarr, expenses on the bank without supplier invoices recorded, date drift between bank settlement and Dolibarr saisie, and personal-account fk_account=3 movements that are invisible via API. Use when the user asks "réconcilier la banque", "qu'est-ce que la banque a vu que Dolibarr n'a pas", "match bank vs ERP", "audit comptable Arcodange", "did KM actually pay X", "cohort review bank evidence". Depends on `dolibarr` for the ERP side. SKIP for write operations (this is read-only; entries go through Dolibarr UI), for non-Arcodange bank accounts, and for Wise BUSINESS balance-statement endpoints (not available to EU personal tokens — we use the activity list instead).
|
|
requires:
|
|
bins: ["curl", "jq", "python3", "openssl"]
|
|
auth: true
|
|
---
|
|
|
|
# arcodange-bank-reco — close the loop between Dolibarr and the bank
|
|
|
|
The V1-V5 skills tell you what Dolibarr *thinks* happened. This one tells you what the **bank actually saw**, and matches the two sides. The three buckets it produces (matched / bank-only / dolibarr-only) are the foundation for any clean accounting audit and any cohort-review evidence pack.
|
|
|
|
Depends on the [dolibarr](../dolibarr/SKILL.md) base skill.
|
|
|
|
**CLI shortcuts:** `bin/arcodange bank probe | qonto-transactions | wise-transactions | match | balance | curl`
|
|
|
|
## Wise SCA & the EU restriction — the path we settled on
|
|
|
|
Wise has TWO ways to list movements:
|
|
|
|
1. **`/v1/profiles/{pid}/balance-statements/{balanceId}/statement.json`** — the obvious "statement" endpoint. **Returns 403 for EU personal tokens** (FR included). Wise's own docs say it: "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." Even with full SCA setup (RSA keypair + uploaded public key), the BUSINESS profile statements stay 403. Don't go down that rabbit hole — we did, and Wise just keeps responding `x-2fa-approval-result: REJECTED` regardless.
|
|
|
|
2. **`/v1/profiles/{pid}/activities`** — the activity list. **Works for EU personal tokens with no SCA.** Returns incoming + outgoing in a unified HTML-tagged feed. This is what the skill uses. The cost is that amounts come back as `"<positive>+ 5,100.00 EUR</positive>"` strings instead of structured numerics, so we parse HTML out — easy.
|
|
|
|
We tried the SCA path first, didn't work, then found this. Documented so the next operator doesn't repeat the dance.
|
|
|
|
## Qonto — no surprises
|
|
|
|
Qonto's API works as documented:
|
|
- `Authorization: <login>:<secret_key>` header.
|
|
- `/v2/organization` lists bank accounts + balances.
|
|
- `/v2/transactions?bank_account_id=<id>&settled_at_from=<iso>&settled_at_to=<iso>` lists transactions with pagination via `current_page`.
|
|
|
|
Wise↔Qonto integration in the Qonto UI does NOT expose Wise data via Qonto's API. We confirmed: no `/v2/external_accounts`, no `/v2/aggregated_accounts` endpoint surfaces Wise transactions. The two banks remain separately queried, then merged in the matching layer.
|
|
|
|
## Prerequisites
|
|
|
|
1. Base skill set up ([dolibarr/README.md](../dolibarr/README.md)).
|
|
2. `.env` extended with:
|
|
```
|
|
QONTO_LOGIN=arcodange-XXXXX
|
|
QONTO_SECRET_KEY=<secret>
|
|
QONTO_ORG_SLUG=arcodange-XXXXX
|
|
WISE_API_TOKEN=<token>
|
|
WISE_PROFILE_ID=<numeric id of the BUSINESS profile>
|
|
```
|
|
3. `chmod 600 ~/.config/arcodange-erp/.env`; propagate to the two in-repo hard copies.
|
|
|
|
To generate the tokens:
|
|
- **Qonto**: https://app.qonto.com/ → Settings → Integrations → API → Generate a new key. Copy login + secret (shown once).
|
|
- **Wise**: https://wise.com/your-account/integrations-and-tools/api-tokens → Add new token → name it `arcodange-bank-reco-readonly` → scope read-only.
|
|
|
|
The `WISE_SCA_KEY_PATH` variable exists in the `.env` schema for completeness but is **NOT REQUIRED** today — the activity-list endpoint we use doesn't need SCA. Keep the keypair generated under `~/.config/arcodange-erp/wise-sca-*.pem` so we can revisit if Wise ever opens the statement endpoint to EU tokens.
|
|
|
|
## Workflows
|
|
|
|
### 1. Probe / discovery
|
|
|
|
```bash
|
|
bin/arcodange bank probe
|
|
```
|
|
|
|
Confirms auth on both banks and prints discovered IDs (Qonto org slug, Wise profile id, balance ids). Run once after any token rotation.
|
|
|
|
### 2. Qonto transactions
|
|
|
|
```bash
|
|
bin/arcodange bank qonto-transactions # last 90 days
|
|
bin/arcodange bank qonto-transactions --month 2026-03
|
|
bin/arcodange bank qonto-transactions --since 2026-01-01 --until 2026-05-31
|
|
bin/arcodange bank qonto-transactions --side credit # filter incoming only
|
|
```
|
|
|
|
Captured at [examples/qonto-transactions.txt](examples/qonto-transactions.txt). The full Jan-May 2026 view shows 9 transactions netting to +4191.54 € — exactly the current Qonto balance.
|
|
|
|
### 3. Wise activities
|
|
|
|
```bash
|
|
bin/arcodange bank wise-transactions # last 365 days
|
|
bin/arcodange bank wise-transactions --month 2026-03
|
|
bin/arcodange bank wise-transactions --type TRANSFER # filter
|
|
bin/arcodange bank wise-transactions --since 2026-01-01 --enrich # add wire references
|
|
```
|
|
|
|
Captured at [examples/wise-transactions.txt](examples/wise-transactions.txt). With `--enrich`, each TRANSFER is annotated with its wire reference (e.g. `FROM KISSMETRICS HOLDINGS INC FOR INVOICE FAC002CL0001002/ VENDOR:DEV`), which makes manual cross-checking trivial.
|
|
|
|
Net = +5308.25 € over the whole period, matches the live balance.
|
|
|
|
### 4. Bank ↔ Dolibarr match (the headline)
|
|
|
|
```bash
|
|
bin/arcodange bank match --month 2026-03 # one month
|
|
bin/arcodange bank match --since 2026-01-01 --until 2026-05-31
|
|
bin/arcodange bank match --month 2026-03 --window-days 14 # looser date tolerance
|
|
bin/arcodange bank match --include-fees # include cashback / charges in matching
|
|
```
|
|
|
|
Exit 0 if every bank movement and every Dolibarr payment in the window pair up cleanly; exit 1 otherwise (with the unmatched entries surfaced).
|
|
|
|
Captured at [examples/bank-match-2026-01-to-05.txt](examples/bank-match-2026-01-to-05.txt) — the V1 baseline.
|
|
|
|
**Output buckets:**
|
|
- `MATCHED` — bank ↔ Dolibarr, with the date delta (`Δ+6d`) so you can see drift between bank settlement and Dolibarr saisie.
|
|
- `INTERNAL` — Wise↔Qonto consolidations auto-detected by equal-amount opposite-sign on close dates. Excluded from matching against Dolibarr (they're transfers between Arcodange's own accounts, not external operations).
|
|
- `BANK-ONLY` — bank movements with no Dolibarr counterpart. Each one is either (a) a missing supplier invoice or unrecorded incoming payment, or (b) a Wise platform fee / cashback that doesn't translate to a Dolibarr entry.
|
|
- `DOLIBARR-ONLY` — Dolibarr payments without a bank movement. Usually fk_account=3 (the CCA1 personal account, not API-visible) or the cancel-and-reissue avoir cycle (bank sees the net, Dolibarr sees the three-way breakdown).
|
|
|
|
**Known findings from the V1 baseline** (to raise during cohort review):
|
|
- **Wise 2026-05-29 +2147 € from Kissmetrics NOT in Dolibarr** — M4 invoice probably emitted but the payment hasn't been entered. Action: enter the payment in Dolibarr.
|
|
- **+1000 € FOUREZ Quentin on Qonto 2026-01-21** — unknown income, ask Gabriel.
|
|
- **MISTRAL.AI -172.68 €, CLAUDE.AI -180 €, URSSAF -493 € on Qonto** — bank-only expenses, missing supplier invoices.
|
|
- **fk_account=3 supplier payments** (~430 € cumul) — paid from G.RADUREAU CCA personal account, not visible via Qonto/Wise APIs. This is normal; documented gap.
|
|
|
|
### 5. Live balances
|
|
|
|
```bash
|
|
bin/arcodange bank balance
|
|
```
|
|
|
|
Prints live balances per bank + the Dolibarr-side cumulative-payments-per-fk_account for cross-reference. Captured at [examples/bank-balance.txt](examples/bank-balance.txt).
|
|
|
|
Current state (V1 baseline):
|
|
- Qonto **Compte principal** : 4 191,54 € live
|
|
- Wise **STANDARD EUR** : 5 308,25 € live
|
|
- **Total bank-side** : 9 499,79 €
|
|
|
|
## Matching heuristic — what's in v1 and what's V7
|
|
|
|
Today's match logic:
|
|
- **Amount equality** within 0.01 € (absolute value).
|
|
- **Date proximity** within `--window-days` (default 7 — covers most settlement drift).
|
|
- **Direction-aware** (bank credit ↔ Dolibarr customer payment; bank debit ↔ Dolibarr supplier payment).
|
|
- **Smallest date delta wins** when multiple candidates qualify.
|
|
- **Internal consolidation detection** by equal-amount opposite-sign cross-bank within ±3d.
|
|
|
|
V7 improvements to consider:
|
|
- **Reference-based matching** using the `--enrich` wire reference: search the Wise reference text for `FAC\d+` patterns and match by ref string. Stronger than date+amount when wire ref is informative.
|
|
- **Multi-row aggregation** for Dolibarr sub-payments: if invoice FAFXXX has two sub-payments on different dates summing to one bank movement, aggregate Dolibarr-side before matching.
|
|
- **Avoir cycle handling**: the V1 AVC001/FAC001-CL00001/FAC001-CL0001001 dance produces 3 Dolibarr-only rows for 1 bank credit. A smarter matcher would net the AVOIR before matching.
|
|
|
|
These are deliberately deferred — V1's accuracy is "good enough to surface the real issues" and the simple heuristic is easy to reason about.
|
|
|
|
## Out of scope
|
|
|
|
- **Writes**: the API tokens are read-only; payment recording happens in Dolibarr UI.
|
|
- **Wise BUSINESS balance statements via API** — region-blocked for EU personal tokens (documented above).
|
|
- **Wise transactions routed through Qonto** — Qonto's API doesn't expose them; the integration is UI-only.
|
|
- **fk_account=3 (CCA1 personal account)** — not API-accessible. Movements there must be reconciled manually against personal bank statements.
|
|
- **Bank statement export to CSV** — possible V8 fallback if any of the APIs go away; out of scope today.
|
|
- **Currency conversion** — everything is EUR. Multi-currency would need adapting the amount parser.
|