add 2 dolibarr-* skills: thirdparty-completeness, tva-deductible #4

Merged
arcodange merged 1 commits from claude/dolibarr-thirdparty-tva-deductible into main 2026-05-29 06:15:05 +02:00
18 changed files with 5736 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
---
name: dolibarr-thirdparty-completeness
description: Audit the completeness of any Arcodange Dolibarr thirdparty (client or supplier) against country-specific French / EU / extra-EU rules. Two workflows — (1) audit one thirdparty by socid (generalized from the V1 KissMetrics-only audit), with country-aware checklist : FR requires SIREN + SIRET + tva_intra (if VAT-registered), EU non-FR requires tva_intra for B2B autoliquidation, extra-EU requires a national tax id in idprof1; (2) audit ALL visible thirdparties in one shot, compact table showing each one's mandatory gaps and exit code reflecting whether the dataset is fully compliant. Use when the user asks "audit fiche client", "audit fiche fournisseur", "complétude des tiers", "qui n'a pas son SIRET / TVA intra", "qualité des données tiers Dolibarr". Supersedes the V1 dolibarr-invoice-audit/scripts/audit-km-thirdparty.sh (KM-hardcoded) by handling any socid and any country. Depends on the `dolibarr` skill. SKIP for write/correction tasks (use the Dolibarr UI), for invoice-side mandatory mentions (handled by `dolibarr-invoice-audit`), and for TVA computation (handled by `dolibarr-tva-reconciliation` / `dolibarr-tva-deductible`).
requires:
bins: ["curl", "jq", "python3"]
auth: true
---
# dolibarr-thirdparty-completeness — generalized thirdparty audit
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.
## The country-aware checklist
| Country | Mandatory | Optional but useful |
|---|---|---|
| **FR** | name, address, zip, town, country_code, **SIREN** (idprof1), **SIRET** (idprof2), **tva_intra** (if supplier or VAT-registered) | APE (idprof3), email, phone, url, IBAN |
| **EU non-FR** | name, address, zip, town, country_code, **tva_intra** (required for B2B autoliquidation under article 196 directive TVA) | national registration id (idprof1), email, phone, url |
| **Extra-EU** | name, address, zip, town, country_code, **national tax id** (idprof1 — EIN for US, equivalent elsewhere) | idprof2, email, phone, url |
The rules mirror the field a French accountant would expect to file on monthly TVA declarations and on the customer file as audit evidence.
## Workflow 1 — Audit one thirdparty
```bash
./scripts/audit-thirdparty.sh 1 # KissMetrics (US, client)
./scripts/audit-thirdparty.sh 2 # Wise Europe SA (BE, supplier)
./scripts/audit-thirdparty.sh 5 # Medialex (FR, supplier)
echo "exit: $?" # 0 = complete, 1 = mandatory gap
```
Live output for the FR-supplier case (captured at [examples/audit-thirdparty-5.txt](examples/audit-thirdparty-5.txt)):
```
================================================================================
Thirdparty 5 — Medialex [supplier, country=FR]
================================================================================
[OK] name = 'Medialex'
[OK] address = '10 rue du Breil'
[OK] zip = '35063'
[OK] town = 'Rennes'
[OK] country_code = 'FR'
[OK] idprof1 (SIREN) = '353403074'
[XX] idprof2 (SIRET) = ''
[OK] idprof3 (APE) = '7312Z' (optional)
[XX] tva_intra (VAT) = ''
...
8 pass, 2 mandatory fail(s), 2 optional unset
```
Status codes:
- `[OK]` — value present.
- `[XX]` — mandatory and missing.
- `[--]` — optional and unset.
## Workflow 2 — Audit all visible thirdparties
```bash
./scripts/audit-all-thirdparties.sh # everyone
./scripts/audit-all-thirdparties.sh --clients-only
./scripts/audit-all-thirdparties.sh --suppliers-only
echo "exit: $?" # 0 if dataset is fully compliant
```
Live output (captured at [examples/audit-all-thirdparties.txt](examples/audit-all-thirdparties.txt)) — **5 / 10 thirdparties have mandatory gaps**:
```
id name cnty role missing
--------------------------------------------------------------------------------------------------------------
1 KissMetrics US client tax_id
2 Wise Europe SA BE supplier tva_intra
3 Greffe du tribunal de commerce Évry FR supplier (complete)
4 YOLAW FR supplier (complete)
5 Medialex FR supplier SIRET, tva_intra
6 Qonto FR supplier SIRET
7 OVH FR supplier (complete)
8 La Poste FR supplier (complete)
9 Infogreffe FR supplier SIRET
10 Darnis Operations FR supplier (complete)
--------------------------------------------------------------------------------------------------------------
# 10 thirdparties audited, 5 with mandatory gaps
```
The 5 gaps to surface to the cohort review / accountant:
- **KissMetrics**: US tax id (EIN) — already known from V1, still pending.
- **Wise Europe SA**: BE tva_intra missing (mandatory for intra-UE B2B autoliquidation; currently the FAF2026001 supplier invoice is recorded with TVA=0 but no tva_intra, which the accountant will flag).
- **Medialex**: SIRET + tva_intra. SIREN is present so the SIRET should be derivable (SIRET = SIREN + 5-digit NIC). The tva_intra is FR + 2 chars + SIREN.
- **Qonto, Infogreffe**: SIRET missing on otherwise-complete records.
Fixing these is a Dolibarr UI task — this skill is read-only.
## Relationship with V1
The V1 `dolibarr-invoice-audit/scripts/audit-km-thirdparty.sh` is kept for backward compatibility but is now a special case of this skill. Recommendation: invoke `./scripts/audit-thirdparty.sh 1` from this skill instead of the V1 script for any new automation.
## Out of scope
- **Writes / corrections.** The API key is read-only. Fix via the Dolibarr UI.
- **VAT-number validation** against the EU VIES service. Future enhancement: `tva_intra` could be looked up against `https://ec.europa.eu/taxation_customs/vies/services/checkVatService` to validate the number. Out of scope here (external API).
- **idprof normalization across forks** of Dolibarr. The idprof1..6 slots can be reordered or relabeled by Dolibarr admins. This skill assumes the stock French Dolibarr mapping (idprof1=SIREN, idprof2=SIRET, idprof3=APE).

