add dolibarr api skills for read-only inspection

First two of an expected family of dolibarr-* skills:

- dolibarr/: platform reference — DOLAPIKEY auth, the voir_tous ACL
  trap, endpoint catalogue, the dol-curl.sh wrapper, .env credentials
  layout (gitignored, mode 600). Every future workflow skill depends
  on this one.
- dolibarr-invoice-audit/: first workflow — list KissMetrics invoices,
  audit one invoice end-to-end (JSON facts + PDF mandatory-mention
  checklist against the French legal corpus), audit the KissMetrics
  thirdparty record.

Live captures in examples/ include real audit findings to surface
to the Arcodange × KissMetrics cohort review: PDFs are missing
capital social, L.441-10 penalties, 40 € indemnity, L.123-22 / R.123-237;
KissMetrics thirdparty has no EIN (idprof1..6 all empty);
static/config/company.json holds placeholder values and a wrong
forme juridique (claims SAS, the real Dolibarr is SARL).

.gitignore hardened with *.credentials, secrets/, *.key, and an
explicit .claude/skills/**/.env pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 18:43:39 +02:00
parent e90ac2df80
commit bbfa50c3eb
18 changed files with 2811 additions and 1 deletions

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Audit the KissMetrics thirdparty record (socid=1) for completeness.
#
# Usage:
# audit-km-thirdparty.sh
#
# Checks the contact fields and the idprof1..idprof6 slots where the US EIN
# should live for a non-EU customer. Today EIN is missing — this script's
# exit-1 IS the finding to surface to the cohort review.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOL_CURL="${SCRIPT_DIR}/../../dolibarr/scripts/dol-curl.sh"
KM_SOCID=1
TMP_JSON="$(mktemp -t doltp.XXXXXX.json)"
trap 'rm -f "${TMP_JSON}"' EXIT
"${DOL_CURL}" "/thirdparties/${KM_SOCID}" > "${TMP_JSON}"
python3 - "${TMP_JSON}" <<'PY'
import json, sys
with open(sys.argv[1]) as f:
d = json.load(f)
print("=" * 80)
print(f" KissMetrics thirdparty audit — socid={d.get('id')}")
print("=" * 80)
required = [
("name", d.get("name")),
("client flag", "yes" if str(d.get("client")) == "1" else None),
("country_code", d.get("country_code")),
("state_id", d.get("state_id")),
("address", d.get("address")),
("zip", d.get("zip")),
("town", d.get("town")),
("email", d.get("email")),
("phone", d.get("phone")),
("url", d.get("url")),
]
idprofs = [(f"idprof{i}", d.get(f"idprof{i}")) for i in range(1, 7)]
ein_present = any(v not in (None, "", "0") for _, v in idprofs)
passes = fails = 0
def check(label, value):
global passes, fails
ok = value not in (None, "", "0")
print(f" [{'OK' if ok else 'XX'}] {label:<18} = {value!r}")
if ok: passes += 1
else: fails += 1
for label, value in required:
check(label, value)
print()
print(" idprof1..idprof6 (EIN slot for non-EU customers):")
for label, value in idprofs:
print(f" {label:<10} = {value!r}")
print(f" [{'OK' if ein_present else 'XX'}] EIN present (any idprof populated)")
if ein_present: passes += 1
else: fails += 1
ao = d.get("array_options") or {}
print()
print(f" array_options (Dolibarr extrafields): {ao if ao else '(none)'}")
print()
print(f" {passes} pass / {fails} fail")
sys.exit(0 if fails == 0 else 1)
PY