Files
erp/.claude/skills/dolibarr-tva-reconciliation/scripts/tva-by-month.sh
Gabriel Radureau f19b1d2ef2 add dolibarr-tva-reconciliation, dolibarr-recurring-templates, dolibarr-data-snapshot
V3 bundle — three sibling skills under .claude/skills/, all read-only,
all depending on the dolibarr base skill.

dolibarr-tva-reconciliation:
- tva-by-month.sh: HT + TVA grouped by (year-month × tva_tx), ready
  for CA3 / CA12 transcription.
- tva-line-detail.sh: per-line audit trail with country-based bucket
  assignment (A1 domestic / A4 intra-UE autoliquidation / E2 export
  hors UE). Documents the French TVA mental model.
- Today every Arcodange line is E2 (KissMetrics, US, autoliquidation
  259-1° CGI). The skill scales for the day a French B2B is invoiced.

dolibarr-recurring-templates:
- list-templates.sh: probes /invoices/templates/{id} since there's no
  list endpoint. Stops after 5 consecutive empty responses.
- inspect-template.sh: full audit per template, with health checks.
- Surfaces that the "Kiss Metrics Invoice" template has frequency=0
  and nb_gen_done=0 — it is NOT auto-firing. Every KM invoice today
  was manually duplicated. Cohort-review implication: the deferred
  9-month cycle depends on Gabriel clicking "Generate" each month,
  not on a Dolibarr cron.

dolibarr-data-snapshot:
- snapshot.sh: bundles every read endpoint the dolibarr-* family uses
  into one JSON with a content_hash (sha256 of data only, excluding
  timestamp — so identical state hashes identically across runs).
- Use cases: cohort evidence packs, drift detection, archival before
  a known-risky UI change.
- V1 baseline summary captured at examples/snapshot-summary.txt
  (the ~246 KB snapshot file itself is intentionally not committed).

Also extends dolibarr/SKILL.md endpoint catalogue with
/invoices/templates/{id} (and its no-list-endpoint quirk + the
id-null sentinel for missing ids), plus links to the three new
sibling skills.

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

107 lines
4.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Monthly TVA basis aggregation for Arcodange — ready to transcribe onto
# CA3 (régime réel normal) or CA12 (réel simplifié) declarations.
#
# Usage:
# tva-by-month.sh [--year YYYY] [--since YYYY-MM-DD] [--until YYYY-MM-DD]
#
# Groups every visible invoice line by (year-month, tva_tx). Sums HT and TVA.
# Surfaces credit notes correctly (negative HT cancels out at the basis level).
#
# Mapping to French TVA forms (régime réel normal CA3):
# - tva_tx == 0 AND client country != FR
# → ligne A4 "Autres opérations imposables" if reverse-charge intra-UE
# → ligne E2 "Exportations hors UE" if extra-UE
# (the line script tva-line-detail.sh distinguishes these)
# - tva_tx == 20 → A1 base + 8 TVA collectée
# - tva_tx == 10 → A1 (taux réduit) base + 9 TVA collectée
# - tva_tx == 5.5 → A1 base + 9B TVA collectée
# - tva_tx == 2.1 → A1 base + 9C TVA collectée
#
# Today Arcodange is monorange autoliquidation 259-1° on KM (extra-UE), so
# everything lands on E2. As soon as a French B2B client is invoiced, the
# 20 % bucket will populate.
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,30p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "tva-by-month.sh: unknown arg: $1" >&2; exit 2 ;;
esac
done
WORK="$(mktemp -d -t tvamonth.XXXXXX)"
trap 'rm -rf "${WORK}"' EXIT
"${DOL_CURL}" '/invoices?limit=500&sortfield=t.datef&sortorder=ASC' > "${WORK}/inv.json"
# Per-invoice detail fetch (the list endpoint doesn't include line-level tva_tx)
IDS=$(python3 -c "import json,sys; print(' '.join(str(r['id']) for r in json.load(open(sys.argv[1]))))" "${WORK}/inv.json")
mkdir -p "${WORK}/detail"
for id in ${IDS}; do
"${DOL_CURL}" "/invoices/${id}" > "${WORK}/detail/${id}.json"
done
python3 - "${WORK}" "${SINCE}" "${UNTIL}" "${YEAR}" <<'PY'
import json, sys, os, collections, datetime
work, since, until, year_arg = sys.argv[1:5]
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
# key: (yyyy-mm, tva_tx) -> {ht, tva, count}
agg = collections.defaultdict(lambda: {"ht":0.0,"tva":0.0,"count":0})
for fn in sorted(os.listdir(os.path.join(work,"detail"))):
try: inv = json.load(open(os.path.join(work,"detail",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")
for line in inv.get("lines") or []:
tx = float(line.get("tva_tx") or 0)
# Normalize tva_tx — Dolibarr returns "0.0000" / "20.0000" strings as numbers
tx = round(tx, 4)
ht = float(line.get("total_ht") or 0)
tva = float(line.get("total_tva") or 0)
agg[(ym, tx)]["ht"] += ht
agg[(ym, tx)]["tva"] += tva
agg[(ym, tx)]["count"] += 1
scope = f"window={since or '-inf'} → {until or '+inf'}"
if year_arg: scope = f"year {year_arg}"
print(f"# TVA basis by month × rate — {scope}")
print()
print(f"{'month':<8} {'tva_tx':>7} {'count':>5} {'basis HT':>12} {'TVA':>10} CA3 line")
print("-" * 70)
total_ht = total_tva = 0.0
for key in sorted(agg):
ym, tx = key
s = agg[key]
line = "(see tva-line-detail for cnty)" if tx == 0 else ("A1+8" if tx==20 else f"A1+? @ {tx}%")
print(f"{ym:<8} {tx:>7.4f} {s['count']:>5} {s['ht']:>12.2f} {s['tva']:>10.2f} {line}")
total_ht += s["ht"]
total_tva += s["tva"]
print("-" * 70)
print(f"{'TOTAL':>16} {' ':>13} {total_ht:>12.2f} {total_tva:>10.2f}")
print()
print("# Notes:")
print("# - Lines with tva_tx==0 require country lookup to choose CA3 E2 (export hors UE)")
print("# vs A4 (autoliquidation intra-UE). Run tva-line-detail.sh for that breakdown.")
print("# - Credit notes (AVOIRs) show as negative HT and are correctly netted at basis.")
PY