View File

@@ -0,0 +1,14 @@
id name cnty role missing
--------------------------------------------------------------------------------------------------------------
1 KissMetrics US client tax_id
2 Wise Europe SA BE supplier tva_intra
3 Greffe du tribunal de commerce Évry FR supplier (complete)
4 YOLAW FR supplier (complete)
5 Medialex FR supplier SIRET, tva_intra
6 Qonto FR supplier SIRET
7 OVH FR supplier (complete)
8 La Poste FR supplier (complete)
9 Infogreffe FR supplier SIRET
10 Darnis Operations FR supplier (complete)
--------------------------------------------------------------------------------------------------------------
# 10 thirdparties audited, 5 with mandatory gaps

View File

@@ -0,0 +1,16 @@
================================================================================
Thirdparty 1 — KissMetrics [client, country=US]
================================================================================
[OK] name = 'KissMetrics'
[OK] address = '2850 34th Street North, 307'
[OK] zip = '33713'
[OK] town = 'St. Petersburg'
[OK] country_code = 'US'
[XX] idprof1 (tax id / EIN) = ''
[--] idprof2 = '' (optional)
[OK] email = 'evan@kissmetrics.io' (optional)
[--] phone = None (optional)
[--] url = None (optional)
[--] iban = (not set) (optional)
6 pass, 1 mandatory fail(s), 3 optional unset

View File

@@ -0,0 +1,16 @@
================================================================================
Thirdparty 2 — Wise Europe SA [supplier, country=BE]
================================================================================
[OK] name = 'Wise Europe SA'
[OK] address = 'Rue du Trône 100, 3rd floor'
[OK] zip = '1050'
[OK] town = 'Brussels'
[OK] country_code = 'BE'
[XX] tva_intra (VAT EU) = ''
[--] idprof1 (national reg) = '' (optional)
[--] email = None (optional)
[--] phone = None (optional)
[OK] url = 'wise.com' (optional)
[--] iban = (not set) (optional)
6 pass, 1 mandatory fail(s), 3 optional unset

View File

