From d2e8b3a3a43f0a350306921a33ba02c63d1be934 Mon Sep 17 00:00:00 2001 From: Gabriel Radureau Date: Mon, 29 Jun 2026 20:49:31 +0200 Subject: [PATCH] =?UTF-8?q?feat(skills):=20dolibarr-sandbox-write=20?= =?UTF-8?q?=E2=80=94=20host-guarded=20write=20skill=20(V9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The write-capable companion to the read-only dolibarr* skills, scoped to the erp-sandbox. Lets an AI agent rehearse bookkeeping writes against a copy of prod (ADR-0003) before a human promotes the reviewed change to prod. - scripts/dol-write.sh: write wrapper that REFUSES any host that is not erp-sandbox.arcodange.lab (the structural prod-safety guarantee) using the ai_agent_sandbox key from a gitignored .env. - scripts/thirdparty-create.sh: create client/supplier fiches; codes auto-assign via the elephant mask (code="-1"). - scripts/invoice-create.sh: customer (/invoices) or supplier (/supplierinvoices) invoices with product/service lines + ref_supplier, optional validate. - scripts/payment-record.sh: record a règlement (VIR/CB/CHQ/LIQ); customer pays full + marks paid, supplier needs an amount. - SKILL.md (safety model + workflows + the human-gated promote flow), .env.example, example input. Proven end-to-end live against the sandbox: client -> invoice (service+product lines, HT 1100 / TTC 1320) -> validate -> payment (paid); supplier -> supplier invoice (ref_supplier carried) -> validate. Host guard verified to refuse a prod URL before sending. Avoirs (credit notes) and bin/arcodange CLI wiring are planned follow-ups. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../dolibarr-sandbox-write/.env.example | 10 ++ .../skills/dolibarr-sandbox-write/SKILL.md | 117 ++++++++++++++++++ .../examples/customer-invoice.json | 9 ++ .../scripts/dol-write.sh | 82 ++++++++++++ .../scripts/invoice-create.sh | 68 ++++++++++ .../scripts/payment-record.sh | 59 +++++++++ .../scripts/thirdparty-create.sh | 50 ++++++++ 7 files changed, 395 insertions(+) create mode 100644 .claude/skills/dolibarr-sandbox-write/.env.example create mode 100644 .claude/skills/dolibarr-sandbox-write/SKILL.md create mode 100644 .claude/skills/dolibarr-sandbox-write/examples/customer-invoice.json create mode 100755 .claude/skills/dolibarr-sandbox-write/scripts/dol-write.sh create mode 100755 .claude/skills/dolibarr-sandbox-write/scripts/invoice-create.sh create mode 100755 .claude/skills/dolibarr-sandbox-write/scripts/payment-record.sh create mode 100755 .claude/skills/dolibarr-sandbox-write/scripts/thirdparty-create.sh diff --git a/.claude/skills/dolibarr-sandbox-write/.env.example b/.claude/skills/dolibarr-sandbox-write/.env.example new file mode 100644 index 0000000..48f107c --- /dev/null +++ b/.claude/skills/dolibarr-sandbox-write/.env.example @@ -0,0 +1,10 @@ +# Copy to .env (mode 600 — gitignored). Never commit a real key. +DOLIBARR_SANDBOX_URL=https://erp-sandbox.arcodange.lab +DOLIBARR_SANDBOX_API_KEY= + +# Populate the key from the Playwright provisioner output (repo test/): +# printf 'DOLIBARR_SANDBOX_API_KEY=%s\n' "$(cat ../../../test/.ai_agent_sandbox.key)" >> .env +# +# The host guard only allows the sandbox FQDN. Override the pattern ONLY if the +# sandbox host changes — never widen it to a prod host. +# DOLIBARR_SANDBOX_HOST_RE=^https://erp-sandbox\.arcodange\.lab(/|$) diff --git a/.claude/skills/dolibarr-sandbox-write/SKILL.md b/.claude/skills/dolibarr-sandbox-write/SKILL.md new file mode 100644 index 0000000..6785224 --- /dev/null +++ b/.claude/skills/dolibarr-sandbox-write/SKILL.md @@ -0,0 +1,117 @@ +--- +name: dolibarr-sandbox-write +description: >- + WRITE operations against the Arcodange Dolibarr SANDBOX (erp-sandbox.arcodange.lab) + — the rehearsal environment where an AI agent records thirdparties, invoices and + payments before any change is promoted to prod. Create client/supplier fiches + (auto-coded), customer + supplier invoices with product/service lines and the + supplier's own reference, validate them, and record règlements (payments). Every + write goes through dol-write.sh, which REFUSES any host that is not the sandbox — + the structural guarantee (ADR-0003) that this skill can never mutate production. + Use when the user asks to "create a thirdparty / supplier / client fiche", "saisir + une facture", "record an invoice with lines", "enregistrer un règlement / paiement", + or to rehearse a write before promoting it to prod. SKIP for production writes + (prod stays read-only via the `dolibarr` skill's `ai_agent` key; promotion is a + separate, human-gated replay), and for credit notes/avoirs (a planned follow-up). + Depends on the write-scoped `ai_agent_sandbox` Dolibarr user + its API key. +requires: + bins: [bash, curl, python3] + auth: ".env with DOLIBARR_SANDBOX_URL + DOLIBARR_SANDBOX_API_KEY (mode 600, gitignored)" +--- + +# dolibarr-sandbox-write + +Write-capable companion to the read-only `dolibarr*` skills, scoped to the +**sandbox**. It exists so an AI agent can *rehearse* bookkeeping writes against a +faithful copy of prod (see ADR-0003 + the `ops/sandbox/` seed tooling), then a +human promotes the reviewed change to prod. + +## The safety model (read this first) + +- **Host guard.** `scripts/dol-write.sh` reads `DOLIBARR_SANDBOX_URL` from `.env` + and refuses to send any request unless it matches `erp-sandbox.arcodange.lab`. + Point it at `erp.arcodange.lab` (prod) and it exits non-zero *before* the + request. This is the structural reason the skill cannot write prod. +- **Credential scope.** The key is `ai_agent_sandbox`'s — valid only on the + sandbox host, with create+read rights on thirdparties / invoices / supplier + invoices / products / contacts (+ `societe client voir`). Prod's `ai_agent` + key is read-only and lives in a different skill's `.env`. +- **Resettable.** Anything written here is wiped by `ops/sandbox/sandbox-lifecycle.sh + refresh-from-prod`, so mistakes cost a reset, not data. +- **Promotion to prod is NOT in this skill.** Rehearse here → capture a reviewable + diff with the read-only `dolibarr-data-snapshot` skill (before/after) → a human + approves → the same operations are replayed against prod under a separate, + human-held prod-write credential. Never wire a prod-write key into this skill. + +## Setup + +Create `.env` (mode 600, gitignored) next to `scripts/`: + +```sh +cd .claude/skills/dolibarr-sandbox-write +umask 077 +{ echo "DOLIBARR_SANDBOX_URL=https://erp-sandbox.arcodange.lab" + printf 'DOLIBARR_SANDBOX_API_KEY=%s\n' "$(cat /path/to/.ai_agent_sandbox.key)"; } > .env +``` + +The key is produced by the Playwright provisioner in the repo's `test/` +(`provisionSandbox.ts` → `.ai_agent_sandbox.key`). Verify: `scripts/dol-write.sh +GET /status` should return HTTP 200 with `"environment":"non-production"`. + +## Workflows + +All three read a JSON object on **stdin** (or a file path as `$1`) and emit ids. + +### 1 · Thirdparty (fiche client/fournisseur) — `scripts/thirdparty-create.sh` + +```sh +echo '{"name":"KissMetrics","role":"client","tva_intra":"US.."}' | scripts/thirdparty-create.sh +echo '{"name":"OVH","role":"supplier","siret":"..."}' | scripts/thirdparty-create.sh +``` +`role`: `client` | `supplier` | `both`. Codes auto-assign from the mask +(`CL{0000}` / `FO{0000}`) via the `-1` sentinel; pass `client_code`/`supplier_code` +to override. Optional: `country_id` (default 1=FR), `siret`, `tva_intra`, +`address`, `zip`, `town`, `email`, `phone`, `idprof1`. Emits the new id. + +### 2 · Invoice (facture) — `scripts/invoice-create.sh` + +```sh +echo '{"socid":42,"kind":"customer","validate":true, + "lines":[{"desc":"Conseil","qty":2,"price_ht":500,"tva":20,"type":"service"}, + {"desc":"Licence","qty":1,"price_ht":100,"tva":20,"type":"product"}]}' \ + | scripts/invoice-create.sh +# supplier invoice carrying the supplier's own reference: +echo '{"socid":7,"kind":"supplier","ref_supplier":"INV-2026-042","validate":true, + "lines":[{"desc":"Hosting","qty":1,"price_ht":80,"tva":20,"type":"service"}]}' \ + | scripts/invoice-create.sh +``` +`kind`: `customer` (`/invoices`) | `supplier` (`/supplierinvoices`). Lines carry +`desc, qty, price_ht, tva, type` (product|service) and optional `product_id` +(`fk_product`) to link a catalogue product. Totals + TVA are computed by Dolibarr. +`validate:true` turns the draft (`PROV…`) into a final numbered invoice; omit it +to leave a draft. Emits `{id, ref, ref_supplier, total_ht, total_ttc, statut}`. + +### 3 · Payment (règlement) — `scripts/payment-record.sh` + +```sh +echo '{"invoice_id":19,"mode":"VIR","account_id":1}' | scripts/payment-record.sh +echo '{"invoice_id":13,"kind":"supplier","mode":"VIR","account_id":1,"amount":96}' \ + | scripts/payment-record.sh +``` +The invoice must be **validated** first. `mode`: `VIR|CB|CHQ|LIQ`. Customer +payments settle the full remaining amount and mark the invoice paid; **supplier** +payments require an explicit `amount`. `account_id` is the bank account id (the +read-only `dolibarr-payments-state` skill lists them; `ai_agent_sandbox` does not +yet have `banque lire`, so pass the id). Emits the new payment id. + +## Gotchas + +- **Validate before paying.** A draft (`statut=0`, ref `PROV…`) cannot be paid. +- **Codes.** The thirdparty code module is `mod_codeclient_elephant` (auto). The + REST create needs `code_client`/`code_fournisseur = "-1"` to trigger it — the + script does this; without it the API errors `ErrorCustomerCodeRequired`. +- **Dates** are sent as Unix epochs; pass `date:"YYYY-MM-DD"` or omit for today. +- **`banque lire`** isn't granted yet → `GET /bankaccounts` returns empty. Add it + to the provisioner's rights set if account discovery from this skill is needed. +- **Avoirs (credit notes)** are a planned follow-up (a customer invoice with + `type=2` referencing the original). diff --git a/.claude/skills/dolibarr-sandbox-write/examples/customer-invoice.json b/.claude/skills/dolibarr-sandbox-write/examples/customer-invoice.json new file mode 100644 index 0000000..0145dc2 --- /dev/null +++ b/.claude/skills/dolibarr-sandbox-write/examples/customer-invoice.json @@ -0,0 +1,9 @@ +{ + "socid": 42, + "kind": "customer", + "validate": true, + "lines": [ + { "desc": "Conseil (jours)", "qty": 2, "price_ht": 500, "tva": 20, "type": "service" }, + { "desc": "Licence annuelle", "qty": 1, "price_ht": 100, "tva": 20, "type": "product" } + ] +} diff --git a/.claude/skills/dolibarr-sandbox-write/scripts/dol-write.sh b/.claude/skills/dolibarr-sandbox-write/scripts/dol-write.sh new file mode 100755 index 0000000..e2ebbc0 --- /dev/null +++ b/.claude/skills/dolibarr-sandbox-write/scripts/dol-write.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# Host-guarded WRITE wrapper for the Arcodange Dolibarr SANDBOX API. +# +# THE GUARANTEE (ADR-0003): this wrapper REFUSES to run against anything that is +# not the erp-sandbox host. It is the structural reason an AI agent driving this +# skill can never mutate production — prod is a different host, and the guard +# below rejects it before any request is sent. +# +# Usage: +# dol-write.sh [json-body|@file] +# dol-write.sh POST /thirdparties '{"name":"Acme","client":"1"}' +# dol-write.sh POST /invoices @invoice.json +# dol-write.sh PUT /thirdparties/42 '{"phone":"+33..."}' +# dol-write.sh GET /thirdparties?limit=5 # reads are allowed too +# +# Reads DOLIBARR_SANDBOX_URL + DOLIBARR_SANDBOX_API_KEY from the sibling .env +# (.claude/skills/dolibarr-sandbox-write/.env), mode 600, gitignored. +# Prints the response body to stdout; exits non-zero on HTTP >= 400. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENV_FILE="${SCRIPT_DIR}/../.env" + +if [[ ! -f "${ENV_FILE}" ]]; then + echo "dol-write.sh: missing ${ENV_FILE}" >&2 + echo " Create it with DOLIBARR_SANDBOX_URL + DOLIBARR_SANDBOX_API_KEY. See README.md." >&2 + exit 2 +fi +# shellcheck disable=SC1090 +set -a; source "${ENV_FILE}"; set +a + +: "${DOLIBARR_SANDBOX_URL:?dol-write.sh: DOLIBARR_SANDBOX_URL not set in .env}" +: "${DOLIBARR_SANDBOX_API_KEY:?dol-write.sh: DOLIBARR_SANDBOX_API_KEY not set in .env}" + +# --------------------------------------------------------------------------- +# HOST GUARD — the structural safety invariant. Only the sandbox host passes. +# Override the allowed pattern only via DOLIBARR_SANDBOX_HOST_RE in .env if the +# sandbox FQDN ever changes; never widen it to include a prod host. +# --------------------------------------------------------------------------- +ALLOWED_RE="${DOLIBARR_SANDBOX_HOST_RE:-^https://erp-sandbox\.arcodange\.lab(/|$)}" +if [[ ! "${DOLIBARR_SANDBOX_URL}" =~ ${ALLOWED_RE} ]]; then + echo "dol-write.sh: REFUSING to write — DOLIBARR_SANDBOX_URL='${DOLIBARR_SANDBOX_URL}'" >&2 + echo " is not the erp-sandbox host (allowed: ${ALLOWED_RE})." >&2 + echo " This skill only writes the sandbox. Promotion to prod is a separate, human-gated step." >&2 + exit 3 +fi + +if [[ $# -lt 2 ]]; then + echo "dol-write.sh: usage: dol-write.sh [json-body|@file]" >&2 + exit 2 +fi +METHOD="$1"; API_PATH="$2"; BODY="${3-}" +case "${METHOD}" in + GET|POST|PUT|DELETE|PATCH) ;; + *) echo "dol-write.sh: unsupported method '${METHOD}'" >&2; exit 2 ;; +esac + +CURL_ARGS=( + -sS -X "${METHOD}" + -H "DOLAPIKEY: ${DOLIBARR_SANDBOX_API_KEY}" + -H "Accept: application/json" + --max-time 30 +) +if [[ -n "${BODY}" ]]; then + # curl --data supports '@file' to read a JSON body from a file. + CURL_ARGS+=( -H "Content-Type: application/json" --data "${BODY}" ) +fi + +BODY_FILE="$(mktemp -t dolwrite.XXXXXX)" +trap 'rm -f "${BODY_FILE}"' EXIT + +HTTP_CODE=$(curl "${CURL_ARGS[@]}" \ + -o "${BODY_FILE}" -w "%{http_code}" \ + "${DOLIBARR_SANDBOX_URL}/api/index.php${API_PATH}") + +cat "${BODY_FILE}" +if [[ "${HTTP_CODE}" -ge 400 ]]; then + echo "" >&2 + echo "dol-write.sh: HTTP ${HTTP_CODE} on ${METHOD} ${API_PATH}" >&2 + exit 1 +fi diff --git a/.claude/skills/dolibarr-sandbox-write/scripts/invoice-create.sh b/.claude/skills/dolibarr-sandbox-write/scripts/invoice-create.sh new file mode 100755 index 0000000..13b0639 --- /dev/null +++ b/.claude/skills/dolibarr-sandbox-write/scripts/invoice-create.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Create a customer or supplier invoice (facture) with product/service lines in +# the SANDBOX, optionally validating it. +# +# Input: a JSON object on stdin (or a file path in $1): +# socid (required) thirdparty id +# kind "customer" | "supplier" (default "customer") +# date "YYYY-MM-DD" (default today) +# ref_supplier supplier's own invoice ref (supplier invoices) +# validate true|false (default false = leave draft) +# lines: [ { desc, qty, price_ht, tva, type: "product"|"service", product_id? } ] +# +# Emits {id, ref, ref_supplier, total_ht, total_ttc, statut} on stdout. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +W="${SCRIPT_DIR}/dol-write.sh" + +SRC="${1:-}" +if [[ -n "${SRC}" && "${SRC}" != "-" ]]; then INPUT="$(cat "${SRC}")"; else INPUT="$(cat)"; fi + +PYF="$(mktemp -t dolpy.XXXXXX)"; trap 'rm -f "${PYF}"' EXIT +cat > "${PYF}" <<'PY' +import json, sys, datetime +d = json.loads(sys.stdin.read()) +if not d.get("socid"): + sys.exit("invoice-create.sh: 'socid' is required") +supplier = d.get("kind", "customer").lower() in ("supplier", "fournisseur") +endpoint = "/supplierinvoices" if supplier else "/invoices" +ds = d.get("date") +epoch = int((datetime.datetime.strptime(ds, "%Y-%m-%d") if ds + else datetime.datetime.now()).timestamp()) +lines = [] +for ln in d.get("lines", []): + is_product = ln.get("type", "service").lower() in ("product", "produit") + L = { + "desc": ln.get("desc", ""), + "subprice": str(ln.get("price_ht", ln.get("subprice", 0))), + "qty": str(ln.get("qty", 1)), + "tva_tx": str(ln.get("tva", ln.get("tva_tx", 20))), + "product_type": "0" if is_product else "1", + } + if ln.get("product_id"): + L["fk_product"] = str(ln["product_id"]) + lines.append(L) +body = {"socid": d["socid"], "date": epoch, "type": 0, "lines": lines} +if supplier and d.get("ref_supplier"): + body["ref_supplier"] = d["ref_supplier"] +print(endpoint) +print(json.dumps(body)) +print("1" if d.get("validate") else "0") +PY + +MAPPED="$(printf '%s' "${INPUT}" | python3 "${PYF}")" +ENDPOINT="$(sed -n 1p <<<"${MAPPED}")" +BODY="$(sed -n 2p <<<"${MAPPED}")" +VALIDATE="$(sed -n 3p <<<"${MAPPED}")" + +ID="$("${W}" POST "${ENDPOINT}" "${BODY}")" +if [[ ! "${ID}" =~ ^[0-9]+$ ]]; then + echo "invoice-create.sh: create did not return an id: ${ID}" >&2 + exit 1 +fi +if [[ "${VALIDATE}" == "1" ]]; then + "${W}" POST "${ENDPOINT}/${ID}/validate" '{}' >/dev/null +fi +"${W}" GET "${ENDPOINT}/${ID}" | python3 -c "import json,sys +d=json.load(sys.stdin) +print(json.dumps({k:d.get(k) for k in ('id','ref','ref_supplier','total_ht','total_ttc','statut')}))" diff --git a/.claude/skills/dolibarr-sandbox-write/scripts/payment-record.sh b/.claude/skills/dolibarr-sandbox-write/scripts/payment-record.sh new file mode 100755 index 0000000..9ea1397 --- /dev/null +++ b/.claude/skills/dolibarr-sandbox-write/scripts/payment-record.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Record a payment (règlement) on a validated invoice in the SANDBOX. +# +# Input: a JSON object on stdin (or a file path in $1): +# invoice_id (required) the invoice to pay +# kind "customer" | "supplier" (default "customer") +# mode "VIR" | "CB" | "CHQ" | "LIQ" (default "VIR") +# account_id (required) the bank account id receiving/paying +# date "YYYY-MM-DD" (default today) +# amount (REQUIRED for supplier; customer pays the full remaining) +# num, comment (optional) +# +# The invoice must be VALIDATED first (invoice-create.sh ... "validate":true). +# Emits the new payment id on stdout. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +W="${SCRIPT_DIR}/dol-write.sh" + +SRC="${1:-}" +if [[ -n "${SRC}" && "${SRC}" != "-" ]]; then INPUT="$(cat "${SRC}")"; else INPUT="$(cat)"; fi + +PYF="$(mktemp -t dolpy.XXXXXX)"; trap 'rm -f "${PYF}"' EXIT +cat > "${PYF}" <<'PY' +import json, sys, datetime +d = json.loads(sys.stdin.read()) +if not d.get("invoice_id"): + sys.exit("payment-record.sh: 'invoice_id' is required") +if not d.get("account_id"): + sys.exit("payment-record.sh: 'account_id' is required") +# Stable Dolibarr c_paiement ids (sandbox seeded from prod / standard defaults). +MODE = {"VIR": 2, "CB": 6, "CHQ": 7, "LIQ": 4} +mode = MODE.get(str(d.get("mode", "VIR")).upper()) +if mode is None: + sys.exit("payment-record.sh: unknown mode (use VIR|CB|CHQ|LIQ)") +supplier = d.get("kind", "customer").lower() in ("supplier", "fournisseur") +ds = d.get("date") +epoch = int((datetime.datetime.strptime(ds, "%Y-%m-%d") if ds + else datetime.datetime.now()).timestamp()) +inv = d["invoice_id"] +if supplier: + if d.get("amount") is None: + sys.exit("payment-record.sh: supplier payments require an 'amount'") + endpoint = "/supplierinvoices/%s/payments" % inv + body = {"datepaye": epoch, "paymentid": mode, "accountid": d["account_id"], + "amount": str(d["amount"]), "num_payment": d.get("num", ""), + "comment": d.get("comment", "")} +else: + endpoint = "/invoices/%s/payments" % inv + body = {"datepaye": epoch, "paymentid": mode, "closepaidinvoices": "yes", + "accountid": d["account_id"], "num_payment": d.get("num", ""), + "comment": d.get("comment", "")} +print(endpoint) +print(json.dumps(body)) +PY + +MAPPED="$(printf '%s' "${INPUT}" | python3 "${PYF}")" +ENDPOINT="$(sed -n 1p <<<"${MAPPED}")" +BODY="$(sed -n 2p <<<"${MAPPED}")" +"${W}" POST "${ENDPOINT}" "${BODY}" diff --git a/.claude/skills/dolibarr-sandbox-write/scripts/thirdparty-create.sh b/.claude/skills/dolibarr-sandbox-write/scripts/thirdparty-create.sh new file mode 100755 index 0000000..a1f374a --- /dev/null +++ b/.claude/skills/dolibarr-sandbox-write/scripts/thirdparty-create.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Create a client and/or supplier thirdparty (fiche tiers) in the SANDBOX. +# +# Input: a JSON object on stdin (or a file path in $1). Fields: +# name (required) +# role "client" | "supplier" | "both" (default "client") +# country_id numeric, default 1 (France) +# client_code / supplier_code default "-1" = auto-generate via the code mask +# siret, tva_intra, address, zip, town, email, phone, idprof1 (optional) +# +# Emits the new thirdparty id on stdout. All writes go through dol-write.sh, +# which refuses any host that is not the sandbox. +# +# Examples: +# echo '{"name":"KissMetrics","role":"client","tva_intra":"US.."}' | thirdparty-create.sh +# thirdparty-create.sh fournisseur.json +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +W="${SCRIPT_DIR}/dol-write.sh" + +SRC="${1:-}" +if [[ -n "${SRC}" && "${SRC}" != "-" ]]; then INPUT="$(cat "${SRC}")"; else INPUT="$(cat)"; fi + +PYF="$(mktemp -t dolpy.XXXXXX)"; trap 'rm -f "${PYF}"' EXIT +cat > "${PYF}" <<'PY' +import json, sys +d = json.loads(sys.stdin.read()) +if not d.get("name"): + sys.exit("thirdparty-create.sh: 'name' is required") +role = d.get("role", "client").lower() +is_client = role in ("client", "both") +is_supp = role in ("supplier", "fournisseur", "both") +body = { + "name": d["name"], + "client": "1" if is_client else "0", + "fournisseur": "1" if is_supp else "0", + "country_id": str(d.get("country_id", 1)), + # "-1" => Dolibarr auto-assigns the next code from the mask + # (COMPANY_ELEPHANT_MASK_CUSTOMER / _SUPPLIER); "0" when that role is off. + "code_client": (d.get("client_code", "-1") if is_client else "0"), + "code_fournisseur": (d.get("supplier_code", "-1") if is_supp else "0"), +} +for k in ("siret", "tva_intra", "address", "zip", "town", "email", "phone", "idprof1"): + if d.get(k): + body[k] = d[k] +print(json.dumps(body)) +PY + +BODY="$(printf '%s' "${INPUT}" | python3 "${PYF}")" +"${W}" POST /thirdparties "${BODY}"