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>
This commit is contained in:
@@ -8,6 +8,8 @@ requires:
|
||||
|
||||
# dolibarr-data-snapshot — point-in-time JSON dump of Dolibarr read state
|
||||
|
||||
**CLI shortcut:** `bin/arcodange snapshot [--out FILE | --print-only]`
|
||||
|
||||
One script: [`snapshot.sh`](scripts/snapshot.sh). Pulls every read-only endpoint the `dolibarr-*` family uses and bundles into a single JSON file with a content hash. Read-only, no side effects.
|
||||
|
||||
Depends on the [dolibarr](../dolibarr/SKILL.md) base skill.
|
||||
|
||||
@@ -8,6 +8,8 @@ requires:
|
||||
|
||||
# dolibarr-invoice-audit — KissMetrics billing audit
|
||||
|
||||
**CLI shortcuts:** `bin/arcodange invoice list` · `bin/arcodange invoice audit <id>`
|
||||
|
||||
This skill answers the questions the Arcodange × KissMetrics cohort review keeps asking:
|
||||
|
||||
- Has the M-N invoice been emitted? Is it paid?
|
||||
|
||||
@@ -8,6 +8,8 @@ requires:
|
||||
|
||||
# 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).
|
||||
|
||||
@@ -8,6 +8,8 @@ requires:
|
||||
|
||||
# dolibarr-recurring-templates — modèles de factures récurrentes
|
||||
|
||||
**CLI shortcuts:** `bin/arcodange templates list` · `bin/arcodange templates inspect <id>`
|
||||
|
||||
Recurring invoice templates are the Dolibarr objects that drive automated monthly (or arbitrary-frequency) billing. This skill answers: **does the template actually fire on schedule? when's the next one? and what does it generate?**
|
||||
|
||||
Depends on the [dolibarr](../dolibarr/SKILL.md) base skill.
|
||||
|
||||
@@ -8,6 +8,8 @@ requires:
|
||||
|
||||
# dolibarr-thirdparty-completeness — generalized thirdparty audit
|
||||
|
||||
**CLI shortcuts:** `bin/arcodange thirdparty audit <socid>` · `bin/arcodange thirdparty audit-all`
|
||||
|
||||
Replaces the V1 KM-only `dolibarr-invoice-audit/scripts/audit-km-thirdparty.sh` with a generalized, country-aware audit that scales as Arcodange adds more clients and suppliers.
|
||||
|
||||
Depends on the [dolibarr](../dolibarr/SKILL.md) base skill.
|
||||
|
||||
@@ -8,6 +8,8 @@ requires:
|
||||
|
||||
# dolibarr-tva-deductible — supplier-side TVA monthly preparation
|
||||
|
||||
**CLI shortcuts:** `bin/arcodange tva deductible` · `bin/arcodange tva deductible-detail` (or the combined `bin/arcodange tva summary`)
|
||||
|
||||
The mirror of [dolibarr-tva-reconciliation](../dolibarr-tva-reconciliation/SKILL.md) on the supplier side. Together they give you the two numbers a French CA3 needs:
|
||||
|
||||
- **TVA collectée** (`dolibarr-tva-reconciliation`) — what Arcodange invoiced and is obligated to remit.
|
||||
|
||||
@@ -8,6 +8,8 @@ requires:
|
||||
|
||||
# dolibarr-tva-reconciliation — monthly TVA basis preparation
|
||||
|
||||
**CLI shortcuts:** `bin/arcodange tva collect` · `bin/arcodange tva collect-detail` (or the combined `bin/arcodange tva summary`)
|
||||
|
||||
Builds the numbers the CA3 (régime réel normal) or CA12 (réel simplifié) declaration needs, straight from Dolibarr invoice lines. **Read-only**: this skill prepares the basis; the actual declaration goes through impots.gouv.fr.
|
||||
|
||||
Depends on the [dolibarr](../dolibarr/SKILL.md) base skill.
|
||||
|
||||
87
.claude/skills/dolibarr-tva-summary/SKILL.md
Normal file
87
.claude/skills/dolibarr-tva-summary/SKILL.md
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: dolibarr-tva-summary
|
||||
description: One-shot CA3-ready monthly French TVA summary for Arcodange — composes `dolibarr-tva-reconciliation` (TVA collectée customer-side) and `dolibarr-tva-deductible` (TVA déductible supplier-side) into a single per-month report with bucket-by-bucket breakdown and a net verdict (TVA à reverser, crédit de TVA, or équilibre). Each month shows both sides with their CA3 bucket assignments (A1 / A4 / E2 for collectée; 19 / 20 / 17+24 for déductible) plus the net = collectée − déductible. Today Arcodange is in cumulative TVA credit of 223.22 € (0 € collectée under KissMetrics autoliquidation 259-1° CGI vs 223.22 € déductible on supplier invoices). Use when the user asks "résumé TVA du mois", "combien à reverser ce mois", "préparer la CA3", "crédit ou dette TVA", "synthèse TVA mensuelle". Depends on `dolibarr`, internally consumes the same endpoints as the two TVA sibling skills. SKIP for per-line audit (use the sibling skills directly), for writes (the declaration itself goes through impots.gouv.fr), and for non-Arcodange TVA regimes.
|
||||
requires:
|
||||
bins: ["curl", "jq", "python3"]
|
||||
auth: true
|
||||
---
|
||||
|
||||
# dolibarr-tva-summary — CA3-ready monthly TVA report
|
||||
|
||||
The single workflow that combines [dolibarr-tva-reconciliation](../dolibarr-tva-reconciliation/SKILL.md) (TVA collectée — customer side) and [dolibarr-tva-deductible](../dolibarr-tva-deductible/SKILL.md) (TVA déductible — supplier side) into one table per month with a net verdict.
|
||||
|
||||
**CLI shortcut:** `bin/arcodange tva summary [--year YYYY] [--since … --until …]`
|
||||
|
||||
Depends on the [dolibarr](../dolibarr/SKILL.md) base skill.
|
||||
|
||||
## What it computes
|
||||
|
||||
For each month in the window:
|
||||
|
||||
```
|
||||
net = TVA collectée − TVA déductible
|
||||
```
|
||||
|
||||
Then categorizes:
|
||||
- `net > 0` → **TVA À REVERSER** (Arcodange doit cet écart à l'État sur la CA3 du mois)
|
||||
- `net < 0` → **CRÉDIT DE TVA** (l'État doit cet écart à Arcodange — report ou remboursement)
|
||||
- `net = 0` → équilibre
|
||||
|
||||
For each side, the output breaks down by CA3 bucket so you can transcribe directly:
|
||||
- **Collectée**: A1 (domestic with TVA), A4 (autoliquidation intra-UE), E2 (export hors UE)
|
||||
- **Déductible**: ligne 19/20 (20 % standard / taux réduits), ligne 17+24 (autoliquidation intra-UE), ligne 7 (import hors UE), FR exempt
|
||||
|
||||
## Workflow
|
||||
|
||||
```bash
|
||||
bin/arcodange tva summary # all-time
|
||||
bin/arcodange tva summary --year 2026
|
||||
bin/arcodange tva summary --since 2026-01-01 --until 2026-01-31
|
||||
```
|
||||
|
||||
Live output for the all-time window (captured at [examples/tva-summary.txt](examples/tva-summary.txt)):
|
||||
|
||||
```
|
||||
=== 2026-01 ===
|
||||
Customer side (TVA collectée) basis HT= 510.00 TVA= 0.00
|
||||
E2 (export hors UE) HT= 510.00 TVA= 0.00
|
||||
Supplier side (TVA déductible) basis HT= 403.45 TVA= 69.00
|
||||
FR exempt / HT seulement HT= 8.43 TVA= 0.00
|
||||
ligne 17+24 (autoliquidation intra-UE) HT= 50.00 TVA= 0.00
|
||||
ligne 19/20 (20.0% déductible) HT= 345.02 TVA= 69.00
|
||||
--- Net du mois : collectée − déductible = 0.00 − 69.00 = -69.00 → CRÉDIT DE TVA : 69.00 €
|
||||
|
||||
=== 2026-02 ===
|
||||
Customer side (TVA collectée) basis HT= 7650.00 TVA= 0.00
|
||||
E2 (export hors UE) HT= 7650.00 TVA= 0.00
|
||||
Supplier side (TVA déductible) basis HT= 765.00 TVA= 153.00
|
||||
ligne 19/20 (20.0% déductible) HT= 765.00 TVA= 153.00
|
||||
--- Net du mois : collectée − déductible = 0.00 − 153.00 = -153.00 → CRÉDIT DE TVA : 153.00 €
|
||||
|
||||
=== CUMUL window=-inf → +inf ===
|
||||
TVA collectée totale : 0.00
|
||||
TVA déductible totale : 223.22
|
||||
Net cumulé : -223.22 → CRÉDIT DE TVA cumulé : 223.22 €
|
||||
```
|
||||
|
||||
## Reading the result for the CA3 declaration
|
||||
|
||||
For each month:
|
||||
1. **Lignes E2 / A4 / A1** on the CA3 form ← from the customer-side breakdown (HT amounts).
|
||||
2. **Lignes 19 / 20 / 17 / 24** ← from the supplier-side breakdown (TVA amounts).
|
||||
3. **TVA nette** = collectée line totals − déductible line totals.
|
||||
4. If credit, request reimbursement or carry forward to the next month.
|
||||
|
||||
Today Arcodange is in **continuous credit** because of the 259-1° CGI export posture (collectée = 0 systematically against KissMetrics) while normal operating expenses generate déductible. As long as Arcodange has only extra-EU export customers, this stays the steady state.
|
||||
|
||||
## When this skill stops being the right answer
|
||||
|
||||
- **A French B2B client is invoiced** (with TVA collectée > 0). Then the A1 bucket starts populating and the net can flip positive. Still the right skill, just different numbers.
|
||||
- **A goods import** lands a customs ligne 7 entry. The deductible-line-detail breakdown will surface it but verifying matches against customs paperwork is out of scope.
|
||||
- **TVA sur encaissements** (régime spécial). The current skill uses invoice date as the period anchor; encaissements would need to swap that for payment date. Not Arcodange's regime today.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- **Writing the declaration on impots.gouv.fr.** Manual step always.
|
||||
- **Préfinancement TVA / régime simplifié CA12.** Different aggregation cadence; could be a sibling skill if Arcodange's regime changes.
|
||||
- **Other periodic taxes** (CFE, CVAE, IS). Different data sources.
|
||||
29
.claude/skills/dolibarr-tva-summary/examples/tva-summary.txt
Normal file
29
.claude/skills/dolibarr-tva-summary/examples/tva-summary.txt
Normal file
@@ -0,0 +1,29 @@
|
||||
# Arcodange TVA monthly summary — window=-inf → +inf
|
||||
# (composition of dolibarr-tva-reconciliation + dolibarr-tva-deductible)
|
||||
|
||||
=== 2025-10 ===
|
||||
Customer side (TVA collectée) basis HT= 0.00 TVA= 0.00
|
||||
Supplier side (TVA déductible) basis HT= 6.08 TVA= 1.22
|
||||
ligne 19/20 (20.0% déductible) HT= 6.08 TVA= 1.22
|
||||
--- Net du mois : collectée − déductible = 0.00 − 1.22 = -1.22 → CRÉDIT DE TVA : 1.22 €
|
||||
|
||||
=== 2026-01 ===
|
||||
Customer side (TVA collectée) basis HT= 510.00 TVA= 0.00
|
||||
E2 (export hors UE) HT= 510.00 TVA= 0.00
|
||||
Supplier side (TVA déductible) basis HT= 403.45 TVA= 69.00
|
||||
FR exempt / HT seulement HT= 8.43 TVA= 0.00
|
||||
ligne 17+24 (autoliquidation intra-UE) HT= 50.00 TVA= 0.00
|
||||
ligne 19/20 (20.0% déductible) HT= 345.02 TVA= 69.00
|
||||
--- Net du mois : collectée − déductible = 0.00 − 69.00 = -69.00 → CRÉDIT DE TVA : 69.00 €
|
||||
|
||||
=== 2026-02 ===
|
||||
Customer side (TVA collectée) basis HT= 7650.00 TVA= 0.00
|
||||
E2 (export hors UE) HT= 7650.00 TVA= 0.00
|
||||
Supplier side (TVA déductible) basis HT= 765.00 TVA= 153.00
|
||||
ligne 19/20 (20.0% déductible) HT= 765.00 TVA= 153.00
|
||||
--- Net du mois : collectée − déductible = 0.00 − 153.00 = -153.00 → CRÉDIT DE TVA : 153.00 €
|
||||
|
||||
=== CUMUL window=-inf → +inf ===
|
||||
TVA collectée totale : 0.00
|
||||
TVA déductible totale : 223.22
|
||||
Net cumulé : -223.22 → CRÉDIT DE TVA cumulé : 223.22 €
|
||||
183
.claude/skills/dolibarr-tva-summary/scripts/tva-summary.sh
Executable file
183
.claude/skills/dolibarr-tva-summary/scripts/tva-summary.sh
Executable file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env bash
|
||||
# CA3-ready monthly TVA summary for Arcodange — collectée − déductible = net.
|
||||
#
|
||||
# Usage:
|
||||
# tva-summary.sh [--year YYYY] [--since YYYY-MM-DD] [--until YYYY-MM-DD]
|
||||
#
|
||||
# Composes:
|
||||
# - customer-side invoice lines (TVA collectée, CA3 A1 / A4 / E2)
|
||||
# - supplier-side invoice lines (TVA déductible, CA3 19 / 20 / 17+24)
|
||||
# Outputs one table per month plus a cumulative net line.
|
||||
#
|
||||
# Net interpretation:
|
||||
# - net > 0 → TVA à reverser à l'État
|
||||
# - net < 0 → crédit de TVA, demande de remboursement / report
|
||||
# - net = 0 → équilibre
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOL_CURL="${SCRIPT_DIR}/../../dolibarr/scripts/dol-curl.sh"
|
||||
|
||||
SINCE=""; UNTIL=""; YEAR=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--since) SINCE="$2"; shift 2 ;;
|
||||
--until) UNTIL="$2"; shift 2 ;;
|
||||
--year) YEAR="$2"; SINCE="$2-01-01"; UNTIL="$2-12-31"; shift 2 ;;
|
||||
-h|--help) sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) echo "tva-summary.sh: unknown arg: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
WORK="$(mktemp -d -t tvasum.XXXXXX)"
|
||||
trap 'rm -rf "${WORK}"' EXIT
|
||||
|
||||
# 1. Customer side (invoices)
|
||||
"${DOL_CURL}" '/invoices?limit=500&sortfield=t.datef&sortorder=ASC' > "${WORK}/inv.json"
|
||||
IDS=$(python3 -c "import json,sys; print(' '.join(str(r['id']) for r in json.load(open(sys.argv[1])) if r.get('id')))" "${WORK}/inv.json")
|
||||
SOCIDS=$(python3 -c "import json,sys; print(' '.join(sorted({str(r.get('socid')) for r in json.load(open(sys.argv[1])) if r.get('socid')})))" "${WORK}/inv.json")
|
||||
mkdir -p "${WORK}/inv" "${WORK}/tp"
|
||||
for id in ${IDS}; do "${DOL_CURL}" "/invoices/${id}" > "${WORK}/inv/${id}.json"; done
|
||||
for s in ${SOCIDS}; do "${DOL_CURL}" "/thirdparties/${s}" > "${WORK}/tp/${s}.json"; done
|
||||
|
||||
# 2. Supplier side (supplier invoices)
|
||||
"${DOL_CURL}" '/supplierinvoices?limit=500' > "${WORK}/sinv.json"
|
||||
SIDS=$(python3 -c "import json,sys; print(' '.join(str(r['id']) for r in json.load(open(sys.argv[1])) if r.get('id')))" "${WORK}/sinv.json")
|
||||
SSOCIDS=$(python3 -c "import json,sys; print(' '.join(sorted({str(r.get('socid')) for r in json.load(open(sys.argv[1])) if r.get('socid')})))" "${WORK}/sinv.json")
|
||||
mkdir -p "${WORK}/sinv"
|
||||
for id in ${SIDS}; do "${DOL_CURL}" "/supplierinvoices/${id}" > "${WORK}/sinv/${id}.json"; done
|
||||
for s in ${SSOCIDS}; do
|
||||
[[ -f "${WORK}/tp/${s}.json" ]] || "${DOL_CURL}" "/thirdparties/${s}" > "${WORK}/tp/${s}.json"
|
||||
done
|
||||
|
||||
python3 - "${WORK}" "${SINCE}" "${UNTIL}" "${YEAR}" <<'PY'
|
||||
import json, sys, os, collections, datetime
|
||||
work, since, until, year_arg = sys.argv[1:5]
|
||||
|
||||
EU = set("AT BE BG HR CY CZ DK EE FI DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE".split())
|
||||
|
||||
# Thirdparty country map
|
||||
tp_cnty = {}
|
||||
for fn in os.listdir(os.path.join(work, "tp")):
|
||||
try: d = json.load(open(os.path.join(work, "tp", fn)))
|
||||
except json.JSONDecodeError: continue
|
||||
tp_cnty[fn[:-len(".json")]] = d.get("country_code") or ""
|
||||
|
||||
def in_window(ts):
|
||||
if not ts: return False
|
||||
d = datetime.date.fromtimestamp(int(ts))
|
||||
if since and d < datetime.date.fromisoformat(since): return False
|
||||
if until and d > datetime.date.fromisoformat(until): return False
|
||||
return True
|
||||
|
||||
# Customer-side buckets
|
||||
def bucket_collected(country, tx):
|
||||
if tx and tx > 0:
|
||||
return f"A1+8 ({tx}% collectée)"
|
||||
if country == "FR": return "A1 0% (FR atypique)"
|
||||
if country in EU: return "A4 (autoliquidation intra-UE)"
|
||||
if country: return "E2 (export hors UE)"
|
||||
return "(country missing)"
|
||||
|
||||
# Supplier-side buckets
|
||||
def bucket_deductible(country, tx):
|
||||
if tx and tx > 0: return f"ligne 19/20 ({tx}% déductible)"
|
||||
if country == "FR": return "FR exempt / HT seulement"
|
||||
if country in EU: return "ligne 17+24 (autoliquidation intra-UE)"
|
||||
if country: return "ligne 7 (import hors UE)"
|
||||
return "(country missing)"
|
||||
|
||||
# Aggregate per month
|
||||
months = collections.defaultdict(lambda: {
|
||||
"collected": {"ht": 0.0, "tva": 0.0, "by_bucket": collections.defaultdict(lambda: {"ht":0.0,"tva":0.0})},
|
||||
"deductible":{"ht": 0.0, "tva": 0.0, "by_bucket": collections.defaultdict(lambda: {"ht":0.0,"tva":0.0})},
|
||||
})
|
||||
|
||||
# Customer side
|
||||
for fn in sorted(os.listdir(os.path.join(work, "inv"))):
|
||||
try: inv = json.load(open(os.path.join(work, "inv", fn)))
|
||||
except json.JSONDecodeError: continue
|
||||
ts = int(inv.get("date") or 0)
|
||||
if not in_window(ts): continue
|
||||
ym = datetime.date.fromtimestamp(ts).strftime("%Y-%m")
|
||||
sid = str(inv.get("socid") or "")
|
||||
cnty = tp_cnty.get(sid, "")
|
||||
for line in inv.get("lines") or []:
|
||||
tx = round(float(line.get("tva_tx") or 0), 4)
|
||||
ht = float(line.get("total_ht") or 0)
|
||||
tva = float(line.get("total_tva") or 0)
|
||||
b = bucket_collected(cnty, tx)
|
||||
months[ym]["collected"]["ht"] += ht
|
||||
months[ym]["collected"]["tva"] += tva
|
||||
months[ym]["collected"]["by_bucket"][b]["ht"] += ht
|
||||
months[ym]["collected"]["by_bucket"][b]["tva"] += tva
|
||||
|
||||
# Supplier side
|
||||
for fn in sorted(os.listdir(os.path.join(work, "sinv"))):
|
||||
try: inv = json.load(open(os.path.join(work, "sinv", fn)))
|
||||
except json.JSONDecodeError: continue
|
||||
ts = int(inv.get("date") or 0)
|
||||
if not in_window(ts): continue
|
||||
ym = datetime.date.fromtimestamp(ts).strftime("%Y-%m")
|
||||
sid = str(inv.get("socid") or "")
|
||||
cnty = tp_cnty.get(sid, "")
|
||||
lines = inv.get("lines") or []
|
||||
if not lines:
|
||||
ht = float(inv.get("total_ht") or 0); tva = float(inv.get("total_tva") or 0)
|
||||
tx = round((tva / ht * 100), 4) if ht else 0.0
|
||||
lines = [{"tva_tx": tx, "total_ht": ht, "total_tva": tva}]
|
||||
for line in lines:
|
||||
tx = round(float(line.get("tva_tx") or 0), 4)
|
||||
ht = float(line.get("total_ht") or 0)
|
||||
tva = float(line.get("total_tva") or 0)
|
||||
b = bucket_deductible(cnty, tx)
|
||||
months[ym]["deductible"]["ht"] += ht
|
||||
months[ym]["deductible"]["tva"] += tva
|
||||
months[ym]["deductible"]["by_bucket"][b]["ht"] += ht
|
||||
months[ym]["deductible"]["by_bucket"][b]["tva"] += tva
|
||||
|
||||
scope = f"window={since or '-inf'} → {until or '+inf'}"
|
||||
if year_arg: scope = f"year {year_arg}"
|
||||
|
||||
print(f"# Arcodange TVA monthly summary — {scope}")
|
||||
print(f"# (composition of dolibarr-tva-reconciliation + dolibarr-tva-deductible)")
|
||||
print()
|
||||
|
||||
cum_collected = cum_deductible = 0.0
|
||||
for ym in sorted(months):
|
||||
m = months[ym]
|
||||
col_tva = m["collected"]["tva"]
|
||||
ded_tva = m["deductible"]["tva"]
|
||||
net = col_tva - ded_tva
|
||||
cum_collected += col_tva
|
||||
cum_deductible += ded_tva
|
||||
|
||||
print(f"=== {ym} ===")
|
||||
print(f" Customer side (TVA collectée) basis HT={m['collected']['ht']:>10.2f} TVA={col_tva:>8.2f}")
|
||||
for b, s in sorted(m["collected"]["by_bucket"].items()):
|
||||
print(f" {b:<42} HT={s['ht']:>10.2f} TVA={s['tva']:>8.2f}")
|
||||
print(f" Supplier side (TVA déductible) basis HT={m['deductible']['ht']:>10.2f} TVA={ded_tva:>8.2f}")
|
||||
for b, s in sorted(m["deductible"]["by_bucket"].items()):
|
||||
print(f" {b:<42} HT={s['ht']:>10.2f} TVA={s['tva']:>8.2f}")
|
||||
if abs(net) < 0.005:
|
||||
verdict = "(équilibre)"
|
||||
elif net > 0:
|
||||
verdict = f"→ TVA À REVERSER : {net:.2f} €"
|
||||
else:
|
||||
verdict = f"→ CRÉDIT DE TVA : {-net:.2f} €"
|
||||
print(f" --- Net du mois : collectée − déductible = {col_tva:.2f} − {ded_tva:.2f} = {net:>8.2f} {verdict}")
|
||||
print()
|
||||
|
||||
cum_net = cum_collected - cum_deductible
|
||||
print(f"=== CUMUL {scope} ===")
|
||||
print(f" TVA collectée totale : {cum_collected:>10.2f}")
|
||||
print(f" TVA déductible totale : {cum_deductible:>10.2f}")
|
||||
print(f" Net cumulé : {cum_net:>10.2f}", end="")
|
||||
if abs(cum_net) < 0.005:
|
||||
print(" (équilibre)")
|
||||
elif cum_net > 0:
|
||||
print(f" → TVA À REVERSER cumulée : {cum_net:.2f} €")
|
||||
else:
|
||||
print(f" → CRÉDIT DE TVA cumulé : {-cum_net:.2f} €")
|
||||
PY
|
||||
@@ -16,9 +16,16 @@ The API key for `ai_agent` is **read-only by design**. Never attempt writes from
|
||||
|
||||
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
|
||||
# → ai_agent
|
||||
```
|
||||
|
||||
If you see `ai_agent`, auth works. If not, check `.env` (next section) and `dol-curl.sh`'s error message.
|
||||
@@ -142,6 +149,7 @@ Not available on this account (intentionally): `/setup/modules` (admin-only), `/
|
||||
- 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).
|
||||
- Future workflow skills follow the `dolibarr-<topic>` convention. 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
|
||||
|
||||
24
README.md
24
README.md
@@ -1,5 +1,29 @@
|
||||
# ERP
|
||||
|
||||
## CLI — `bin/arcodange`
|
||||
|
||||
Read-only operational CLI for the Arcodange Dolibarr at `erp.arcodange.lab`. One entry point, subcommands per domain:
|
||||
|
||||
```sh
|
||||
bin/arcodange ping # Dolibarr version + liveness
|
||||
bin/arcodange whoami # confirm auth as ai_agent
|
||||
bin/arcodange invoice list # KissMetrics invoices with payment state
|
||||
bin/arcodange invoice audit 12 # JSON facts + PDF mandatory-mention audit
|
||||
bin/arcodange payments state # per-invoice TTC vs payments reconciliation
|
||||
bin/arcodange payments timeline --year 2026 # cash receipts with cumulative balance
|
||||
bin/arcodange tva summary # CA3-ready collectée − déductible per month
|
||||
bin/arcodange thirdparty audit-all # completeness audit, country-aware
|
||||
bin/arcodange templates inspect 1 # recurring template health (frequency, next fire, …)
|
||||
bin/arcodange snapshot --out /tmp/erp.json # full state dump with content_hash
|
||||
bin/arcodange help # full command tree
|
||||
```
|
||||
|
||||
**Read-only by design.** The underlying API key (`ai_agent`) has no write permissions; corrections go through the Dolibarr UI.
|
||||
|
||||
**Credentials.** Reads `.claude/skills/dolibarr/.env` (mode 600, gitignored). Setup instructions: [.claude/skills/dolibarr/README.md](.claude/skills/dolibarr/README.md).
|
||||
|
||||
**Source of behaviour.** Each subcommand delegates to a script under `.claude/skills/<skill>/scripts/`. The skills' `SKILL.md` files document the business logic and are also discoverable by Claude Code via skill triggers.
|
||||
|
||||
## Dolibarr
|
||||
|
||||
### Premiers démarrages
|
||||
|
||||
226
bin/arcodange
Executable file
226
bin/arcodange
Executable file
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env bash
|
||||
# arcodange — read-only operational CLI for the Arcodange Dolibarr ERP.
|
||||
#
|
||||
# Usage: arcodange <command> [subcommand] [args]
|
||||
#
|
||||
# Run `arcodange help` for the full command list.
|
||||
#
|
||||
# This is a thin dispatcher: every subcommand delegates to a script under
|
||||
# .claude/skills/<skill>/scripts/. The skills (markdown SKILL.md files)
|
||||
# remain the source of behaviour documentation; this CLI is the human-
|
||||
# friendly entry point so you don't have to spell out 5-component paths.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Locate the project root ----------------------------------------------
|
||||
# Strategy:
|
||||
# 1. git rev-parse --show-toplevel (works when CWD is inside the repo).
|
||||
# 2. Walk up from this script's directory to find a sibling .claude/skills.
|
||||
# Either approach handles being run from a worktree without surprises.
|
||||
|
||||
if SROOT=$(git rev-parse --show-toplevel 2>/dev/null) && [[ -d "${SROOT}/.claude/skills" ]]; then
|
||||
:
|
||||
else
|
||||
SROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
if [[ ! -d "${SROOT}/.claude/skills" ]]; then
|
||||
echo "arcodange: cannot find project root (no .claude/skills/ found)" >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
SKILLS="${SROOT}/.claude/skills"
|
||||
DOLC="${SKILLS}/dolibarr/scripts/dol-curl.sh"
|
||||
|
||||
# --- Help text -----------------------------------------------------------
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
arcodange — read-only Arcodange Dolibarr CLI.
|
||||
|
||||
USAGE
|
||||
arcodange <command> [subcommand] [args...]
|
||||
|
||||
COMMANDS
|
||||
|
||||
tva French TVA monthly preparation
|
||||
collect [--year|--since|--until] TVA collectée by month × rate (CA3 A1/A4/E2)
|
||||
collect-detail [--year|--since|--until] Per-line audit, customer side
|
||||
deductible [--year|--since|--until] TVA déductible by month × rate (CA3 19/20/17+24)
|
||||
deductible-detail [--year|--since|--until] Per-line audit, supplier side
|
||||
summary [--year|--since|--until] Composite CA3-ready monthly summary
|
||||
|
||||
invoice Customer invoices (KissMetrics)
|
||||
list [--since YYYY-MM-DD] Table of KM invoices with payment state
|
||||
audit <invoice-id> JSON facts + PDF mandatory-mention audit
|
||||
|
||||
thirdparty Clients + suppliers completeness
|
||||
audit <socid> Country-aware audit for one thirdparty
|
||||
audit-all [--clients-only|--suppliers-only] Audit every visible thirdparty
|
||||
|
||||
payments Cash receipts (KissMetrics-side)
|
||||
state [--since YYYY-MM-DD] Per-invoice TTC vs payments reconciliation
|
||||
timeline [--year|--since|--until] Payment timeline with cumulative balance
|
||||
by-month [--year|--all-clients] Monthly aggregation
|
||||
|
||||
templates Recurring invoice templates
|
||||
list [--max-id N] Enumerate templates (probes ids)
|
||||
inspect <template-id> Full template audit with health checks
|
||||
|
||||
snapshot [--out FILE|--print-only] Bundle full read-only state into one JSON
|
||||
|
||||
whoami GET /users/info — confirm auth
|
||||
ping GET /status — liveness + Dolibarr version
|
||||
curl <path> Raw read-only curl through dol-curl.sh
|
||||
|
||||
help [command] Show this help (or subcommand help)
|
||||
|
||||
CREDENTIALS
|
||||
Reads .claude/skills/dolibarr/.env (mode 600, gitignored). See
|
||||
.claude/skills/dolibarr/README.md for one-time setup.
|
||||
|
||||
EXAMPLES
|
||||
arcodange ping
|
||||
arcodange whoami
|
||||
arcodange invoice list
|
||||
arcodange invoice audit 12
|
||||
arcodange tva summary --year 2026
|
||||
arcodange thirdparty audit-all
|
||||
arcodange snapshot --out /tmp/erp.json
|
||||
EOF
|
||||
}
|
||||
|
||||
# --- Dispatch ------------------------------------------------------------
|
||||
|
||||
cmd="${1:-help}"
|
||||
shift || true
|
||||
|
||||
case "${cmd}" in
|
||||
|
||||
tva)
|
||||
sub="${1:-help}"; shift || true
|
||||
case "${sub}" in
|
||||
collect) exec "${SKILLS}/dolibarr-tva-reconciliation/scripts/tva-by-month.sh" "$@" ;;
|
||||
collect-detail) exec "${SKILLS}/dolibarr-tva-reconciliation/scripts/tva-line-detail.sh" "$@" ;;
|
||||
deductible) exec "${SKILLS}/dolibarr-tva-deductible/scripts/deductible-by-month.sh" "$@" ;;
|
||||
deductible-detail) exec "${SKILLS}/dolibarr-tva-deductible/scripts/deductible-line-detail.sh" "$@" ;;
|
||||
summary) exec "${SKILLS}/dolibarr-tva-summary/scripts/tva-summary.sh" "$@" ;;
|
||||
help|-h|--help)
|
||||
cat <<'EOF'
|
||||
arcodange tva — French TVA monthly preparation.
|
||||
|
||||
collect TVA collectée by month × rate (CA3 A1/A4/E2)
|
||||
collect-detail Per-line audit, customer side
|
||||
deductible TVA déductible by month × rate (CA3 19/20/17+24)
|
||||
deductible-detail Per-line audit, supplier side
|
||||
summary CA3-ready monthly net summary (collectée − déductible)
|
||||
|
||||
All accept --year YYYY, --since YYYY-MM-DD, --until YYYY-MM-DD.
|
||||
EOF
|
||||
;;
|
||||
*) echo "arcodange tva: unknown subcommand '${sub}' (try 'arcodange tva help')" >&2; exit 2 ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
invoice)
|
||||
sub="${1:-help}"; shift || true
|
||||
case "${sub}" in
|
||||
list) exec "${SKILLS}/dolibarr-invoice-audit/scripts/list-km-invoices.sh" "$@" ;;
|
||||
audit) exec "${SKILLS}/dolibarr-invoice-audit/scripts/audit-invoice.sh" "$@" ;;
|
||||
help|-h|--help)
|
||||
cat <<'EOF'
|
||||
arcodange invoice — customer (KissMetrics) invoice operations.
|
||||
|
||||
list [--since YYYY-MM-DD] Table of KM invoices with payment state
|
||||
audit <invoice-id> JSON facts + PDF mandatory-mention audit
|
||||
EOF
|
||||
;;
|
||||
*) echo "arcodange invoice: unknown subcommand '${sub}' (try 'arcodange invoice help')" >&2; exit 2 ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
thirdparty)
|
||||
sub="${1:-help}"; shift || true
|
||||
case "${sub}" in
|
||||
audit) exec "${SKILLS}/dolibarr-thirdparty-completeness/scripts/audit-thirdparty.sh" "$@" ;;
|
||||
audit-all) exec "${SKILLS}/dolibarr-thirdparty-completeness/scripts/audit-all-thirdparties.sh" "$@" ;;
|
||||
help|-h|--help)
|
||||
cat <<'EOF'
|
||||
arcodange thirdparty — clients + suppliers completeness.
|
||||
|
||||
audit <socid> Country-aware audit for one thirdparty
|
||||
audit-all [--clients-only|--suppliers-only] Audit every visible thirdparty
|
||||
EOF
|
||||
;;
|
||||
*) echo "arcodange thirdparty: unknown subcommand '${sub}'" >&2; exit 2 ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
payments)
|
||||
sub="${1:-help}"; shift || true
|
||||
case "${sub}" in
|
||||
state) exec "${SKILLS}/dolibarr-payments-state/scripts/km-payment-state.sh" "$@" ;;
|
||||
timeline) exec "${SKILLS}/dolibarr-payments-state/scripts/km-payment-timeline.sh" "$@" ;;
|
||||
by-month) exec "${SKILLS}/dolibarr-payments-state/scripts/payments-by-month.sh" "$@" ;;
|
||||
help|-h|--help)
|
||||
cat <<'EOF'
|
||||
arcodange payments — KissMetrics cash-receipt tracking.
|
||||
|
||||
state [--since YYYY-MM-DD] Per-invoice TTC vs payments reconciliation
|
||||
timeline [--year|--since|--until] Payment timeline with cumulative balance
|
||||
by-month [--year|--all-clients] Monthly aggregation
|
||||
EOF
|
||||
;;
|
||||
*) echo "arcodange payments: unknown subcommand '${sub}'" >&2; exit 2 ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
templates)
|
||||
sub="${1:-help}"; shift || true
|
||||
case "${sub}" in
|
||||
list) exec "${SKILLS}/dolibarr-recurring-templates/scripts/list-templates.sh" "$@" ;;
|
||||
inspect) exec "${SKILLS}/dolibarr-recurring-templates/scripts/inspect-template.sh" "$@" ;;
|
||||
help|-h|--help)
|
||||
cat <<'EOF'
|
||||
arcodange templates — recurring invoice templates.
|
||||
|
||||
list [--max-id N] Enumerate templates (probes ids)
|
||||
inspect <template-id> Full audit with health checks
|
||||
EOF
|
||||
;;
|
||||
*) echo "arcodange templates: unknown subcommand '${sub}'" >&2; exit 2 ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
snapshot)
|
||||
exec "${SKILLS}/dolibarr-data-snapshot/scripts/snapshot.sh" "$@"
|
||||
;;
|
||||
|
||||
whoami)
|
||||
exec "${DOLC}" /users/info
|
||||
;;
|
||||
|
||||
ping)
|
||||
exec "${DOLC}" /status
|
||||
;;
|
||||
|
||||
curl)
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "arcodange curl: missing path. Example: arcodange curl /invoices/12" >&2
|
||||
exit 2
|
||||
fi
|
||||
exec "${DOLC}" "$@"
|
||||
;;
|
||||
|
||||
help|-h|--help|"")
|
||||
if [[ $# -gt 0 ]]; then
|
||||
exec "$0" "$1" help
|
||||
fi
|
||||
usage
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "arcodange: unknown command '${cmd}'" >&2
|
||||
echo " Try 'arcodange help' for the list." >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user