Files
erp/.claude/skills/dolibarr/SKILL.md
Gabriel Radureau bd90266372 add arcodange-bank-reco — Qonto + Wise reconciliation against Dolibarr
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>
2026-05-31 13:57:21 +02:00

161 lines
13 KiB
Markdown

---
name: dolibarr
description: Reference + connection layer for the Arcodange Dolibarr ERP REST API at https://erp.arcodange.lab/. Documents authentication via DOLAPIKEY, the read-only `ai_agent` account's permission requirements (the `voir_tous` ACL trap that returns empty arrays or 404s instead of 403s on listing endpoints), the credentials layout at `.claude/skills/dolibarr/.env`, the `scripts/dol-curl.sh` wrapper, the catalogue of useful read endpoints (invoices, thirdparties, documents/download, products, status, users/info), and the common gotchas (mode-as-int, paye-vs-status, empty-vs-404-vs-403, unix-epoch dates). Use when setting up the connection, debugging an API call, looking up an endpoint, decoding a permission error, or as a dependency referenced by any `dolibarr-*` workflow skill. SKIP for Dolibarr admin / Helm chart / Ansible config (covered by `chart/` + `ansible/`), for any ERP other than Arcodange's, and for specific business workflows that have their own `dolibarr-<topic>` skill.
requires:
bins: ["curl", "jq", "python3"]
auth: true
---
# dolibarr — Arcodange ERP API platform reference
This is the **base skill** for talking to the Arcodange Dolibarr instance from Claude Code. It documents the connection, the permissions, the endpoint catalogue, and the gotchas. **Each business workflow lives in its own `dolibarr-<topic>` sibling skill** that depends on this one — V1 ships `dolibarr-invoice-audit` for the KissMetrics audit; future siblings will follow the same pattern (e.g. `dolibarr-payments-state`, `dolibarr-tva-reconciliation`).
The API key for `ai_agent` is **read-only by design**. Never attempt writes from any `dolibarr-*` skill — use the Dolibarr UI directly for corrections.
## Quick start
From the repo root:
```bash
bin/arcodange whoami | jq -r .login # → ai_agent
bin/arcodange ping # → {"success":{"code":200,"dolibarr_version":"22.0.4",...}}
bin/arcodange curl /invoices/12 # raw read-only call
```
The `bin/arcodange` CLI is the human entry point; under the hood it delegates to the same per-skill scripts. Direct script access still works:
```bash
./.claude/skills/dolibarr/scripts/dol-curl.sh /users/info | jq -r .login
```
If you see `ai_agent`, auth works. If not, check `.env` (next section) and `dol-curl.sh`'s error message.
For a liveness check that needs no auth:
```bash
curl -s https://erp.arcodange.lab/api/index.php/status | jq .
# → {"success":{"code":200,"dolibarr_version":"22.0.4",...}}
```
## Credentials
Schema of `.claude/skills/dolibarr/.env` (mode **600**, gitignored):
```
DOLIBARR_URL=https://erp.arcodange.lab
DOLIBARR_API_KEY=<rotatable api key for ai_agent>
DOLIBARR_USER=ai_agent
DOLIBARR_PASSWORD=<used only for browser/UI login, very rarely>
```
The DOLAPIKEY is the only secret needed for API calls. `DOLIBARR_USER`/`DOLIBARR_PASSWORD` are kept here only so the operator has them at hand if they need to log into the Dolibarr UI to fix permissions.
**Rules**:
- `chmod 600 .env`. Verify with `stat -f %Lp .env``600`.
- Never commit. The root `.gitignore` already excludes `.env` at any depth; redundant patterns (`*.credentials`, `.claude/skills/**/.env`) belt-and-braces it.
- Never paste the key into chat. If a key leaks, rotate it from the Dolibarr UI: `Users → ai_agent → API key → Generate new`.
- Every sibling `dolibarr-*` skill sources this file via `../dolibarr/.env`. There is exactly one copy.
## Permission gotcha — read this first
Dolibarr enforces TWO layers of access:
1. Per-API-endpoint permissions (does the user's role allow `/invoices`?).
2. Per-record ACL (can this user see THIS invoice/thirdparty?).
Layer 2 is the trap. When `ai_agent` has the endpoint permission but **not** the `voir_tous` flag, listing endpoints silently filter rows out:
- `GET /invoices` returns `[]` with HTTP **200** (looks like "no invoices exist").
- `GET /thirdparties` returns HTTP **404** with `"No third parties found"` (looks like "no thirdparties exist").
- `GET /thirdparties/{id}` returns HTTP **403** with `"Access not allowed for login ai_agent on this thirdparty"` (this is the only honest error).
**Diagnostic recipe.** If a listing endpoint returns empty/404 *while you expect data*:
```bash
./scripts/dol-curl.sh /thirdparties/1
```
If you get a 403 with `Access not allowed`, the ACL is the cause (saved as a reference at [examples/acl_403_thirdparty.json](examples/acl_403_thirdparty.json)). **Do not retry the call blindly.** Fix the permissions, then retest.
**Fix.** In the Dolibarr UI (https://erp.arcodange.lab/ → Setup → Users & Groups → `ai_agent` → Permissions), tick:
- **Tiers** → Lire les tiers
- **Tiers** → Voir tous les tiers (et pas seulement ceux liés à l'utilisateur courant)
- **Factures** → Lire les factures
- **Factures** → Voir toutes les factures (et pas seulement celles liées à l'utilisateur courant)
Add the equivalent flags for any new module a future `dolibarr-*` skill needs (Paiements, Produits, …). The `ai_agent` user has no UI access by default, so `voir_tous` is the right grant — there's no sales-rep relationship to honor.
## Endpoint catalogue
Read-only endpoints we've validated against this instance. Live captures are under [examples/](examples/).
| Endpoint | HTTP | Purpose | Key params / gotchas | Example |
|------------------------------------------------|------|--------------------------------------|------------------------------------------------------------------|---------|
| `GET /status` | 200 | Liveness + Dolibarr version | No auth needed | [status.json](examples/status.json) |
| `GET /users/info` | 200 | Current API user (auth sanity) | Returns `login`, `id`, `rights` tree | [users_info.json](examples/users_info.json) |
| `GET /invoices` | 200 | List invoices | `limit`, `sortfield=t.datef`, `sortorder=DESC`, `status=draft\|unpaid\|paid`, `sqlfilters` | [invoices_list.json](examples/invoices_list.json) |
| `GET /invoices/{id}` | 200 | Invoice detail | `paye=1` is the canonical "paid" flag | [invoice_detail.json](examples/invoice_detail.json) |
| `GET /invoices/{id}/payments` | 200 | Payments applied to one invoice | Returns array of `{amount, type, date, num, ref, fk_bank_line}`. **`date` is a string `YYYY-MM-DD HH:MM:SS`, NOT unix epoch** like elsewhere. Amounts negative for AVOIRs. | [invoices_12_payments.json](examples/invoices_12_payments.json) |
| `GET /payments` | 501 | (list-all not implemented) | Returns "API not found". To enumerate payments, iterate invoices. | — |
| `GET /bankaccounts` / `/{id}` | 200 | List bank accounts / detail | Returns `ref`, `label`, `iban`, `country_code`. `fk_bank_line` on a payment doesn't directly map to the account id — see `dolibarr-payments-state` skill for the lookup. | [bankaccounts_list.json](examples/bankaccounts_list.json) |
| `GET /thirdparties` | 200 | List thirdparties | `mode` MUST be integer (`mode=1` = customers). String fails 400. | (use `/{id}` below) |
| `GET /thirdparties/{id}` | 200 | Thirdparty detail | KissMetrics is `id=1` in this instance | [thirdparty_km.json](examples/thirdparty_km.json) |
| `GET /products` / `/products/{id}` | 200 | Product catalogue | `KM-audit` is `id=1`, `KM-cloud-devops` is `id=2` | [products_list.json](examples/products_list.json) |
| `GET /supplierinvoices` / `/{id}` | 200 | Supplier (fournisseur) invoices | Lines are included inline on the detail endpoint (same shape as `/invoices/{id}`). Sub-endpoint `/lines` returns **HTTP 403** for `ai_agent` — not needed since inline lines work. | [supplierinvoices_list.json](examples/supplierinvoices_list.json) · [supplierinvoice_detail.json](examples/supplierinvoice_detail.json) |
| `GET /invoices/templates/{id}` | 200 | Recurring invoice template detail | **No list endpoint** — probe ids 1..N. Empty ids return HTTP 200 with `id=null` (sentinel for "doesn't exist"). Fields: `ref`, `socid`, `frequency`, `unit_frequency`, `nb_gen_done`, `date_when`, `date_last_gen`, `suspended`, `auto_validate`, `lines`. | [invoice_template_km.json](examples/invoice_template_km.json) |
| `GET /documents/download` | 200 | Download a stored document as base64 | `modulepart=facture&original_file=<REF>/<REF>.pdf` (URL-encode slashes). Returns `{filename, content-type, filesize, content}` with `content` base64. | [document_download_meta.json](examples/document_download_meta.json) |
Not available on this account (intentionally): `/setup/modules` (admin-only), `/invoices/templates` (requires a path arg — different route shape; V2).
## The `dol-curl.sh` wrapper
[scripts/dol-curl.sh](scripts/dol-curl.sh) is a thin wrapper used by this skill and every sibling. It:
- Sources `../.env` (relative to itself), so it works regardless of the caller's CWD.
- Fails loudly if `.env` is missing or `DOLIBARR_URL` / `DOLIBARR_API_KEY` is unset.
- Prepends `DOLAPIKEY` and `Accept: application/json` headers + a 30s timeout.
- Prints the response body to stdout.
- On HTTP ≥ 400, also prints `dol-curl.sh: HTTP <code> on <path>` to stderr and exits 1 — so `set -e` in caller scripts surfaces the failure.
```bash
# Examples
./scripts/dol-curl.sh /users/info | jq .login
./scripts/dol-curl.sh '/invoices?limit=5&sortfield=t.datef&sortorder=DESC' | jq '.[].ref'
./scripts/dol-curl.sh /invoices/12 | jq '{ref, paye, total_ht, last_main_doc}'
./scripts/dol-curl.sh '/documents/download?modulepart=facture&original_file=FAC002-CL0001002%2FFAC002-CL0001002.pdf' \
| jq -r .content | base64 -d > /tmp/inv.pdf
```
## Common gotchas
- **`mode` is an integer.** `?mode=1` works; `?mode=customer` → HTTP 400 `Invalid value specified for mode`.
- **Empty list vs 404 vs 403.** `/invoices` returns `[]` (200) on empty/permission-filtered. `/thirdparties` returns 404 `"No third parties found"`. `/thirdparties/{id}` returns 403 if the ACL hides the row. All three can mean "no permission" — the diagnostic is to hit `/thirdparties/1` directly (see Permission gotcha above).
- **Dates are usually unix epoch (seconds)** on most objects: `.date`, `.datef`, `.date_lim_reglement`, `.tms` are integers.
```bash
python3 -c "import datetime,sys; print(datetime.datetime.fromtimestamp(int(sys.argv[1])))" 1771887600
# → 2026-02-24 00:00:00
```
**Exception**: `/invoices/{id}/payments` returns `.date` as the string `"YYYY-MM-DD HH:MM:SS"`, not an int. Check the field type before parsing.
- **`paye` is the paid flag, `status` is the workflow state.** `status=2` means "validated", `paye=1` means "fully paid". They're independent — a status-2 invoice can still be unpaid.
- **`last_main_doc` paths** are relative to Dolibarr's document root. For `/documents/download`, pass `modulepart=facture` + URL-encoded `original_file=<dir>/<file>` (encode the `/`).
- **`array_options=[]` vs `{}`.** Dolibarr returns `[]` when no extrafields are set and `{}` when there are some. Treat both as "no custom fields" in Python.
## Pointers
- Workflow skill for invoice + thirdparty audits: [dolibarr-invoice-audit](../dolibarr-invoice-audit/SKILL.md).
- Workflow skill for payment-state and cash-receipt tracking: [dolibarr-payments-state](../dolibarr-payments-state/SKILL.md).
- Workflow skill for monthly TVA basis (CA3 / CA12 preparation): [dolibarr-tva-reconciliation](../dolibarr-tva-reconciliation/SKILL.md).
- Workflow skill for recurring invoice templates: [dolibarr-recurring-templates](../dolibarr-recurring-templates/SKILL.md).
- Workflow skill for point-in-time state archival: [dolibarr-data-snapshot](../dolibarr-data-snapshot/SKILL.md).
- Workflow skill for thirdparty completeness audit (any client / supplier): [dolibarr-thirdparty-completeness](../dolibarr-thirdparty-completeness/SKILL.md).
- Workflow skill for supplier-side TVA déductible (CA3 lignes 19 / 20 / 17+24): [dolibarr-tva-deductible](../dolibarr-tva-deductible/SKILL.md).
- Workflow skill for composite CA3-ready TVA summary (collectée + déductible + net): [dolibarr-tva-summary](../dolibarr-tva-summary/SKILL.md).
- **Bank-side reconciliation** (Qonto + Wise ↔ Dolibarr matching): [arcodange-bank-reco](../arcodange-bank-reco/SKILL.md).
- Future workflow skills follow the `dolibarr-<topic>` convention (ERP-internal) or `arcodange-<topic>` (cross-system, like bank reconciliation). Each one depends on this skill for connection + permissions + endpoint reference; each one keeps its triggers focused on its specific business workflow.
## Out of scope
- **Writes.** The `ai_agent` key is read-only by design. For corrections (edit a thirdparty, void an invoice, fix a mention), use the Dolibarr UI.
- **Admin endpoints.** `/setup/*` is gated to admins or to logins listed in `API_LOGINS_ALLOWED_FOR_GET_MODULES`; not available to `ai_agent`.
- **Helm chart / Ansible / Kubernetes operations.** Covered by `chart/`, `ansible/`, and the project root `README.md`.