@@ -0,0 +1,18 @@
================================================================================
Thirdparty 5 — Medialex [supplier, country=FR]
================================================================================
[OK] name = 'Medialex'
[OK] address = '10 rue du Breil'
[OK] zip = '35063'
[OK] town = 'Rennes'
[OK] country_code = 'FR'
[OK] idprof1 (SIREN) = '353403074'
[XX] idprof2 (SIRET) = ''
[OK] idprof3 (APE) = '7312Z' (optional)
[XX] tva_intra (VAT) = ''
[--] email = None (optional)
[OK] phone = '0299264200' (optional)
[--] url = None (optional)
[--] iban = (not set) (optional)
8 pass, 2 mandatory fail(s), 2 optional unset

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
# Audit every visible Arcodange thirdparty for completeness.
#
# Usage:
# audit-all-thirdparties.sh [--clients-only] [--suppliers-only]
#
# Iterates /thirdparties, runs the per-id audit logic inline (one HTTP call
# per thirdparty), prints a compact table: id, name, country, role,
# mandatory-fail count, optional-unset count, top missing fields. Exits 0
# only if every visible thirdparty has zero mandatory failures.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOL_CURL="${SCRIPT_DIR}/../../dolibarr/scripts/dol-curl.sh"
FILTER="all"
while [[ $# -gt 0 ]]; do
case "$1" in
--clients-only) FILTER="client"; shift ;;
--suppliers-only) FILTER="supplier"; shift ;;
-h|--help) sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "audit-all-thirdparties.sh: unknown arg: $1" >&2; exit 2 ;;
esac
done
WORK="$(mktemp -d -t dolaud.XXXXXX)"
trap 'rm -rf "${WORK}"' EXIT
"${DOL_CURL}" '/thirdparties?limit=500' > "${WORK}/list.json"
IDS=$(python3 -c "
import json,sys
print(' '.join(str(t['id']) for t in json.load(open(sys.argv[1])) if t.get('id')))
" "${WORK}/list.json")
mkdir -p "${WORK}/tp"
for id in ${IDS}; do "${DOL_CURL}" "/thirdparties/${id}" > "${WORK}/tp/${id}.json"; done
python3 - "${WORK}" "${FILTER}" <<'PY'
import json, sys, os
work, filt = sys.argv[1], sys.argv[2]
EU = set("AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE".split())
def audit(d):
cnty = d.get("country_code") or ""
is_client = str(d.get("client") or "0") in ("1","2","3")
is_supplier = str(d.get("fournisseur") or "0") == "1"
# Build the same rules as audit-thirdparty.sh (keep in sync)
mandatory_missing = []
for label, val, mandatory in [
("name", d.get("name"), True),
("address", d.get("address"), True),
("zip", d.get("zip"), True),
("town", d.get("town"), True),
("country", d.get("country_code"), True),
]:
if mandatory and not (val not in (None, "", "0")): mandatory_missing.append(label)
if cnty == "FR":
for label, val in (("SIREN", d.get("idprof1")), ("SIRET", d.get("idprof2"))):
if not val: mandatory_missing.append(label)
if is_supplier and not d.get("tva_intra"):
mandatory_missing.append("tva_intra")
elif cnty in EU and cnty:
if not d.get("tva_intra"): mandatory_missing.append("tva_intra")
elif cnty:
if not d.get("idprof1"): mandatory_missing.append("tax_id")
role_bits = []
if is_client: role_bits.append("client")
if is_supplier: role_bits.append("supplier")
role = "/".join(role_bits) or "-"
return cnty, role, mandatory_missing
rows = []
for fn in sorted(os.listdir(os.path.join(work, "tp")), key=lambda f: int(f[:-len(".json")])):
try: d = json.load(open(os.path.join(work, "tp", fn)))
except json.JSONDecodeError: continue
cnty, role, missing = audit(d)
if filt == "client" and "client" not in role: continue
if filt == "supplier" and "supplier" not in role: continue
rows.append((d.get("id"), d.get("name") or d.get("ref"), cnty, role, missing))
print(f"{'id':>3} {'name':<35} {'cnty':<4} {'role':<16} {'missing'}")
print("-" * 110)
fails = 0
for iid, name, cnty, role, missing in rows:
miss = ", ".join(missing) if missing else "(complete)"
print(f"{iid:>3} {(name or '-')[:35]:<35} {cnty:<4} {role:<16} {miss}")
if missing: fails += 1
print("-" * 110)
print(f"# {len(rows)} thirdparties audited, {fails} with mandatory gaps")
sys.exit(0 if fails == 0 else 1)
PY

View File

@@ -0,0 +1,113 @@
#!/usr/bin/env bash
# Country-aware completeness audit for one Arcodange thirdparty.
#
# Usage:
# audit-thirdparty.sh <socid>
#
# Replaces the V1 dolibarr-invoice-audit/scripts/audit-km-thirdparty.sh
# (which hardcoded socid=1) with a generalized version that:
# - Detects whether the thirdparty is a client / supplier / both
# - Applies country-specific completeness rules
# FR → SIREN (idprof1) + SIRET (idprof2) + tva_intra (if VAT-registered)
# EU non-FR → tva_intra required for B2B autoliquidation
# Extra-EU → EIN-equivalent in idprof1 (or note that there's no enforceable rule)
# - Checks email / phone / url / IBAN where applicable
#
# Exits 0 if every applicable field is populated, 1 otherwise.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOL_CURL="${SCRIPT_DIR}/../../dolibarr/scripts/dol-curl.sh"
if [[ $# -lt 1 ]]; then
echo "audit-thirdparty.sh: missing socid. Usage: audit-thirdparty.sh <socid>" >&2
exit 2
fi
SOCID="$1"
TMP_JSON="$(mktemp -t doltp.XXXXXX.json)"
trap 'rm -f "${TMP_JSON}"' EXIT
"${DOL_CURL}" "/thirdparties/${SOCID}" > "${TMP_JSON}"
python3 - "${TMP_JSON}" <<'PY'
import json, sys
with open(sys.argv[1]) as f:
d = json.load(f)
if not d.get("id"):
print(f"audit-thirdparty.sh: no thirdparty at id={sys.argv[1]}", file=sys.stderr)
sys.exit(2)
EU = set("AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE".split())
socid = d.get("id")
name = d.get("name") or d.get("ref")
cnty = d.get("country_code") or ""
is_client = str(d.get("client") or "0") in ("1", "2", "3") # 1=client, 2=prospect, 3=both
is_supplier = str(d.get("fournisseur") or "0") == "1"
role_bits = []
if is_client: role_bits.append("client")
if is_supplier: role_bits.append("supplier")
role = "/".join(role_bits) or "neither"
# What completeness rules apply depends on country + role
def rules_for(cnty, is_client, is_supplier):
rules = []
# Universal
rules.append(("name", d.get("name"), True))
rules.append(("address", d.get("address"), True))
rules.append(("zip", d.get("zip"), True))
rules.append(("town", d.get("town"), True))
rules.append(("country_code", d.get("country_code"), True))
# Country-specific identifiers
if cnty == "FR":
rules.append(("idprof1 (SIREN)", d.get("idprof1"), True))
rules.append(("idprof2 (SIRET)", d.get("idprof2"), True))
rules.append(("idprof3 (APE)", d.get("idprof3"), False)) # nice-to-have
rules.append(("tva_intra (VAT)", d.get("tva_intra"), is_supplier)) # mandatory if supplier
elif cnty in EU and cnty:
rules.append(("tva_intra (VAT EU)", d.get("tva_intra"), True)) # autoliquidation requires it
rules.append(("idprof1 (national reg)", d.get("idprof1"), False))
elif cnty:
# Non-EU
rules.append(("idprof1 (tax id / EIN)", d.get("idprof1"), True))
rules.append(("idprof2", d.get("idprof2"), False))
# Contact
rules.append(("email", d.get("email"), False))
rules.append(("phone", d.get("phone"), False))
rules.append(("url", d.get("url"), False))
return rules
rules = rules_for(cnty, is_client, is_supplier)
print("=" * 80)
print(f" Thirdparty {socid} — {name} [{role}, country={cnty or '?'}]")
print("=" * 80)
mandatory_fails = 0
optional_fails = 0
for label, value, mandatory in rules:
ok = value not in (None, "", "0")
flag = "OK" if ok else ("XX" if mandatory else "--")
suffix = "" if mandatory else " (optional)"
print(f" [{flag}] {label:<22} = {value!r}{suffix}")
if not ok:
if mandatory: mandatory_fails += 1
else: optional_fails += 1
# Surface what bank account info we have, if any
account_iban = d.get("iban") or ""
if account_iban:
print(f" [OK] iban = {account_iban!r}")
elif is_client or is_supplier:
print(f" [--] iban = (not set) (optional)")
ao = d.get("array_options") or {}
if ao:
print(f" array_options (extrafields): {ao}")
print()
print(f" {len(rules) - mandatory_fails - optional_fails} pass, {mandatory_fails} mandatory fail(s), {optional_fails} optional unset")
sys.exit(0 if mandatory_fails == 0 else 1)
PY

View File

@@ -0,0 +1,113 @@
---
name: dolibarr-tva-deductible
description: Prepare the TVA déductible side of the French monthly declaration (CA3 lignes 19 / 20 / 17 / 24) from Arcodange supplier invoices (`/supplierinvoices`). Two workflows — (1) per-period basis × rate aggregation (HT and TVA déductible grouped by year-month and tva_tx) ready to transcribe onto CA3 lignes 20 (standard 20 %), 19 (taux réduits), 17+24 (autoliquidation intra-UE); (2) per-line audit trail with supplier country classification (FR domestic / EU intra-UE / extra-EU import). Mirrors `dolibarr-tva-reconciliation` on the supplier side — TVA collectée vs TVA déductible = TVA nette à reverser. Today on Arcodange the TVA déductible totals ~223 €/all-time across 12 supplier-side 20 % lines plus 1 autoliquidation intra-UE entry (Wise BE) and 1 FR exempt line (timbres La Poste). Use when the user asks "préparer TVA déductible", "achats du mois", "TVA à récupérer", "réconciliation TVA fournisseur", "combien de TVA nette à reverser". Depends on the `dolibarr` skill. SKIP for TVA collectée (handled by `dolibarr-tva-reconciliation`), for the customer-side audit (different skills), and for writes (the declaration goes through impots.gouv.fr).
requires:
bins: ["curl", "jq", "python3"]
auth: true
---
# dolibarr-tva-deductible — supplier-side TVA monthly preparation
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.
- **TVA déductible** (this skill) — what Arcodange paid to suppliers and can deduct.
The net to remit is the difference. **Read-only**: the declaration itself goes through impots.gouv.fr.
Depends on the [dolibarr](../dolibarr/SKILL.md) base skill.
## CA3 mapping (supplier side)
| Bucket | Condition | CA3 line(s) |
|---|---|---|
| **20 % standard** | `tva_tx == 20` | ligne 20 |
| **10 % intermediate** | `tva_tx == 10` | ligne 19 |
| **5.5 % reduced** | `tva_tx == 5.5` | ligne 19 |
| **2.1 % special** | `tva_tx == 2.1` | ligne 19 |
| **Intra-UE autoliquidation** | `tva_tx == 0` AND supplier in EU (excl. FR) | ligne 17 (TVA auto-collectée) + ligne 24 (TVA déductible) |
| **FR exempt / HT-only** | `tva_tx == 0` AND supplier country == FR | (e.g. timbres La Poste — no TVA line) |
| **Extra-EU import** | `tva_tx == 0` AND supplier outside EU | ligne 7 (import TVA — usually via customs declaration) |
## API gotchas to know
- **`/supplierinvoices/{id}/lines` returns HTTP 403** for `ai_agent`. We don't need it — lines are also included inline on `/supplierinvoices/{id}`. (Reference: [../dolibarr/examples/acl_403_supplier_lines.json](../dolibarr/examples/acl_403_supplier_lines.json).)
- **`/supplierinvoices` list works fine** — no `voir_tous` gap on supplier invoices the way V1 hit on customer ones.
## Workflow 1 — Aggregate per month × rate (the CA3 basis)
```bash
./scripts/deductible-by-month.sh # all-time
./scripts/deductible-by-month.sh --year 2026
./scripts/deductible-by-month.sh --since 2026-01-01 --until 2026-01-31
```
Live output (captured at [examples/deductible-by-month.txt](examples/deductible-by-month.txt)):
```
# TVA déductible by month × rate — window=-inf → +inf
month tva_tx count basis HT TVA ded CA3 line
------------------------------------------------------------------------------
2025-10 20.0000 2 6.08 1.22 ligne 20
2026-01 0.0000 2 58.43 0.00 ligne 17 + 24 (autoliquidation) — verify with line-detail
2026-01 20.0000 8 345.02 69.00 ligne 20
2026-02 20.0000 2 765.00 153.00 ligne 20
------------------------------------------------------------------------------
TOTAL 1174.53 223.22
```
The 2026-02 ligne 20 number (765 € HT / 153 € TVA) is dominated by Darnis Operations (510 + 255). The 2026-01 row at 0 % is mixed (Wise BE autoliquidation 50 € + La Poste FR exempt 8.43 €) — workflow 2 splits them properly.
## Workflow 2 — Per-line audit (CA3 bucket assignment)
```bash
./scripts/deductible-line-detail.sh # all-time
./scripts/deductible-line-detail.sh --year 2026
./scripts/deductible-line-detail.sh --since 2026-02-01 --until 2026-02-28
```
Live output (captured at [examples/deductible-line-detail.txt](examples/deductible-line-detail.txt)) — supplier name and country annotated:
```
date ref supplier cnty tx HT TVA CA3 bucket
------------------------------------------------------------------------------------------------------------------------
2026-01-26 FAF2026001 Wise Europe SA BE 0.00 50.00 0.00 ligne 17+24 (autoliquidation intra-UE)
2026-02-28 FAF2026009 Darnis Operations FR 20.00 255.00 51.00 ligne 20/19 (déductible 20.0%)
...
2026-01-12 FAF2026006 La Poste FR 0.00 8.43 0.00 FR exempt / HT seulement
...
# Aggregated by CA3 bucket:
FR exempt / HT seulement count= 1 HT= 8.43 TVA= 0.00
ligne 17+24 (autoliquidation intra-UE) count= 1 HT= 50.00 TVA= 0.00
ligne 20/19 (déductible 20.0%) count= 12 HT= 1116.10 TVA= 223.22
```
## Tying back to the CA3 — combined view
For any given month, the **net TVA à reverser** computation is:
```
net = TVA collectée (from tva-by-month.sh)
- TVA déductible (from deductible-by-month.sh)
- autoliquidation neutralized (ligne 17 = ligne 24, cancels)
```
For Arcodange today:
- TVA collectée = 0 € (all KissMetrics, all E2 autoliquidation 259-1°)
- TVA déductible = 223.22 € (mainly 20 % FR suppliers)
- **Net = TVA crédit de 223.22 €** — Arcodange is in TVA credit, can request reimbursement or carry forward.
That's the kind of summary a `dolibarr-tva-summary` (or `arcodange-tva-monthly-report`) skill could produce by composing this skill + `dolibarr-tva-reconciliation`. V5 candidate.
## Wise BE specific gotcha
The Wise Europe SA supplier (BE) currently has **no `tva_intra` field populated** ([dolibarr-thirdparty-completeness](../dolibarr-thirdparty-completeness/SKILL.md) flags this). Article 196 directive TVA requires the EU supplier's VAT number on Arcodange's books to substantiate the autoliquidation entry. **Mandatory remediation** before the next TVA declaration: add Wise's BE VAT number (BE0833 281 858 per their public registration) on the thirdparty record.
## Out of scope
- **Writes.** The declaration goes through impots.gouv.fr.
- **Customs / import TVA on goods.** Service-only Arcodange today; if goods imports happen, customs declarations are out of scope.
- **TVA sur encaissements** (régime spécial — TVA due on the payment date rather than invoice date for services). Not Arcodange's regime today.
- **Composition with collectée** (the net-to-remit summary). V5 candidate — a `dolibarr-tva-summary` skill.

View File

@@ -0,0 +1,16 @@
# TVA déductible by month × rate — window=-inf → +inf
month tva_tx count basis HT TVA ded CA3 line
------------------------------------------------------------------------------
2025-10 20.0000 2 6.08 1.22 ligne 20
2026-01 0.0000 2 58.43 0.00 ligne 17 + 24 (autoliquidation) — verify with line-detail
2026-01 20.0000 8 345.02 69.00 ligne 20
2026-02 20.0000 2 765.00 153.00 ligne 20
------------------------------------------------------------------------------
TOTAL 1174.53 223.22
# Notes:
# - This is TVA déductible (supplier side). For TVA collectée (customer side),
# use dolibarr-tva-reconciliation/scripts/tva-by-month.sh.
# - tva_tx==0 lines may be either truly exempt (e.g. La Poste timbres)
# or autoliquidation intra-UE (e.g. Wise). Run deductible-line-detail.sh.

View File

@@ -0,0 +1,22 @@
date ref supplier cnty tx HT TVA CA3 bucket
------------------------------------------------------------------------------------------------------------------------
2026-01-26 FAF2026001 Wise Europe SA BE 0.00 50.00 0.00 ligne 17+24 (autoliquidation intra-UE)
2026-02-28 FAF2026009 Darnis Operations FR 20.00 255.00 51.00 ligne 20/19 (déductible 20.0%)
2026-01-09 FAF2026002 Greffe du tribunal de commer FR 20.00 16.95 3.39 ligne 20/19 (déductible 20.0%)
2026-01-09 FAF2026002 Greffe du tribunal de commer FR 20.00 23.30 4.66 ligne 20/19 (déductible 20.0%)
2026-01-09 FAF2026002 Greffe du tribunal de commer FR 20.00 6.36 1.27 ligne 20/19 (déductible 20.0%)
2026-01-09 FAF2026002 Greffe du tribunal de commer FR 20.00 1.08 0.22 ligne 20/19 (déductible 20.0%)
2026-01-04 FAF2026003 YOLAW FR 20.00 1.66 0.33 ligne 20/19 (déductible 20.0%)
2026-01-09 FAF2026004 Medialex FR 20.00 124.00 24.80 ligne 20/19 (déductible 20.0%)
2026-01-06 FAF2026005 Qonto FR 20.00 169.00 33.80 ligne 20/19 (déductible 20.0%)
2025-10-24 FAF2025001 OVH FR 20.00 4.99 1.00 ligne 20/19 (déductible 20.0%)
2025-10-24 FAF2025001 OVH FR 20.00 1.09 0.22 ligne 20/19 (déductible 20.0%)
2026-01-12 FAF2026006 La Poste FR 0.00 8.43 0.00 FR exempt / HT seulement
2026-01-17 FAF2026007 Infogreffe FR 20.00 2.67 0.53 ligne 20/19 (déductible 20.0%)
2026-02-28 FAF2026008 Darnis Operations FR 20.00 510.00 102.00 ligne 20/19 (déductible 20.0%)
------------------------------------------------------------------------------------------------------------------------
# Aggregated by CA3 bucket:
FR exempt / HT seulement count= 1 HT= 8.43 TVA= 0.00
ligne 17+24 (autoliquidation intra-UE) count= 1 HT= 50.00 TVA= 0.00
ligne 20/19 (déductible 20.0%) count= 12 HT= 1116.10 TVA= 223.22

View File

@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# Monthly TVA déductible aggregation for Arcodange — the supplier-side
# counterpart to dolibarr-tva-reconciliation.
#
# Usage:
# deductible-by-month.sh [--year YYYY] [--since YYYY-MM-DD] [--until YYYY-MM-DD]
#
# Groups every visible supplier invoice line by (year-month, tva_tx) and sums
# HT and TVA. The TVA figure feeds the CA3 lignes 19 / 20 (déductible).
#
# Mapping to French CA3:
# - tva_tx == 20 → ligne 20 (TVA déductible normale 20 %)
# - tva_tx == 10 → ligne 19 (taux intermédiaire)
# - tva_tx == 5.5 → ligne 19 (taux réduit)
# - tva_tx == 2.1 → ligne 19 (taux particulier)
# - tva_tx == 0 → likely autoliquidation intra-UE (achats à l'étranger)
# → goes on ligne 17 (auto-collected) AND ligne 24 (déductible)
# run deductible-line-detail.sh for the per-supplier breakdown
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,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "deductible-by-month.sh: unknown arg: $1" >&2; exit 2 ;;
esac
done
WORK="$(mktemp -d -t deduc.XXXXXX)"
trap 'rm -rf "${WORK}"' EXIT
"${DOL_CURL}" '/supplierinvoices?limit=500' > "${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")
mkdir -p "${WORK}/detail"
for id in ${IDS}; do "${DOL_CURL}" "/supplierinvoices/${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
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")
lines = inv.get("lines") or []
if lines:
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)
agg[(ym, tx)]["ht"] += ht
agg[(ym, tx)]["tva"] += tva
agg[(ym, tx)]["count"] += 1
else:
# Fallback: aggregate at invoice level (no lines available)
# Derive an effective tva_tx if possible: TVA / HT × 100
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
agg[(ym, tx)]["ht"] += ht
agg[(ym, tx)]["tva"] += tva
agg[(ym, tx)]["count"] += 1
def ca3_line(tx):
if tx == 20: return "ligne 20"
if tx == 10: return "ligne 19 (10 %)"
if tx == 5.5: return "ligne 19 (5,5 %)"
if tx == 2.1: return "ligne 19 (2,1 %)"
if tx == 0: return "ligne 17 + 24 (autoliquidation) — verify with line-detail"
return f"manual @ {tx}%"
scope = f"window={since or '-inf'} → {until or '+inf'}"
if year_arg: scope = f"year {year_arg}"
print(f"# TVA déductible by month × rate — {scope}")
print()
print(f"{'month':<8} {'tva_tx':>7} {'count':>5} {'basis HT':>12} {'TVA ded':>10} CA3 line")
print("-" * 78)
total_ht = total_tva = 0.0
for key in sorted(agg):
ym, tx = key
s = agg[key]
print(f"{ym:<8} {tx:>7.4f} {s['count']:>5} {s['ht']:>12.2f} {s['tva']:>10.2f} {ca3_line(tx)}")
total_ht += s["ht"]
total_tva += s["tva"]
print("-" * 78)
print(f"{'TOTAL':>16} {' ':>13} {total_ht:>12.2f} {total_tva:>10.2f}")
print()
print("# Notes:")
print("# - This is TVA déductible (supplier side). For TVA collectée (customer side),")
print("# use dolibarr-tva-reconciliation/scripts/tva-by-month.sh.")
print("# - tva_tx==0 lines may be either truly exempt (e.g. La Poste timbres)")
print("# or autoliquidation intra-UE (e.g. Wise). Run deductible-line-detail.sh.")
PY

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env bash
# Per-line TVA déductible breakdown with supplier country classification.
#
# Usage:
# deductible-line-detail.sh [--year YYYY] [--since YYYY-MM-DD] [--until YYYY-MM-DD]
#
# For each supplier-invoice line: date, supplier name, country, tva_tx,
# HT, TVA, and a French CA3 bucket assignment. Plus a summary per bucket.
#
# Buckets:
# - FR domestic with TVA → ligne 20 / 19 (TVA déductible standard)
# - EU intra-UE no TVA → ligne 17 (auto-collected) + ligne 24 (déductible) — autoliquidation
# - Extra-EU no TVA → import — likely requires customs TVA declaration, see ligne 7
# - FR with TVA == 0 → exempt or HT-only invoice (e.g. timbres La Poste, AMF)
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 "deductible-line-detail.sh: unknown arg: $1" >&2; exit 2 ;;
esac
done
WORK="$(mktemp -d -t deducline.XXXXXX)"
trap 'rm -rf "${WORK}"' EXIT
"${DOL_CURL}" '/supplierinvoices?limit=500' > "${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}" "/supplierinvoices/${id}" > "${WORK}/inv/${id}.json"; done
for s in ${SOCIDS}; do "${DOL_CURL}" "/thirdparties/${s}" > "${WORK}/tp/${s}.json"; done
python3 - "${WORK}" "${SINCE}" "${UNTIL}" "${YEAR}" <<'PY'
import json, sys, os, datetime, collections
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())
tp = {}
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[fn[:-len(".json")]] = {
"name": d.get("name") or d.get("ref") or "-",
"country": d.get("country_code") or "",
}
def bucket(country, tx, ht, tva):
if tx and tx > 0:
return f"ligne 20/19 (déductible {tx}%)"
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)"
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
print(f"{'date':<10} {'ref':<14} {'supplier':<28} {'cnty':<4} {'tx':>5} {'HT':>10} {'TVA':>8} CA3 bucket")
print("-" * 120)
agg = collections.defaultdict(lambda: {"ht":0.0,"tva":0.0,"n":0})
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
dt = datetime.date.fromtimestamp(ts)
ref = inv.get("ref") or "-"
sid = str(inv.get("socid") or "")
sup = tp.get(sid, {}).get("name", f"socid={sid}")
cnty = tp.get(sid, {}).get("country", "")
lines = inv.get("lines") or []
if not lines:
# Aggregate at invoice level
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(cnty, tx, ht, tva)
agg[b]["ht"] += ht; agg[b]["tva"] += tva; agg[b]["n"] += 1
print(f"{dt} {ref:<14} {sup[:28]:<28} {cnty:<4} {tx:>5.2f} {ht:>10.2f} {tva:>8.2f} {b}")
print("-" * 120)
print()
print("# Aggregated by CA3 bucket:")
for b, s in sorted(agg.items()):
print(f" {b:<45} count={s['n']:>3} HT={s['ht']:>10.2f} TVA={s['tva']:>8.2f}")
PY

