diff --git a/.claude/skills/dolibarr-sandbox-write/RUNBOOK_charges_sociales.md b/.claude/skills/dolibarr-sandbox-write/RUNBOOK_charges_sociales.md index d28f588..becbd90 100644 --- a/.claude/skills/dolibarr-sandbox-write/RUNBOOK_charges_sociales.md +++ b/.claude/skills/dolibarr-sandbox-write/RUNBOOK_charges_sociales.md @@ -6,32 +6,58 @@ fois, elle doit en coûter deux minutes ensuite. ## Pourquoi ce n'est pas une facture fournisseur -L'URSSAF n'est pas un fournisseur. Sa cotisation va au compte **646** -(cotisations personnelles du dirigeant), pas au compte fournisseur — l'inscrire -en facture fournisseur pollue le grand livre auxiliaire, les balances âgées et -les états de dettes fournisseurs. +L'URSSAF n'est pas un fournisseur. L'inscrire en facture fournisseur pollue le +grand livre auxiliaire, les balances âgées et les états de dettes fournisseurs. +C'est une **charge sociale**, saisie comme telle. -**645 contre 646**, la distinction qui décide de tout : +## Quel compte — et pourquoi ce n'est pas 646 -| Compte | Pour qui | -| --- | --- | -| 645 | cotisations **patronales sur salaires** — suppose des salariés | -| **646** | cotisations **personnelles du dirigeant TNS** | +> [!IMPORTANT] +> **Arcodange est une SARL à l'IS. Le compte est 641, pas 646.** +> Le **646** (« cotisations sociales de l'exploitant ») est réservé à +> l'**entreprise individuelle et aux sociétés à l'IR**. Il ne s'applique pas à +> une société. Quand la société règle les cotisations personnelles de son gérant +> majoritaire, c'est un **complément de rémunération** : compte **641**, dans un +> sous-compte dédié (`641150` « Sécurité sociale des indépendants » par exemple), +> et c'est **déductible** du résultat de la société. -Arcodange n'a aucun salarié et Gabriel est gérant associé unique d'une SARLU, -donc **TNS** : tout va en 646. Le compte 645 doit rester vide. +| Compte | Pour qui | Arcodange | +| --- | --- | --- | +| **641** (sous-compte dédié) | rémunération + cotisations du **gérant majoritaire TNS** | ← **celui-ci** | +| 646 | cotisations de l'**exploitant individuel** / société à l'IR | sans objet | +| 645 | cotisations **sur salaires** | vide — aucun salarié | +| 631 / 633 | contributions **de l'employeur** (formation, apprentissage) | vide — **Gabriel n'est pas employeur** | -Dans Dolibarr, cela se pilote par le **type de charge**, jamais par une saisie -manuelle du compte : +Contrepartie **431** (Sécurité sociale), soldée par **512** au paiement. +Régularisation à la clôture : 641 contre **4286** (autres charges à payer). -- `Securite sociale (URSSAF / MSA)` → régime salarié → 645 -- **`Securite sociale des indépendants (URSSAF)`** → TNS → 646 ← **celui-ci** +L'appel URSSAF se comptabilise **globalement** dans ce sous-compte 641 : pas de +ventilation entre cotisations, CFP et CSG/CRDS. En particulier, la contribution +à la formation professionnelle d'un TNS **n'est pas** la participation employeur +du compte 6333 — Arcodange n'emploie personne ; c'est une contribution +personnelle, elle suit les cotisations. > [!WARNING] -> Le code comptable de chaque type vit dans **Configuration → Dictionnaires → -> Types de charges sociales**. Vérifier une fois que la ligne « indépendants » -> porte bien 646 : si elle porte autre chose, le bon type enverra quand même -> l'écriture au mauvais compte. Non vérifié à ce jour. +> **Le piège CSG.** L'appel URSSAF porte une mention « montant de CSG déductible +> fiscalement : N € ». Elle vise l'**impôt sur le revenu personnel du gérant** +> (art. 62 CGI), **pas l'IS de la société**. Pour la SARL, la CSG/CRDS est +> **intégralement déductible** : aucune réintégration extra-comptable. Ne pas +> transposer le raisonnement de l'entreprise individuelle, où la CSG non +> déductible se reclasse en compte de l'exploitant. + +### Ce que Dolibarr ne fait pas + +Le type de charge **ne pilote aucune écriture**. Vérifié le 2026-08-13 dans +**Configuration → Dictionnaires → Types de charges sociales ou fiscales** +(`/admin/dict.php?id=7`) : la colonne **« Code comptable » est vide pour tous +les types**, `TAXSSI` compris. Le module comptabilité n'est pas déployé — +`/accountancy/*` répond 404. + +Conséquence pratique : **le choix du compte ne se joue pas dans l'ERP**. Dolibarr +porte les faits (montant, échéance, justificatif) ; l'imputation vit dans le +grand livre de l'expert-comptable. Choisir le type +`Securite sociale des indépendants (URSSAF)` pour la lisibilité, sans croire +qu'il décide de quoi que ce soit. ## La commande @@ -67,6 +93,19 @@ a réellement eu lieu — jamais par anticipation. - **Garde d'hôte** : `guard.ts` refuse toute cible qui n'est pas la sandbox, sauf double opt-in production. +> [!CAUTION] +> **Une charge sociale est IMMUABLE sur ce déploiement. La date doit être juste +> du premier coup.** Toute soumission du formulaire d'édition échoue — +> y compris en ne touchant que le montant : +> `ERROR 42601: multiple assignments to same column "fk_user_modif"`. +> `ChargeSociales::update()` affecte deux fois la même colonne dans son `UPDATE` ; +> MySQL l'accepte, **PostgreSQL le rejette**. Vérifié le 2026-08-13 en sandbox sur +> la date et sur le montant. Le défaut est propre à cet objet — la mise à jour +> d'un tiers via REST fonctionne. `test/updateSocialCharge.ts` conserve le cas de +> reproduction et diagnostique l'erreur. +> +> Relire la date **avant** de soumettre : `--dry-run` l'affiche. + ## Les quatre pièges, tous rencontrés 1. **La date est un piège à double fond.** Le champ visible `ech` est décoratif : @@ -97,8 +136,32 @@ gate. Acceptable pour une opération à trois champs ; à ne pas généraliser. ## Après l'enregistrement - Rapprocher le prélèvement bancaire quand il apparaît (Qonto pour Arcodange). +- **Attacher le justificatif à la charge.** L'appel de cotisations est la pièce + qui la justifie ; sans lui la charge n'est qu'une affirmation. + ```bash + curl -s -X POST https://erp.arcodange.lab/api/index.php/documents/upload \ + -H "DOLAPIKEY: $(cat test/.ai_agent_prod_prod_write.key)" \ + -H 'Content-Type: application/json' -d @- <","filecontent":"","fileencoding":"base64", + "overwriteifexists":1} + JSON + ``` + **`subdir` doit être l'identifiant nu de la charge** (`2`), rien d'autre. + L'API accepte silencieusement n'importe quel chemin (`sociales/2`, `tax/2`…) + et y dépose un fichier que l'onglet Documents ne montrera jamais. Vérifier sur + `/compta/sociales/document.php?id=` : « Nombre de fichiers liés » doit + passer à 1. `modulepart=tax` avec `ref` renvoie 500, c'est normal. - Le calendrier `fleet/profile/calendar.yaml` porte les échéances URSSAF 2026 : - 493,00 (22/05) + 1 215,00 (05/08) + 1 333,00 (05/11) = **3 041,00 €**. + 493,00 (05/05) + 1 215,00 (05/08) + 1 333,00 (05/11) = **3 041,00 €**. +- **Échéance ≠ prélèvement.** L'appel donne des dates d'échéance au 5 du mois ; + le débit bancaire tombe plus tard (22/05 pour l'échéance du 05/05). Enregistrer + la charge à la **date d'échéance** — c'est elle qui fait foi et qui détermine + un éventuel retard. +- **Les montants sont provisoires.** Tant que l'activité est en début d'activité, + ils sont calculés sur une base forfaitaire et **régularisés** après déclaration + des revenus. L'assiette est la **rémunération du gérant**, jamais le chiffre + d'affaires — ne pas anticiper de régularisation à partir du CA encaissé. - L'échéancier officiel n'est disponible **que** dans l'espace urssaf.fr : les notifications par mail ne contiennent aucun montant, et le transfert Gmail → Zoho remplace même leur contenu par un texte générique. Récupérer le PDF à la diff --git a/fleet/profile/calendar.yaml b/fleet/profile/calendar.yaml index 068473b..115c007 100644 --- a/fleet/profile/calendar.yaml +++ b/fleet/profile/calendar.yaml @@ -168,8 +168,8 @@ entries: due: "2026-08-05" authority: "URSSAF Île-de-France" amount_eur: 1215.00 - source: "échéancier de cotisations 2026 communiqué par l'opérateur le 2026-08-13" - notes: "Réglée par l'opérateur le 13/08/2026 (SEPA), avec 8 jours de retard sur l'échéance. Non encore visible sur Qonto au 13/08 — un SEPA met 1 à 2 jours ouvrés. À rapprocher du compte 646 (cotisations personnelles du dirigeant TNS) — PAS 645, qui vise les cotisations patronales sur salaires et doit rester vide : Arcodange n'a aucun salarié." + source: "appel de cotisations 2026 URSSAF Île-de-France du 09/07/2026, n° TI 117 1583346778 4 (PDF attaché à la charge en production)" + notes: "Réglée par l'opérateur le 13/08/2026 (SEPA), avec 8 jours de retard sur l'échéance. Non encore visible sur Qonto au 13/08 — un SEPA met 1 à 2 jours ouvrés. Imputation : compte 641 (sous-compte dédié), PAS 646 qui vise l'exploitant individuel et les sociétés à l'IR — Arcodange est une SARL à l'IS, la prise en charge des cotisations du gérant majoritaire est un complément de rémunération déductible. 645 et 631/633 restent vides : aucun salarié, l'opérateur n'est pas employeur." - id: "urssaf-2026-t3" title: "URSSAF — 3e et dernière échéance 2026 (1 333 EUR)" @@ -178,15 +178,27 @@ entries: due: "2026-11-05" authority: "URSSAF Île-de-France" amount_eur: 1333.00 - source: "échéancier de cotisations 2026 ; prélèvement SEPA validé par l'opérateur le 2026-08-13" - notes: "Prélèvement automatique déjà autorisé. Provisionner sur Qonto : c'est le compte prélevé (cf. l'échéance du 22/05)." + source: "appel de cotisations 2026 URSSAF Île-de-France du 09/07/2026, n° TI 117 1583346778 4 (PDF attaché à la charge en production)" + notes: "Prélèvement automatique déjà autorisé. Provisionner sur Qonto : c'est le compte prélevé (cf. l'échéance du 05/05, débitée le 22/05). Inclut 120 EUR de contribution à la formation professionnelle — contribution personnelle du TNS, pas la participation employeur du compte 6333 : l'opérateur n'est pas employeur." - # Total 2026 : 493,00 (22/05, prélevé) + 1 215,00 (05/08) + 1 333,00 (05/11) = 3 041,00 EUR. + - id: "urssaf-2026-regularisation" + title: "URSSAF — régularisation des cotisations 2026 (montant inconnu)" + category: "social" + status: "pending-definition" + authority: "URSSAF Île-de-France" + source: "appel de cotisations 2026 du 09/07/2026 : « calculées de façon provisoire sur la base forfaitaire applicable aux assurés en début d'activité […] régularisé dès que vous aurez effectué la déclaration de vos revenus 2026 »" + notes: "Les 3 041 EUR appelés en 2026 sont PROVISOIRES, assis sur un forfait début d'activité de 8 856 EUR ; ils seront régularisés après la déclaration des revenus 2026 (mi-2027), à la hausse ou à la baisse. PIÈGE : l'assiette est la RÉMUNÉRATION DU GÉRANT, jamais le chiffre d'affaires encaissé — ne pas dimensionner la provision sur le CA. Aucune rémunération n'a été versée en 2026 (vérifié au grand livre bancaire au 13/08), ce qui oriente vers une régularisation à la baisse ; mais la prise en charge des cotisations par la société est elle-même un complément de rémunération, donc l'assiette n'est pas nulle. À arbitrer avec l'expert-comptable, avec la décision de verser ou non une rémunération avant le 31/12." + + # Total 2026 : 493,00 (05/05, débité le 22/05) + 1 215,00 (05/08) + 1 333,00 (05/11) + # = 3 041,00 EUR, dont 2 062 de cotisations (après 449 d'exonération ACRE), + # 120 de formation professionnelle et 859 de CSG/CRDS. + # Les dates ci-dessus sont les dates d'ÉCHÉANCE portées par l'appel officiel (le 5 du + # mois), pas les dates de débit bancaire, qui tombent plus tard. # Cette ligne remplace l'ancienne entrée « urssaf-echeancier / pending-definition » : # la cadence n'était pas trimestrielle mais un échéancier à trois appels irréguliers, # et l'hypothèse PRD « URSSAF dormant tant qu'il n'y a pas de salaire » était fausse — - # le gérant cotise. Le point erp#57 est clos sur la partie cadence ; reste à qualifier - # la nature exacte des cotisations avec l'expert-comptable si besoin. + # le gérant cotise. Le point erp#57 est clos sur la partie cadence ; la nature des + # cotisations est qualifiée par adc-009. # --- Contract-driven (KissMetrics) ---------------------------------------- diff --git a/fleet/profile/decisions/adc-009-cotisations-tns-compte-641.md b/fleet/profile/decisions/adc-009-cotisations-tns-compte-641.md new file mode 100644 index 0000000..d899628 --- /dev/null +++ b/fleet/profile/decisions/adc-009-cotisations-tns-compte-641.md @@ -0,0 +1,131 @@ +--- +id: adc-009 +title: "Cotisations TNS du gérant en compte 641, pas 646 — CSG/CRDS intégralement déductible à l'IS" +status: Proposed +decided: null +effective_from: 2026-01-01 +effective_until: null +supersedes: null +superseded_by: null +--- + +# adc-009 — Imputation des cotisations sociales personnelles du gérant + +## Context + +L'appel de cotisations URSSAF 2026 (09/07/2026, n° TI 117 1583346778 4) porte +**3 041 €** au titre des cotisations personnelles du gérant : 2 062 € de +cotisations obligatoires après 449 € d'exonération ACRE, 120 € de contribution à +la formation professionnelle, 859 € de CSG/CRDS. Il mentionne un « montant de CSG +déductible fiscalement » de 602 €. + +Les trois échéances ont été enregistrées en production comme charges sociales +(ids 1, 2, 3). Restait à qualifier leur imputation comptable — question laissée +explicitement ouverte par `RUNBOOK_charges_sociales.md`, qui affirmait le +compte **646**. + +Arcodange est une **SARL unipersonnelle à l'IS**. Gabriel Radureau en est le +gérant associé unique, donc **TNS**. La société **n'a aucun salarié** et +l'opérateur **n'est pas employeur**. + +## Decision + +Les cotisations personnelles du gérant réglées par la société s'imputent au +compte **641**, dans un sous-compte dédié (par exemple `641150` « Sécurité +sociale des indépendants »), en contrepartie du compte **431**, soldé par le +**512** au paiement. La régularisation constatée à la clôture passe par le +compte **4286** (autres charges à payer). + +L'appel se comptabilise **globalement** : aucune ventilation entre cotisations, +contribution formation professionnelle et CSG/CRDS. + +La **CSG/CRDS est intégralement déductible** du résultat de la société. Aucune +réintégration extra-comptable n'est pratiquée. + +Les comptes **645** (cotisations sur salaires) et **631 / 633** (contributions de +l'employeur) restent **vides**. + +## Base légale & doctrine + +Le compte **646** est défini comme « cotisations sociales de l'exploitant » : il +vise l'entreprise individuelle et les sociétés soumises à l'IR. Il ne s'applique +pas à une société à l'IS. + +Lorsque la société prend en charge les cotisations obligatoires de son gérant +majoritaire, cette prise en charge s'analyse comme un **complément de +rémunération** — d'où l'imputation en 641 aux côtés de la rémunération, et sa +déductibilité du résultat social. Corrélativement, la rémunération du gérant +majoritaire relève de l'**article 62 du CGI** pour son imposition personnelle. + +C'est ce dernier point qui explique le piège de la CSG : la mention « CSG +déductible fiscalement : 602 € » portée par l'appel URSSAF concerne l'**impôt sur +le revenu du gérant**, pas l'IS de la société. Le mécanisme de réintégration de +la CSG non déductible appartient au régime de l'entreprise individuelle, où la +quote-part non déductible se reclasse en compte de l'exploitant. Il n'a pas de +transposition à l'IS. + +La contribution à la formation professionnelle d'un TNS est une contribution +**personnelle**, recouvrée par l'URSSAF avec les cotisations. Elle n'est pas la +participation de l'employeur à la formation professionnelle continue du compte +**6333**, qui suppose des salaires — Arcodange n'en verse aucun. + +## Alternatives rejected + +- **Compte 646** — première proposition, portée par le runbook. Rejetée : réservée + à l'exploitant individuel et aux sociétés à l'IR. +- **Compte 658 ou 455, charge non déductible, réintégration de 2 439 € au titre + d'un acte anormal de gestion** — position soutenue par `mistral-large-2512` + lors du challenge contradictoire. Rejetée : la prise en charge des cotisations + obligatoires du gérant est un complément de rémunération déductible, pas une + libéralité. Le challenger étayait cette position sur des références + invérifiables et sur des libellés de comptes erronés (cf. QA). +- **Ventilation de la CSG/CRDS entre 635 et 637 selon sa déductibilité** — même + origine. Rejetée deux fois : la déductibilité ne détermine pas le compte, et la + CSG est ici intégralement déductible. +- **Compte 6333 pour la contribution formation** — rejetée : compte de + participation **employeur**, assis sur les salaires. Sans objet ici. + +## Consequences + +- **L'ERP ne tranche pas cette question et n'a pas à la trancher.** Le + dictionnaire *Types de charges sociales ou fiscales* (`/admin/dict.php?id=7`) a + sa colonne « Code comptable » **vide pour tous les types**, `TAXSSI` compris — + vérifié au niveau cellule le 13/08/2026. Le module comptabilité n'est pas + déployé (`/accountancy/*` répond 404). Dolibarr porte les faits — montant, + échéance, justificatif — et l'imputation vit dans le grand livre de + l'expert-comptable. Le type de charge n'est qu'un libellé. +- `RUNBOOK_charges_sociales.md` est corrigé : il affirmait 646 et prétendait que + le type de charge pilotait le compte. Les deux étaient faux. +- **L'assiette des cotisations est la rémunération du gérant, jamais le chiffre + d'affaires.** Toute anticipation de régularisation bâtie sur le CA encaissé est + sans fondement. Entrée `urssaf-2026-regularisation` au calendrier. +- Question laissée ouverte, qui conditionne le passage de cette décision en + *Accepted* : aucune rémunération n'ayant été versée en 2026, la prise en charge + des cotisations constitue-t-elle à elle seule l'assiette ? Et faut-il verser une + rémunération avant le 31/12 ? Arbitrage expert-comptable. + +## QA & validation + +- Imputation vérifiée contre **deux sources indépendantes et concordantes** + (compta-facile, compta-online), toutes deux explicites sur le fait que le 646 + ne s'applique pas aux sociétés. +- **Le challenge contradictoire a été lui-même vérifié, et pris en défaut.** + `mistral-large-2512` donnait le compte 6333 pour « versements libératoires + taxe d'apprentissage » (c'est le 6335) et le 6354 pour « impôts sur + rémunérations » (c'est « droits d'enregistrement et de timbre »), et citait des + décisions de justice invérifiables. Son diagnostic « 646 est faux » était juste, + sa correction ne l'était pas. **Un verdict de challenger ne se reporte pas sans + contrôle** — c'est la leçon opératoire de cette décision. +- Contrôle arithmétique de l'appel : 2 062 + 120 + 859 = 3 041 ✓ ; + échéancier 0 + 493 + 1 215 + 1 333 = 3 041 ✓. +- Justificatif attaché aux trois charges de production, vérifié sur + `/compta/sociales/document.php?id=1|2|3`. + +## References + +- Appel de cotisations 2026, URSSAF Île-de-France, 09/07/2026, n° TI 117 + 1583346778 4 — attaché aux charges 1, 2 et 3 en production. +- Échéancier et régularisation : `fleet/profile/calendar.yaml`, + entrées `urssaf-2026-t2`, `urssaf-2026-t3`, `urssaf-2026-regularisation`. +- Mode opératoire : `.claude/skills/dolibarr-sandbox-write/RUNBOOK_charges_sociales.md`. +- Exercice comptable : `fleet/profile/fiscal.yaml`. diff --git a/test/updateSocialCharge.ts b/test/updateSocialCharge.ts new file mode 100644 index 0000000..bdc98d1 --- /dev/null +++ b/test/updateSocialCharge.ts @@ -0,0 +1,146 @@ +/* + Correct the dates of an existing social charge through the Dolibarr UI. + + !! BLOQUÉ PAR UN BUG AMONT — CE SCRIPT NE PEUT PAS ABOUTIR AUJOURD'HUI. !! + + Sur ce déploiement (Dolibarr 22.0.4 + PostgreSQL), AUCUNE charge sociale n'est + modifiable : toute soumission du formulaire d'édition, même en ne touchant que + le montant, échoue sur + + ERROR 42601: multiple assignments to same column "fk_user_modif" + + ChargeSociales::update() construit un UPDATE qui affecte deux fois la même + colonne. MySQL l'accepte, PostgreSQL le rejette. Vérifié le 2026-08-13 en + sandbox sur la date ET sur le montant ; la mise à jour d'un tiers via REST + fonctionne, donc le défaut est propre à cet objet, pas systémique. + + CONSÉQUENCE OPÉRATOIRE : une charge sociale est de fait IMMUABLE. Sa date doit + être juste à la création — voir RUNBOOK_charges_sociales.md, « échéance ≠ + prélèvement ». Ce script est conservé comme cas de reproduction : il + diagnostique l'erreur au lieu de la subir, et redeviendra utile dès que le bug + amont sera corrigé. + + WHY THIS EXISTS: recordSocialCharge.ts only creates. When a charge was booked + on the wrong date the record must be corrected in place — deleting and + recreating is not an option, since no agent scope grants DELETE and a ledger + is append-only by doctrine. + + THE CORRECTION THIS WAS WRITTEN FOR: the URSSAF échéancier gives DUE dates on + the 5th of the month. The bank debit lands later (22/05 for the 05/05 due + date). The first échéance had been recorded at its debit date, which makes a + 17-day late payment look punctual and puts the charge in the wrong month. + + Same double-bottom date trap as the create form: the visible `ech` / `period` + inputs are decorative, the backend reads ONLY the hidden day/month/year + triplets fed by the jQuery datepicker. See RUNBOOK_charges_sociales.md. + + Usage: + deno run -A test/updateSocialCharge.ts --id 1 --due 2026-05-05 \ + --period 2026-05-05 [--dry-run] +*/ +import "load_dotenv"; +import { chromium } from "playwright"; +import login from "./scripts/login.ts"; +import { assertSandbox } from "./scripts/guard.ts"; + +const argv = Deno.args; +const pick = (f: string, d = "") => (argv.includes(f) ? argv[argv.indexOf(f) + 1] : d); + +const id = pick("--id"); +const due = pick("--due"); // yyyy-mm-dd +const period = pick("--period"); // yyyy-mm-dd +const dryRun = argv.includes("--dry-run"); + +if (!id || !due) { + console.error("--id and --due (yyyy-mm-dd) are required"); + Deno.exit(2); +} + +const dolibarrAddress = assertSandbox(); +const fr = (iso: string) => { + const [y, m, d] = iso.split("-"); + return `${d}/${m}/${y}`; +}; + +console.log(`target : ${dolibarrAddress}`); +console.log(`charge ${id} : échéance → ${fr(due)}${period ? `, période → ${fr(period)}` : ""}`); +if (dryRun) { + console.log("\n--dry-run : rien n'est soumis."); + Deno.exit(0); +} + +const browser = await chromium.launch({ headless: true }); +const context = await browser.newContext({ locale: "fr-FR" }); +const page = await context.newPage(); + +async function setDolibarrDate(name: string, iso: string): Promise { + const [y, m, d] = iso.split("-"); + await page.fill(`input[name="${name}"]`, fr(iso)).catch(() => {}); + await page.evaluate( + ({ n, dd, mm, yy }: { n: string; dd: string; mm: string; yy: string }) => { + const doc = (globalThis as unknown as { + document: { querySelector(s: string): { value: string } | null }; + }).document; + const set = (suffix: string, v: string) => { + const el = doc.querySelector(`input[name="${n}${suffix}"]`); + if (el) el.value = v; + }; + set("day", String(Number(dd))); + set("month", String(Number(mm))); + set("year", yy); + }, + { n: name, dd: d, mm: m, yy: y }, + ); +} + +/** Read the dates back off the card — the only trustworthy confirmation. */ +async function readDates(): Promise { + await page.goto(`${dolibarrAddress}/compta/sociales/card.php?id=${id}`); + return (await page.locator("div.fichecenter, table").first().innerText()) + .replace(/\s+/g, " ").trim().slice(0, 300); +} + +try { + await login.doAdminLogin({ + page, + dolibarrAddress, + adminCredentials: { + username: Deno.env.get("DOLI_ADMIN_LOGIN") || "undefined", + password: Deno.env.get("DOLI_ADMIN_PASSWORD") || "undefined", + }, + }); + + console.log(`avant : ${await readDates()}`); + + await page.goto(`${dolibarrAddress}/compta/sociales/card.php?id=${id}&action=edit`); + await setDolibarrDate("ech", due); + if (period) await setDolibarrDate("period", period); + await page.locator('input[name="save"], input[type="submit"][value="Enregistrer"]').first().click(); + await page.waitForLoadState("networkidle"); + + // Diagnose the known upstream failure rather than reporting a bare mismatch: + // "la date n'a pas changé" sends the reader hunting through the date fields, + // which are not the problem. + const submitted = (await page.locator("body").innerText()).replace(/\s+/g, " "); + const dup = submitted.match(/multiple assignments to same column '?"?(\w+)/); + if (dup) { + console.error( + `\nBLOQUÉ PAR LE BUG AMONT — UPDATE invalide sur la colonne « ${dup[1]} ».\n` + + `ChargeSociales::update() l'affecte deux fois ; PostgreSQL rejette (42601).\n` + + `Une charge sociale est immuable sur ce déploiement : la recréer avec la\n` + + `bonne date, ou corriger en base, sont les seules voies.`, + ); + Deno.exit(1); + } + + const after = await readDates(); + console.log(`après : ${after}`); + if (!after.includes(fr(due))) { + console.error(`\nÉCHEC — la carte ne porte pas ${fr(due)}.`); + Deno.exit(1); + } + console.log(`\ncorrigée et vérifiée : échéance ${fr(due)}`); +} finally { + await context.close(); + await browser.close(); +}