Files
erp/.claude/skills/dolibarr/SKILL.md
Gabriel Radureau 1c0ba8ea75 add bin/arcodange CLI and dolibarr-tva-summary skill
Two changes that go together: now operators can run every read-only
workflow without going through Claude. The skills (SKILL.md files)
remain the source of behaviour documentation and Claude triggers;
bin/arcodange is the human-facing entry point.

bin/arcodange:
- Bash dispatcher at the project root. Subcommands per domain:
  tva {collect, collect-detail, deductible, deductible-detail, summary},
  invoice {list, audit}, thirdparty {audit, audit-all},
  payments {state, timeline, by-month},
  templates {list, inspect},
  snapshot, whoami, ping, curl, help.
- Locates the project root via `git rev-parse` so it works from any
  CWD (including from a worktree).
- Per-subcommand `help` text. Unknown commands exit 2 with a hint.
- Reuses the existing per-skill scripts under .claude/skills/<name>/
  scripts/ via `exec` (zero behaviour drift, full credit to the
  existing tested code).

dolibarr-tva-summary:
- Composes dolibarr-tva-reconciliation (TVA collectée customer-side)
  and dolibarr-tva-deductible (TVA déductible supplier-side) into a
  single CA3-ready monthly summary with per-month net verdict
  (TVA à reverser / crédit de TVA / équilibre) and a cumulative line.
- Live baseline: Arcodange en crédit de TVA de 223.22 € cumulé
  (0 € collectée 259-1° CGI vs 223.22 € déductible).
- Exposed as `arcodange tva summary [--year|--since|--until]`.

Each existing skill's SKILL.md gets a one-line "CLI shortcut" near
the top so the human path is discoverable from any skill page.
The project root README.md gets a CLI section as the primary
operator entry point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 11:30:18 +02:00

13 KiB

name, description, requires
name description requires
dolibarr 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.
bins auth
curl
jq
python3
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:

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:

./.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:

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 .env600.
  • 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:

./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). 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/.

Endpoint HTTP Purpose Key params / gotchas Example
GET /status 200 Liveness + Dolibarr version No auth needed status.json
GET /users/info 200 Current API user (auth sanity) Returns login, id, rights tree users_info.json
GET /invoices 200 List invoices limit, sortfield=t.datef, sortorder=DESC, status=draft|unpaid|paid, sqlfilters invoices_list.json
GET /invoices/{id} 200 Invoice detail paye=1 is the canonical "paid" flag 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
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
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
GET /products / /products/{id} 200 Product catalogue KM-audit is id=1, KM-cloud-devops is id=2 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 · 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
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

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

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.