View File

@@ -30,6 +30,8 @@ In the Dolibarr UI (https://erp.arcodange.lab/ → **Setup → Users & Groups
- [ ] **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)
- [ ] **Factures fournisseurs** → Lire les factures fournisseurs (required by `dolibarr-tva-deductible`)
- [ ] **Factures fournisseurs** → Voir toutes les factures fournisseurs
Save. Future modules used by `dolibarr-*` sibling skills (Paiements, Produits, …) need the same treatment.

View File

@@ -94,6 +94,7 @@ Read-only endpoints we've validated against this instance. Live captures are und
| `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`, `KM-cloud-devops` is `id=2` | [products_list.json](examples/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](examples/supplierinvoices_list.json) · [supplierinvoice_detail.json](examples/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](examples/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](examples/document_download_meta.json) |
@@ -139,6 +140,8 @@ Not available on this account (intentionally): `/setup/modules` (admin-only), `/
- Workflow skill for monthly TVA basis (CA3 / CA12 preparation): [dolibarr-tva-reconciliation](../dolibarr-tva-reconciliation/SKILL.md).
- Workflow skill for recurring invoice templates: [dolibarr-recurring-templates](../dolibarr-recurring-templates/SKILL.md).
- 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).
- 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

View File

@@ -0,0 +1,16 @@
{
"_captured_when": "2026-05-29 — ai_agent permission baseline. Lines come through inline on /supplierinvoices/{id} so this 403 is informational only and doesn't block any current workflow. If a future skill needs the dedicated /lines endpoint, ai_agent needs an additional permission grant.",
"_curl": "curl -H 'DOLAPIKEY: <key>' https://erp.arcodange.lab/api/index.php/supplierinvoices/1/lines",
"_http_status": 403,
"error": {
"code": 403,
"message": "Forbidden"
},
"debug": {
"source": "api_supplier_invoices.class.php:614 at call stage",
"stages": {
"success": ["get", "route", "negotiate", "authenticate", "validate"],
"failure": ["call", "message"]
}
}
}

View File

@@ -0,0 +1,257 @@
{
"module": null,
"id": "1",
"entity": "1",
"import_key": null,
"array_options": [],
"array_languages": null,
"contacts_ids": null,
"contacts_ids_internal": null,
"linkedObjectsIds": [],
"canvas": null,
"fk_project": null,
"contact_id": null,
"user": null,
"origin_type": null,
"origin_id": null,
"ref": "FAF2026001",
"ref_ext": "",
"statut": "2",
"status": "2",
"country_id": null,
"country_code": null,
"state_id": null,
"region_id": null,
"mode_reglement_id": "2",
"cond_reglement_id": null,
"demand_reason_id": null,
"transport_mode_id": null,
"shipping_method_id": null,
"shipping_method": null,
"fk_multicurrency": "0",
"multicurrency_code": "EUR",
"multicurrency_tx": "1.00000000",
"multicurrency_total_ht": "50.00000000",
"multicurrency_total_tva": "0.00000000",
"multicurrency_total_localtax1": null,
"multicurrency_total_localtax2": null,
"multicurrency_total_ttc": "50.00000000",
"last_main_doc": null,
"fk_account": "1",
"note_public": "",
"note_private": "",
"total_ht": "50.00000000",
"total_tva": "0.00000000",
"total_localtax1": "0.00000000",
"total_localtax2": "0.00000000",
"total_ttc": "50.00000000",
"lines": [
{
"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,
"origin_type": null,
"origin_id": null,
"ref": null,
"ref_ext": null,
"statut": null,
"status": null,
"state_id": null,
"region_id": null,
"demand_reason_id": null,
"transport_mode_id": null,
"shipping_method": null,
"multicurrency_tx": null,
"multicurrency_total_ht": "50.00000000",
"multicurrency_total_tva": "0.00000000",
"multicurrency_total_localtax1": null,
"multicurrency_total_localtax2": null,
"multicurrency_total_ttc": "50.00000000",
"last_main_doc": null,
"fk_account": null,
"total_ht": "50.00000000",
"total_tva": "0.00000000",
"total_localtax1": "0.00000000",
"total_localtax2": "0.00000000",
"total_ttc": "50.00000000",
"lines": null,
"actiontypecode": null,
"civility_code": null,
"date_creation": null,
"date_validation": null,
"date_modification": null,
"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,
"parent_element": "facture_fourn",
"fk_parent_attribute": "fk_facture_fourn",
"fk_unit": null,
"date_debut_prevue": null,
"date_debut_reel": null,
"date_fin_prevue": null,
"date_fin_reel": null,
"weight": null,
"weight_units": null,
"length": null,
"length_units": null,
"width": null,
"width_units": null,
"height": null,
"height_units": null,
"surface": null,
"surface_units": null,
"volume": null,
"volume_units": null,
"multilangs": null,
"product_type": "1",
"fk_product": null,
"desc": "Ouverture compte -&nbsp;<em>Exon&eacute;ration de TVA &ndash; Article 261 C du CGI</em>",
"description": "Ouverture compte -&nbsp;<em>Exon&eacute;ration de TVA &ndash; Article 261 C du CGI</em>",
"product_ref": null,
"product_label": null,
"product_barcode": null,
"product_desc": null,
"fk_product_type": null,
"qty": "1",
"duree": null,
"remise_percent": "0",
"info_bits": "0",
"special_code": "0",
"subprice": "50.00000000",
"subprice_ttc": null,
"tva_tx": "0.0000",
"multicurrency_subprice": "50.00000000",
"multicurrency_subprice_ttc": null,
"ref_supplier": "",
"pu_ht": "50.00000000",
"pu_ttc": "50.00000000",
"fk_facture_fourn": "1",
"label": null,
"date_start": "",
"date_end": "",
"fk_code_ventilation": null,
"situation_percent": null,
"fk_prev_id": null,
"vat_src_code": "",
"localtax1_tx": "0.0000",
"localtax2_tx": "0.0000",
"pa_ht": null,
"fk_remise_except": null,
"fk_parent_line": null,
"rang": "1",
"localtax1_type": "0",
"localtax2_type": "0",
"libelle": null,
"fk_accounting_account": "9"
}
],
"actiontypecode": null,
"name": null,
"lastname": null,
"firstname": null,
"civility_id": null,
"civility_code": null,
"date_creation": null,
"date_validation": null,
"date_modification": null,
"tms": 1771929935,
"date_cloture": null,
"user_author": null,
"user_creation": null,
"user_creation_id": "2",
"user_valid": null,
"user_validation": null,
"user_validation_id": "2",
"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,
"title": null,
"type": 0,
"subtype": 0,
"fk_soc": null,
"socid": "2",
"paye": "1",
"date": 1769382000,
"date_lim_reglement": null,
"cond_reglement_code": null,
"cond_reglement_label": null,
"cond_reglement_doc": null,
"mode_reglement_code": "VIR",
"revenuestamp": null,
"totaldeposits": null,
"totalcreditnotes": null,
"sumpayed": null,
"sumpayed_multicurrency": null,
"sumdeposit": null,
"sumdeposit_multicurrency": null,
"sumcreditnote": null,
"sumcreditnote_multicurrency": null,
"remaintopay": null,
"nbofopendirectdebitorcredittransfer": null,
"creditnote_ids": [],
"stripechargedone": null,
"stripechargeerror": null,
"description": null,
"ref_client": null,
"situation_cycle_ref": null,
"close_code": null,
"close_note": null,
"postactionmessages": null,
"fk_incoterms": "0",
"label_incoterms": null,
"location_incoterms": "",
"ref_supplier": "PLAN_ORDER_CHECKOUT-invoice-23917601",
"libelle": "",
"label": "",
"fk_statut": "2",
"paid": "1",
"datec": 1771931434,
"date_echeance": 1769382000,
"amount": 0,
"remise": 0,
"tva": null,
"localtax1": null,
"localtax2": null,
"propalid": null,
"vat_reverse_charge": 0,
"fournisseur": null,
"fk_facture_source": null,
"fac_rec": null,
"fk_fac_rec_source": null,
"fk_user_valid": null
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff