add dolibarr-payments-state skill for cash receipt tracking
V2 in the dolibarr-* family. Three workflows:
- km-payment-state.sh: per-invoice reconciliation (TTC vs sum of
payments) with OK / PARTIAL / UNPAID / OVERPAID classification.
More honest than the `paye` boolean for deferred-cycle agreements.
- km-payment-timeline.sh: all KM payments sorted by date with
cumulative balance — the foundation for cohort-review deferred
9-month-cycle tracking (actual cash receipts vs contractual schedule).
- payments-by-month.sh: monthly aggregation, KM-scoped by default
or --all-clients for accounting basis.
Also updates dolibarr/SKILL.md endpoint catalogue with
/invoices/{id}/payments (note the date-as-string vs unix-epoch quirk)
and /bankaccounts, plus captures the corresponding examples.
V1 baseline of live data: KM is fully reconciled across 5 invoices
(1 avoir + 4 regular), 8160 € total cash receipts spread Feb/Mar/Apr 2026,
all on WISE EURO (BE).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
142
.claude/skills/dolibarr-payments-state/SKILL.md
Normal file
142
.claude/skills/dolibarr-payments-state/SKILL.md
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
name: dolibarr-payments-state
|
||||
description: Track Arcodange cash receipts and payment-state per invoice via the Dolibarr API. Three workflows — (1) per-invoice payment reconciliation for KissMetrics: total TTC vs sum of payments, with PARTIAL / UNPAID / OK / OVERPAID classification (replaces relying on the `paye=1` flag alone); (2) full payment timeline sorted by date with cumulative balance, suitable for cohort-review deferred-cycle tracking (compare actual cash receipts against the contractual schedule); (3) monthly cash-receipt aggregation, scoped to KissMetrics by default or `--all-clients` for global accounting. Surfaces credit notes (AVOIRs as negative payments) and the bank-account catalogue (QONTO, WISE EURO, G.RADUREAU CCA). Use when the user asks "état des paiements KM", "is invoice X paid", "qui doit combien", "suivi des paiements deferred", "deferred 9-month cycle", "cash receipts par mois", "réconciliation factures vs paiements", or any cohort-review billing-cycle check. Depends on the `dolibarr` skill for connection. SKIP for the legal-mention audit (handled by `dolibarr-invoice-audit`), for write operations (the API is read-only — payments are recorded by the Dolibarr UI), and for full bank-statement reconciliation (which would require parsing bank exports outside Dolibarr — out of scope here, V3 candidate).
|
||||
requires:
|
||||
bins: ["curl", "jq", "python3"]
|
||||
auth: true
|
||||
---
|
||||
|
||||
# dolibarr-payments-state — KissMetrics cash-receipt tracking
|
||||
|
||||
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).
|
||||
|
||||
## Why a dedicated skill?
|
||||
|
||||
The `paye` flag on `/invoices/{id}` is a single boolean. The cohort review needs the actual reconciliation: how much was paid, when, by what means, on which bank account. Three things the flag can't tell you:
|
||||
|
||||
1. **Partial payments.** An invoice with one of three deferred installments paid still has `paye=0` but `sum(payments) > 0`.
|
||||
2. **Credit notes.** An AVOIR shows up in the payments list as a negative amount tied to the canceled invoice's pair — the timeline is the easy way to see the chain.
|
||||
3. **Cash-receipt timing.** For deferred-cycle agreements, the **date** a payment hit the bank matters more than whether the invoice is fully paid.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [`dolibarr/.env`](../dolibarr/.env) populated, mode 600.
|
||||
- `ai_agent` permissions from [dolibarr/README.md](../dolibarr/README.md). The existing Factures `voir_tous` covers payments.
|
||||
|
||||
## Workflow 1 — Per-invoice payment state (reconciliation)
|
||||
|
||||
```bash
|
||||
./scripts/km-payment-state.sh # all KM invoices
|
||||
./scripts/km-payment-state.sh --since 2026-02-01 # window
|
||||
echo "exit: $?" # 0 if every invoice reconciles, 1 otherwise
|
||||
```
|
||||
|
||||
Live output (captured at [examples/km-payment-state.txt](examples/km-payment-state.txt)):
|
||||
|
||||
```
|
||||
id ref date HT paid balance state payments
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
12 FAC002-CL0001002 2026-02-24 5100.00 5100.00 0.00 OK 5100.00@2026-03-12(VIR)
|
||||
13 FAC003-CL0001003 2026-02-24 2550.00 2550.00 0.00 OK 2550.00@2026-04-20(VIR)
|
||||
2 FAC001-CL00001 2026-01-30 510.00 510.00 0.00 OK 510.00@2026-02-05(VIR)
|
||||
11 FAC001-CL0001001 2026-01-30 510.00 510.00 0.00 OK 510.00@2026-02-05(VIR)
|
||||
10 AVC001-CL0001001 2026-01-30 -510.00 -510.00 0.00 OK -510.00@2026-02-05(VIR)
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
TOTAL 8160.00 8160.00 0.00
|
||||
# 5 invoice(s), 0 not fully reconciled
|
||||
```
|
||||
|
||||
**state column legend:**
|
||||
- `OK` — `|balance| < 0.005` (reconciled to the cent).
|
||||
- `UNPAID` — no payments recorded at all.
|
||||
- `PARTIAL` — payments < TTC.
|
||||
- `OVERPAID` — payments > TTC. Worth investigating (overpayment, duplicate posting, currency drift).
|
||||
|
||||
Reconciles against **TTC** (the contractual payable), not HT, because that's what the customer actually sends. For KM under 259-1° CGI (TVA=0), HT == TTC, so the distinction doesn't matter; for any future French customer with TVA > 0, it would.
|
||||
|
||||
## Workflow 2 — Payment timeline (cohort deferred-cycle tracking)
|
||||
|
||||
```bash
|
||||
./scripts/km-payment-timeline.sh # all-time
|
||||
./scripts/km-payment-timeline.sh --year 2026 # one year
|
||||
./scripts/km-payment-timeline.sh --since 2026-02-01 --until 2026-04-30
|
||||
```
|
||||
|
||||
Live output (captured at [examples/km-payment-timeline.txt](examples/km-payment-timeline.txt)):
|
||||
|
||||
```
|
||||
date invoice amount type ref bank_line running
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
2026-02-05 AVC001-CL0001001 -510.00 VIR REC003-CL00001 bl=7 -510.00
|
||||
2026-02-05 FAC001-CL0001001 510.00 VIR REC002-####002 bl=6 0.00
|
||||
2026-02-05 FAC001-CL00001 510.00 VIR REC001-####001 bl=5 510.00
|
||||
2026-03-12 FAC002-CL0001002 5100.00 VIR REC003-CL00002 bl=19 5610.00
|
||||
2026-04-20 FAC003-CL0001003 2550.00 VIR REC001-CL0001001 bl=24 8160.00
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
# 5 payment(s), net cash receipts: 8160.00
|
||||
|
||||
# Bank accounts known to this Dolibarr (label / IBAN-leading):
|
||||
# id=1 ref=QON1 label=QONTO country=FR iban=FR761695800001...
|
||||
# id=2 ref=WIS2 label=WISE EURO country=BE iban=BE58967543094979
|
||||
# id=3 ref=CCA1 label=G.RADUREAU Compte Courant Asso country=FR iban=-
|
||||
```
|
||||
|
||||
**bank_line note.** The `fk_bank_line` value on a payment is the row id in the bank-line ledger, not the bank-account id. Dolibarr's API doesn't expose `/bankaccounts/{id}/lines` to `ai_agent` in V1, so we surface the bank-account catalogue at the bottom of the timeline and leave the per-payment lookup as a manual / V3 task. For KissMetrics specifically, the IBAN on the FAC002 PDF (`BE58 9675 4309 4979`) matches **WIS2 (WISE EURO)** — all KM receipts land there.
|
||||
|
||||
**Deferred-cycle interpretation** (cohort review context):
|
||||
- The contract's deferred-payment schedule (9 months for KM) defines an *expected* cash-receipt timeline.
|
||||
- This workflow gives the *actual* one. `diff` between them surfaces overdue cycles or unexpected early payments.
|
||||
|
||||
## Workflow 3 — Monthly cash receipts
|
||||
|
||||
```bash
|
||||
./scripts/payments-by-month.sh # KM only (default)
|
||||
./scripts/payments-by-month.sh --year 2026
|
||||
./scripts/payments-by-month.sh --all-clients # global, for accounting
|
||||
```
|
||||
|
||||
Live output (captured at [examples/payments-by-month.txt](examples/payments-by-month.txt)):
|
||||
|
||||
```
|
||||
# Monthly cash receipts — scope: KissMetrics (socid=1)
|
||||
|
||||
month count amount cumulative
|
||||
--------------------------------------------------
|
||||
2026-02 3 510.00 510.00
|
||||
2026-03 1 5100.00 5610.00
|
||||
2026-04 1 2550.00 8160.00
|
||||
--------------------------------------------------
|
||||
TOTAL 5 8160.00
|
||||
```
|
||||
|
||||
The 2026-02 count of 3 (for a net 510 €) is the AVOIR + reissue cycle around `FAC001-CL00001` — three movements, one net economic event. This is normal and reflects the legal correctness of the credit-note + new-invoice approach Arcodange uses for corrections.
|
||||
|
||||
Use `--all-clients` once Arcodange has more than KM on the books, for monthly accounting / TVA basis (next skill).
|
||||
|
||||
## Captured baselines
|
||||
|
||||
[`examples/`](examples/) holds the live outputs of each script as of the V1 baseline (2026-05-28). To detect drift:
|
||||
|
||||
```bash
|
||||
./scripts/km-payment-state.sh > /tmp/new.txt 2>&1
|
||||
diff examples/km-payment-state.txt /tmp/new.txt && echo "unchanged"
|
||||
```
|
||||
|
||||
When the actual data legitimately changes (new invoice, new payment), re-capture the baselines in the same commit.
|
||||
|
||||
## Adding a new check / column
|
||||
|
||||
The scripts share a common shape: pull invoices → pull per-invoice payments → reconcile in Python (heredoc, argv-passing tmpfile pattern — the pipe + heredoc-stdin collision is real on macOS bash 3.2, see V1 commit history for the trap). Patterns to follow:
|
||||
|
||||
- Sort by **TTC reconciliation**, not `paye` flag (workflow 1 demonstrates this).
|
||||
- The payment `date` is a `YYYY-MM-DD HH:MM:SS` **string**, not a unix epoch — different shape from `/invoices/{id}.date`. Don't `int()` it.
|
||||
- AVOIRs have negative amounts; treat them as first-class. Don't filter them out — that hides the chain.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- **Bank-statement reconciliation.** Matching Dolibarr payments against actual bank exports (Qonto / Wise CSV) is a separate concern; would live in a V3 `arcodange-bank-reco` skill.
|
||||
- **TVA basis calculation per month.** Needs the HT × TVA rate per period — next skill: `dolibarr-tva-reconciliation`.
|
||||
- **Writes.** Recording a payment is done in the Dolibarr UI (or via a script with a write-scoped API key — not in scope here).
|
||||
- **Multi-currency.** Everything is EUR today. If a future contract is USD or other, the cross-multicurrency fields (`multicurrency_total_ttc`) need to be added to the reconciliation.
|
||||
@@ -0,0 +1,10 @@
|
||||
id ref date HT paid balance state payments
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
12 FAC002-CL0001002 2026-02-24 5100.00 5100.00 0.00 OK 5100.00000000@2026-03-12(VIR)
|
||||
13 FAC003-CL0001003 2026-02-24 2550.00 2550.00 0.00 OK 2550.00000000@2026-04-20(VIR)
|
||||
2 FAC001-CL00001 2026-01-30 510.00 510.00 0.00 OK 510.00000000@2026-02-05(VIR)
|
||||
11 FAC001-CL0001001 2026-01-30 510.00 510.00 0.00 OK 510.00000000@2026-02-05(VIR)
|
||||
10 AVC001-CL0001001 2026-01-30 -510.00 -510.00 0.00 OK -510.00000000@2026-02-05(VIR)
|
||||
----------------------------------------------------------------------------------------------------------------------------------
|
||||
TOTAL 8160.00 8160.00 0.00
|
||||
# 5 invoice(s), 0 not fully reconciled
|
||||
@@ -0,0 +1,14 @@
|
||||
date invoice amount type ref bank_line running
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
2026-02-05 AVC001-CL0001001 -510.00 VIR REC003-CL00001 bl=7 -510.00
|
||||
2026-02-05 FAC001-CL0001001 510.00 VIR REC002-####002 bl=6 0.00
|
||||
2026-02-05 FAC001-CL00001 510.00 VIR REC001-####001 bl=5 510.00
|
||||
2026-03-12 FAC002-CL0001002 5100.00 VIR REC003-CL00002 bl=19 5610.00
|
||||
2026-04-20 FAC003-CL0001003 2550.00 VIR REC001-CL0001001 bl=24 8160.00
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
# 5 payment(s), net cash receipts: 8160.00
|
||||
|
||||
# Bank accounts known to this Dolibarr (label / IBAN-leading):
|
||||
# id=1 ref=QON1 label=QONTO country=FR iban=FR7616958000019048...
|
||||
# id=2 ref=WIS2 label=WISE EURO country=BE iban=BE58967543094979
|
||||
# id=3 ref=CCA1 label=G.RADUREAU Compte Courant Asso country=FR iban=-
|
||||
@@ -0,0 +1,9 @@
|
||||
# Monthly cash receipts — scope: KissMetrics (socid=1)
|
||||
|
||||
month count amount cumulative
|
||||
--------------------------------------------------
|
||||
2026-02 3 510.00 510.00
|
||||
2026-03 1 5100.00 5610.00
|
||||
2026-04 1 2550.00 8160.00
|
||||
--------------------------------------------------
|
||||
TOTAL 5 8160.00
|
||||
106
.claude/skills/dolibarr-payments-state/scripts/km-payment-state.sh
Executable file
106
.claude/skills/dolibarr-payments-state/scripts/km-payment-state.sh
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env bash
|
||||
# Per-invoice payment state for KissMetrics.
|
||||
#
|
||||
# For each KM invoice (socid=1): total HT, sum of payments, balance, status.
|
||||
# Surfaces partials (paye=0 but payments > 0) and unpaid (no payments).
|
||||
#
|
||||
# Usage:
|
||||
# km-payment-state.sh [--since YYYY-MM-DD]
|
||||
#
|
||||
# Exit 0 if every KM invoice in the date window is fully reconciled
|
||||
# (balance == 0). Exit 1 if there's any partial / unpaid.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOL_CURL="${SCRIPT_DIR}/../../dolibarr/scripts/dol-curl.sh"
|
||||
|
||||
KM_SOCID=1
|
||||
SINCE=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--since) SINCE="$2"; shift 2 ;;
|
||||
-h|--help) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) echo "km-payment-state.sh: unknown arg: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
SINCE_EPOCH=0
|
||||
if [[ -n "${SINCE}" ]]; then
|
||||
if SINCE_EPOCH=$(date -j -f "%Y-%m-%d" "${SINCE}" "+%s" 2>/dev/null); then :
|
||||
else SINCE_EPOCH=$(date -d "${SINCE}" "+%s"); fi
|
||||
fi
|
||||
|
||||
# 1. Pull KM invoices
|
||||
INV_TMP="$(mktemp -t kmpay.inv.XXXXXX.json)"
|
||||
trap 'rm -f "${INV_TMP}"' EXIT
|
||||
"${DOL_CURL}" '/invoices?limit=100&sortfield=t.datef&sortorder=DESC' > "${INV_TMP}"
|
||||
|
||||
# 2. For each KM invoice in the window, pull its payments
|
||||
KM_IDS=$(python3 -c "
|
||||
import json,sys
|
||||
rows = json.load(open(sys.argv[1]))
|
||||
since = int(sys.argv[2])
|
||||
km_socid = sys.argv[3]
|
||||
ids = [str(r['id']) for r in rows if str(r.get('socid'))==km_socid and (since==0 or int(r.get('date') or 0)>=since)]
|
||||
print(' '.join(ids))
|
||||
" "${INV_TMP}" "${SINCE_EPOCH}" "${KM_SOCID}")
|
||||
|
||||
PAY_DIR="$(mktemp -d -t kmpay.XXXXXX)"
|
||||
trap 'rm -rf "${INV_TMP}" "${PAY_DIR}"' EXIT
|
||||
for id in ${KM_IDS}; do
|
||||
"${DOL_CURL}" "/invoices/${id}/payments" > "${PAY_DIR}/${id}.json"
|
||||
done
|
||||
|
||||
# 3. Reconcile in python
|
||||
python3 - "${INV_TMP}" "${PAY_DIR}" "${SINCE_EPOCH}" "${KM_SOCID}" <<'PY'
|
||||
import json, sys, os, datetime
|
||||
inv_path, pay_dir, since, km_socid = sys.argv[1], sys.argv[2], int(sys.argv[3]), sys.argv[4]
|
||||
invoices = json.load(open(inv_path))
|
||||
|
||||
km = [r for r in invoices if str(r.get("socid"))==km_socid]
|
||||
km = [r for r in km if since==0 or int(r.get("date") or 0)>=since]
|
||||
km.sort(key=lambda r: int(r.get("date") or 0), reverse=True)
|
||||
|
||||
print(f"{'id':>4} {'ref':<24} {'date':<10} {'HT':>10} {'paid':>10} {'balance':>10} {'state':<10} payments")
|
||||
print("-" * 130)
|
||||
fails = 0
|
||||
sum_ht = sum_paid = sum_bal = 0.0
|
||||
for r in km:
|
||||
iid = r["id"]
|
||||
pays = []
|
||||
p = os.path.join(pay_dir, f"{iid}.json")
|
||||
if os.path.exists(p):
|
||||
try: pays = json.load(open(p))
|
||||
except json.JSONDecodeError: pays = []
|
||||
paid = sum(float(x.get("amount") or 0) for x in pays)
|
||||
ht = float(r.get("total_ht") or 0)
|
||||
ttc = float(r.get("total_ttc") or 0)
|
||||
# Reconcile against TTC (the contractual payable), not HT.
|
||||
balance = ttc - paid
|
||||
if abs(balance) < 0.005:
|
||||
state = "OK"
|
||||
elif paid == 0:
|
||||
state = "UNPAID"
|
||||
fails += 1
|
||||
elif abs(paid) < abs(ttc):
|
||||
state = "PARTIAL"
|
||||
fails += 1
|
||||
else:
|
||||
state = "OVERPAID"
|
||||
fails += 1
|
||||
ts = int(r.get("date") or 0)
|
||||
dt = datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d") if ts else "-"
|
||||
pay_summary = "; ".join(
|
||||
f"{x['amount']}@{x['date'].split(' ')[0]}({x.get('type','?')})"
|
||||
for x in pays
|
||||
) or "(none)"
|
||||
print(f"{iid:>4} {r['ref']:<24} {dt:<10} {ht:>10.2f} {paid:>10.2f} {balance:>10.2f} {state:<10} {pay_summary}")
|
||||
sum_ht += ht
|
||||
sum_paid += paid
|
||||
sum_bal += balance
|
||||
print("-" * 130)
|
||||
print(f"{'TOTAL':>40} {sum_ht:>10.2f} {sum_paid:>10.2f} {sum_bal:>10.2f}")
|
||||
print(f"# {len(km)} invoice(s), {fails} not fully reconciled")
|
||||
sys.exit(0 if fails == 0 else 1)
|
||||
PY
|
||||
94
.claude/skills/dolibarr-payments-state/scripts/km-payment-timeline.sh
Executable file
94
.claude/skills/dolibarr-payments-state/scripts/km-payment-timeline.sh
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
# Timeline of all payments against KissMetrics invoices, sorted by date.
|
||||
#
|
||||
# Usage:
|
||||
# km-payment-timeline.sh [--year YYYY] [--since YYYY-MM-DD] [--until YYYY-MM-DD]
|
||||
#
|
||||
# Shows, for each KM payment: date, invoice ref, amount, type, bank account,
|
||||
# and a running cumulative balance. Useful for the deferred-cycle tracking
|
||||
# (compare actual cash receipts against the contractual schedule).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOL_CURL="${SCRIPT_DIR}/../../dolibarr/scripts/dol-curl.sh"
|
||||
|
||||
KM_SOCID=1
|
||||
SINCE=""
|
||||
UNTIL=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--since) SINCE="$2"; shift 2 ;;
|
||||
--until) UNTIL="$2"; shift 2 ;;
|
||||
--year) SINCE="$2-01-01"; UNTIL="$2-12-31"; shift 2 ;;
|
||||
-h|--help) sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) echo "km-payment-timeline.sh: unknown arg: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
WORK="$(mktemp -d -t kmtimeline.XXXXXX)"
|
||||
trap 'rm -rf "${WORK}"' EXIT
|
||||
|
||||
"${DOL_CURL}" '/invoices?limit=100&sortfield=t.datef&sortorder=DESC' > "${WORK}/inv.json"
|
||||
"${DOL_CURL}" '/bankaccounts' > "${WORK}/banks.json"
|
||||
|
||||
# Pull per-invoice payments only for KM invoices
|
||||
KM_IDS=$(python3 -c "
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
print(' '.join(str(r['id']) for r in d if str(r.get('socid'))==sys.argv[2]))
|
||||
" "${WORK}/inv.json" "${KM_SOCID}")
|
||||
|
||||
mkdir -p "${WORK}/pay"
|
||||
for id in ${KM_IDS}; do
|
||||
"${DOL_CURL}" "/invoices/${id}/payments" > "${WORK}/pay/${id}.json"
|
||||
done
|
||||
|
||||
python3 - "${WORK}" "${SINCE}" "${UNTIL}" "${KM_SOCID}" <<'PY'
|
||||
import json, sys, os, datetime
|
||||
work, since, until, km_socid = sys.argv[1:5]
|
||||
|
||||
invoices = {str(r["id"]): r for r in json.load(open(os.path.join(work,"inv.json")))}
|
||||
banks = {str(b["id"]): b for b in json.load(open(os.path.join(work,"banks.json")))}
|
||||
|
||||
def parse_iso(s):
|
||||
return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
|
||||
def in_window(dt):
|
||||
if since and dt.date() < datetime.date.fromisoformat(since): return False
|
||||
if until and dt.date() > datetime.date.fromisoformat(until): return False
|
||||
return True
|
||||
|
||||
rows = []
|
||||
for fn in os.listdir(os.path.join(work,"pay")):
|
||||
iid = fn[:-len(".json")]
|
||||
inv = invoices.get(iid)
|
||||
if not inv or str(inv.get("socid")) != km_socid: continue
|
||||
try: pays = json.load(open(os.path.join(work,"pay",fn)))
|
||||
except json.JSONDecodeError: continue
|
||||
for p in pays:
|
||||
dt = parse_iso(p["date"])
|
||||
if not in_window(dt): continue
|
||||
rows.append((dt, inv["ref"], float(p.get("amount") or 0), p.get("type",""), p.get("ref",""), p.get("fk_bank_line","")))
|
||||
|
||||
rows.sort(key=lambda r: r[0])
|
||||
|
||||
# We don't have a direct fk_bank_line → fk_account mapping from /bankaccounts
|
||||
# alone (would need /bankaccounts/{id}/lines). Show fk_bank_line as-is and
|
||||
# annotate with the IBAN-bearing account when only one account holds receipts.
|
||||
# Receivable accounts in this instance: QON1, WIS2, CCA1.
|
||||
|
||||
print(f"{'date':<10} {'invoice':<22} {'amount':>10} {'type':<6} {'ref':<20} {'bank_line':<10} running")
|
||||
print("-" * 110)
|
||||
running = 0.0
|
||||
for dt, ref, amt, typ, pref, fbl in rows:
|
||||
running += amt
|
||||
print(f"{dt.date()} {ref:<22} {amt:>10.2f} {typ:<6} {pref:<20} bl={fbl:<8} {running:>10.2f}")
|
||||
print("-" * 110)
|
||||
print(f"# {len(rows)} payment(s), net cash receipts: {running:.2f}")
|
||||
print()
|
||||
print("# Bank accounts known to this Dolibarr (label / IBAN-leading):")
|
||||
for bid, b in sorted(banks.items(), key=lambda kv: int(kv[0])):
|
||||
iban = b.get("iban") or "-"
|
||||
label = b.get("label") or b.get("ref") or "-"
|
||||
print(f"# id={bid} ref={b.get('ref','-'):<6} label={label:<32} country={b.get('country_code','-'):<3} iban={iban[:18]+'...' if len(iban)>18 else iban}")
|
||||
PY
|
||||
80
.claude/skills/dolibarr-payments-state/scripts/payments-by-month.sh
Executable file
80
.claude/skills/dolibarr-payments-state/scripts/payments-by-month.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
# Monthly cash-receipt aggregation for KissMetrics payments.
|
||||
#
|
||||
# Usage:
|
||||
# payments-by-month.sh [--year YYYY] [--all-clients]
|
||||
#
|
||||
# By default only sums KissMetrics payments (socid=1). With --all-clients,
|
||||
# iterates every visible invoice. Useful as the foundation for the
|
||||
# cohort-review deferred-cycle dashboard (sum-by-month + count-by-month).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOL_CURL="${SCRIPT_DIR}/../../dolibarr/scripts/dol-curl.sh"
|
||||
|
||||
KM_SOCID=1
|
||||
ALL=0
|
||||
YEAR=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--year) YEAR="$2"; shift 2 ;;
|
||||
--all-clients) ALL=1; shift ;;
|
||||
-h|--help) sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) echo "payments-by-month.sh: unknown arg: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
WORK="$(mktemp -d -t pmonth.XXXXXX)"
|
||||
trap 'rm -rf "${WORK}"' EXIT
|
||||
|
||||
"${DOL_CURL}" '/invoices?limit=100&sortfield=t.datef&sortorder=DESC' > "${WORK}/inv.json"
|
||||
|
||||
# Filter invoice ids
|
||||
if [[ "${ALL}" == "1" ]]; then
|
||||
IDS=$(python3 -c "import json,sys; print(' '.join(str(r['id']) for r in json.load(open(sys.argv[1]))))" "${WORK}/inv.json")
|
||||
else
|
||||
IDS=$(python3 -c "import json,sys; print(' '.join(str(r['id']) for r in json.load(open(sys.argv[1])) if str(r.get('socid'))==sys.argv[2]))" "${WORK}/inv.json" "${KM_SOCID}")
|
||||
fi
|
||||
|
||||
mkdir -p "${WORK}/pay"
|
||||
for id in ${IDS}; do
|
||||
"${DOL_CURL}" "/invoices/${id}/payments" > "${WORK}/pay/${id}.json"
|
||||
done
|
||||
|
||||
python3 - "${WORK}" "${YEAR}" "${ALL}" "${KM_SOCID}" <<'PY'
|
||||
import json, sys, os, collections, datetime
|
||||
work, year, all_clients, km_socid = sys.argv[1], sys.argv[2], sys.argv[3]=="1", sys.argv[4]
|
||||
|
||||
invoices = {str(r["id"]): r for r in json.load(open(os.path.join(work,"inv.json")))}
|
||||
|
||||
monthly = collections.defaultdict(lambda: {"count":0, "amount":0.0})
|
||||
for fn in sorted(os.listdir(os.path.join(work,"pay"))):
|
||||
iid = fn[:-len(".json")]
|
||||
inv = invoices.get(iid)
|
||||
if not inv: continue
|
||||
if not all_clients and str(inv.get("socid")) != km_socid: continue
|
||||
try: pays = json.load(open(os.path.join(work,"pay",fn)))
|
||||
except json.JSONDecodeError: continue
|
||||
for p in pays:
|
||||
d = datetime.datetime.strptime(p["date"], "%Y-%m-%d %H:%M:%S").date()
|
||||
if year and d.strftime("%Y") != year: continue
|
||||
key = d.strftime("%Y-%m")
|
||||
monthly[key]["count"] += 1
|
||||
monthly[key]["amount"] += float(p.get("amount") or 0)
|
||||
|
||||
scope = "all clients" if all_clients else f"KissMetrics (socid={km_socid})"
|
||||
print(f"# Monthly cash receipts — scope: {scope}" + (f" — year {year}" if year else ""))
|
||||
print()
|
||||
print(f"{'month':<8} {'count':>5} {'amount':>12} cumulative")
|
||||
print("-" * 50)
|
||||
cum = 0.0
|
||||
for key in sorted(monthly):
|
||||
s = monthly[key]
|
||||
cum += s["amount"]
|
||||
print(f"{key:<8} {s['count']:>5} {s['amount']:>12.2f} {cum:>10.2f}")
|
||||
print("-" * 50)
|
||||
total_count = sum(v["count"] for v in monthly.values())
|
||||
total_amount = sum(v["amount"] for v in monthly.values())
|
||||
print(f"{'TOTAL':<8} {total_count:>5} {total_amount:>12.2f}")
|
||||
PY
|
||||
@@ -88,6 +88,9 @@ Read-only endpoints we've validated against this instance. Live captures are und
|
||||
| `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` | — |
|
||||
@@ -118,11 +121,12 @@ Not available on this account (intentionally): `/setup/modules` (admin-only), `/
|
||||
|
||||
- **`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 unix epoch (seconds).** `.date`, `.datef`, `.date_lim_reglement`, `.tms` are integers.
|
||||
- **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.
|
||||
@@ -130,6 +134,7 @@ Not available on this account (intentionally): `/setup/modules` (admin-only), `/
|
||||
## 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).
|
||||
- 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
|
||||
|
||||
371
.claude/skills/dolibarr/examples/bankaccounts_list.json
Normal file
371
.claude/skills/dolibarr/examples/bankaccounts_list.json
Normal file
@@ -0,0 +1,371 @@
|
||||
[
|
||||
{
|
||||
"module": null,
|
||||
"id": "1",
|
||||
"entity": null,
|
||||
"import_key": null,
|
||||
"array_options": [],
|
||||
"array_languages": null,
|
||||
"contacts_ids": null,
|
||||
"contacts_ids_internal": null,
|
||||
"linkedObjectsIds": null,
|
||||
"canvas": null,
|
||||
"fk_project": null,
|
||||
"contact_id": null,
|
||||
"user": null,
|
||||
"origin_type": null,
|
||||
"origin_id": null,
|
||||
"ref": "QON1",
|
||||
"ref_ext": null,
|
||||
"statut": null,
|
||||
"status": "0",
|
||||
"country_id": "1",
|
||||
"country_code": "FR",
|
||||
"state_id": "378",
|
||||
"region_id": null,
|
||||
"barcode_type": null,
|
||||
"barcode_type_coder": null,
|
||||
"mode_reglement_id": null,
|
||||
"cond_reglement_id": null,
|
||||
"demand_reason_id": null,
|
||||
"transport_mode_id": null,
|
||||
"shipping_method_id": null,
|
||||
"shipping_method": null,
|
||||
"fk_multicurrency": null,
|
||||
"multicurrency_code": null,
|
||||
"multicurrency_tx": null,
|
||||
"multicurrency_total_ht": null,
|
||||
"multicurrency_total_tva": null,
|
||||
"multicurrency_total_localtax1": null,
|
||||
"multicurrency_total_localtax2": null,
|
||||
"multicurrency_total_ttc": null,
|
||||
"last_main_doc": null,
|
||||
"fk_account": null,
|
||||
"note_public": null,
|
||||
"note_private": null,
|
||||
"total_ht": null,
|
||||
"total_tva": null,
|
||||
"total_localtax1": null,
|
||||
"total_localtax2": null,
|
||||
"total_ttc": null,
|
||||
"lines": null,
|
||||
"actiontypecode": null,
|
||||
"name": null,
|
||||
"lastname": null,
|
||||
"firstname": null,
|
||||
"civility_id": null,
|
||||
"civility_code": null,
|
||||
"date_creation": 1769886460,
|
||||
"date_validation": null,
|
||||
"date_modification": 1769882860,
|
||||
"tms": null,
|
||||
"date_cloture": null,
|
||||
"user_author": null,
|
||||
"user_creation": null,
|
||||
"user_creation_id": null,
|
||||
"user_valid": null,
|
||||
"user_validation": null,
|
||||
"user_validation_id": null,
|
||||
"user_closing_id": null,
|
||||
"user_modification": null,
|
||||
"user_modification_id": null,
|
||||
"fk_user_creat": null,
|
||||
"fk_user_modif": null,
|
||||
"specimen": 0,
|
||||
"totalpaid": null,
|
||||
"extraparams": [],
|
||||
"product": null,
|
||||
"cond_reglement_supplier_id": null,
|
||||
"deposit_percent": null,
|
||||
"retained_warranty_fk_cond_reglement": null,
|
||||
"warehouse_id": null,
|
||||
"label": "QONTO",
|
||||
"courant": "1",
|
||||
"type": "1",
|
||||
"bank": "QONTO",
|
||||
"clos": "0",
|
||||
"rappro": "1",
|
||||
"url": "qonto.com",
|
||||
"code_banque": "16958",
|
||||
"code_guichet": "00001",
|
||||
"number": "90485256392",
|
||||
"cle_rib": "15",
|
||||
"bic": "QNTOFRP1XXX",
|
||||
"iban": "FR7616958000019048525639215",
|
||||
"iban_prefix": null,
|
||||
"pti_in_ctti": "0",
|
||||
"proprio": "ARCODANGE",
|
||||
"owner_name": "ARCODANGE",
|
||||
"owner_address": "73 boulevard de l'yerres",
|
||||
"owner_zip": "91000",
|
||||
"owner_town": "Courcouronnes",
|
||||
"owner_country_id": "1",
|
||||
"owner_country_code": null,
|
||||
"domiciliation": null,
|
||||
"address": "Qonto (Olinda SAS), 18 rue de Navarin, 75009 Paris, France",
|
||||
"type_lib": [
|
||||
"BankType0",
|
||||
"BankType1",
|
||||
"BankType2"
|
||||
],
|
||||
"account_number": "51211",
|
||||
"fk_accountancy_journal": "9",
|
||||
"accountancy_journal": "BQ1",
|
||||
"currency_code": "EUR",
|
||||
"account_currency_code": "EUR",
|
||||
"min_allowed": "0",
|
||||
"min_desired": "0",
|
||||
"comment": " <br />\r\nPour recevoir des virements internationaux, utilisez le BIC de notre banque intermédiaire:<br />\r\n<br />\r\nTRWIBEB3XXX<br />\r\n<br />\r\n<br />\r\n<br />\r\n(Privilégier Wise)<br />\r\n ",
|
||||
"date_solde": null,
|
||||
"solde": null,
|
||||
"balance": 5032,
|
||||
"ics": "",
|
||||
"ics_transfer": ""
|
||||
},
|
||||
{
|
||||
"module": null,
|
||||
"id": "2",
|
||||
"entity": null,
|
||||
"import_key": null,
|
||||
"array_options": [],
|
||||
"array_languages": null,
|
||||
"contacts_ids": null,
|
||||
"contacts_ids_internal": null,
|
||||
"linkedObjectsIds": null,
|
||||
"canvas": null,
|
||||
"fk_project": null,
|
||||
"contact_id": null,
|
||||
"user": null,
|
||||
"origin_type": null,
|
||||
"origin_id": null,
|
||||
"ref": "WIS2",
|
||||
"ref_ext": null,
|
||||
"statut": null,
|
||||
"status": "0",
|
||||
"country_id": "2",
|
||||
"country_code": "BE",
|
||||
"state_id": "140",
|
||||
"region_id": null,
|
||||
"barcode_type": null,
|
||||
"barcode_type_coder": null,
|
||||
"mode_reglement_id": null,
|
||||
"cond_reglement_id": null,
|
||||
"demand_reason_id": null,
|
||||
"transport_mode_id": null,
|
||||
"shipping_method_id": null,
|
||||
"shipping_method": null,
|
||||
"fk_multicurrency": null,
|
||||
"multicurrency_code": null,
|
||||
"multicurrency_tx": null,
|
||||
"multicurrency_total_ht": null,
|
||||
"multicurrency_total_tva": null,
|
||||
"multicurrency_total_localtax1": null,
|
||||
"multicurrency_total_localtax2": null,
|
||||
"multicurrency_total_ttc": null,
|
||||
"last_main_doc": null,
|
||||
"fk_account": null,
|
||||
"note_public": null,
|
||||
"note_private": null,
|
||||
"total_ht": null,
|
||||
"total_tva": null,
|
||||
"total_localtax1": null,
|
||||
"total_localtax2": null,
|
||||
"total_ttc": null,
|
||||
"lines": null,
|
||||
"actiontypecode": null,
|
||||
"name": null,
|
||||
"lastname": null,
|
||||
"firstname": null,
|
||||
"civility_id": null,
|
||||
"civility_code": null,
|
||||
"date_creation": 1769887227,
|
||||
"date_validation": null,
|
||||
"date_modification": 1769883627,
|
||||
"tms": null,
|
||||
"date_cloture": null,
|
||||
"user_author": null,
|
||||
"user_creation": null,
|
||||
"user_creation_id": null,
|
||||
"user_valid": null,
|
||||
"user_validation": null,
|
||||
"user_validation_id": null,
|
||||
"user_closing_id": null,
|
||||
"user_modification": null,
|
||||
"user_modification_id": null,
|
||||
"fk_user_creat": null,
|
||||
"fk_user_modif": null,
|
||||
"specimen": 0,
|
||||
"totalpaid": null,
|
||||
"extraparams": [],
|
||||
"product": null,
|
||||
"cond_reglement_supplier_id": null,
|
||||
"deposit_percent": null,
|
||||
"retained_warranty_fk_cond_reglement": null,
|
||||
"warehouse_id": null,
|
||||
"label": "WISE EURO",
|
||||
"courant": "1",
|
||||
"type": "1",
|
||||
"bank": "WISE",
|
||||
"clos": "0",
|
||||
"rappro": "1",
|
||||
"url": "https://wise.com",
|
||||
"code_banque": "",
|
||||
"code_guichet": "",
|
||||
"number": "",
|
||||
"cle_rib": "",
|
||||
"bic": "TRWIBEB1XXX",
|
||||
"iban": "BE58967543094979",
|
||||
"iban_prefix": null,
|
||||
"pti_in_ctti": "0",
|
||||
"proprio": "ARCODANGE",
|
||||
"owner_name": "ARCODANGE",
|
||||
"owner_address": "73 BD de l'yerres",
|
||||
"owner_zip": "91000",
|
||||
"owner_town": "\u00c9vry-Courcouronnes",
|
||||
"owner_country_id": "1",
|
||||
"owner_country_code": null,
|
||||
"domiciliation": null,
|
||||
"address": "Wise, Rue du Tr\u00f4ne 100, 3rd floor, Brussels, 1050, Belgium",
|
||||
"type_lib": [
|
||||
"BankType0",
|
||||
"BankType1",
|
||||
"BankType2"
|
||||
],
|
||||
"account_number": "51212",
|
||||
"fk_accountancy_journal": "8",
|
||||
"accountancy_journal": "BQ2",
|
||||
"currency_code": "EUR",
|
||||
"account_currency_code": "EUR",
|
||||
"min_allowed": "0",
|
||||
"min_desired": "0",
|
||||
"comment": "",
|
||||
"date_solde": null,
|
||||
"solde": null,
|
||||
"balance": 3160,
|
||||
"ics": "",
|
||||
"ics_transfer": ""
|
||||
},
|
||||
{
|
||||
"module": null,
|
||||
"id": "3",
|
||||
"entity": null,
|
||||
"import_key": null,
|
||||
"array_options": [],
|
||||
"array_languages": null,
|
||||
"contacts_ids": null,
|
||||
"contacts_ids_internal": null,
|
||||
"linkedObjectsIds": null,
|
||||
"canvas": null,
|
||||
"fk_project": null,
|
||||
"contact_id": null,
|
||||
"user": null,
|
||||
"origin_type": null,
|
||||
"origin_id": null,
|
||||
"ref": "CCA1",
|
||||
"ref_ext": null,
|
||||
"statut": null,
|
||||
"status": "0",
|
||||
"country_id": "1",
|
||||
"country_code": "FR",
|
||||
"state_id": null,
|
||||
"region_id": null,
|
||||
"barcode_type": null,
|
||||
"barcode_type_coder": null,
|
||||
"mode_reglement_id": null,
|
||||
"cond_reglement_id": null,
|
||||
"demand_reason_id": null,
|
||||
"transport_mode_id": null,
|
||||
"shipping_method_id": null,
|
||||
"shipping_method": null,
|
||||
"fk_multicurrency": null,
|
||||
"multicurrency_code": null,
|
||||
"multicurrency_tx": null,
|
||||
"multicurrency_total_ht": null,
|
||||
"multicurrency_total_tva": null,
|
||||
"multicurrency_total_localtax1": null,
|
||||
"multicurrency_total_localtax2": null,
|
||||
"multicurrency_total_ttc": null,
|
||||
"last_main_doc": null,
|
||||
"fk_account": null,
|
||||
"note_public": null,
|
||||
"note_private": null,
|
||||
"total_ht": null,
|
||||
"total_tva": null,
|
||||
"total_localtax1": null,
|
||||
"total_localtax2": null,
|
||||
"total_ttc": null,
|
||||
"lines": null,
|
||||
"actiontypecode": null,
|
||||
"name": null,
|
||||
"lastname": null,
|
||||
"firstname": null,
|
||||
"civility_id": null,
|
||||
"civility_code": null,
|
||||
"date_creation": 1772107398,
|
||||
"date_validation": null,
|
||||
"date_modification": 1772103843,
|
||||
"tms": null,
|
||||
"date_cloture": null,
|
||||
"user_author": null,
|
||||
"user_creation": null,
|
||||
"user_creation_id": null,
|
||||
"user_valid": null,
|
||||
"user_validation": null,
|
||||
"user_validation_id": null,
|
||||
"user_closing_id": null,
|
||||
"user_modification": null,
|
||||
"user_modification_id": null,
|
||||
"fk_user_creat": null,
|
||||
"fk_user_modif": null,
|
||||
"specimen": 0,
|
||||
"totalpaid": null,
|
||||
"extraparams": [],
|
||||
"product": null,
|
||||
"cond_reglement_supplier_id": null,
|
||||
"deposit_percent": null,
|
||||
"retained_warranty_fk_cond_reglement": null,
|
||||
"warehouse_id": null,
|
||||
"label": "G.RADUREAU Compte Courant Asso",
|
||||
"courant": "1",
|
||||
"type": "1",
|
||||
"bank": "",
|
||||
"clos": "0",
|
||||
"rappro": "1",
|
||||
"url": null,
|
||||
"code_banque": "",
|
||||
"code_guichet": "",
|
||||
"number": "",
|
||||
"cle_rib": "",
|
||||
"bic": "",
|
||||
"iban": "",
|
||||
"iban_prefix": null,
|
||||
"pti_in_ctti": "0",
|
||||
"proprio": "",
|
||||
"owner_name": "",
|
||||
"owner_address": "",
|
||||
"owner_zip": "",
|
||||
"owner_town": "",
|
||||
"owner_country_id": null,
|
||||
"owner_country_code": null,
|
||||
"domiciliation": null,
|
||||
"address": "",
|
||||
"type_lib": [
|
||||
"BankType0",
|
||||
"BankType1",
|
||||
"BankType2"
|
||||
],
|
||||
"account_number": "45511",
|
||||
"fk_accountancy_journal": "13",
|
||||
"accountancy_journal": "CCA1",
|
||||
"currency_code": "EUR",
|
||||
"account_currency_code": "EUR",
|
||||
"min_allowed": "0",
|
||||
"min_desired": "0",
|
||||
"comment": "",
|
||||
"date_solde": null,
|
||||
"solde": null,
|
||||
"balance": -429.75,
|
||||
"ics": "",
|
||||
"ics_transfer": ""
|
||||
}
|
||||
]
|
||||
12
.claude/skills/dolibarr/examples/invoices_12_payments.json
Normal file
12
.claude/skills/dolibarr/examples/invoices_12_payments.json
Normal file
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"amount": "5100.00000000",
|
||||
"type": "VIR",
|
||||
"typeline": "payment",
|
||||
"date": "2026-03-12 12:00:00",
|
||||
"num": "",
|
||||
"ref": "REC003-CL00002",
|
||||
"ref_ext": "",
|
||||
"fk_bank_line": "19"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user