WIP: fix(profile): cotisations TNS en 641 et non 646 — échéancier URSSAF 2026 réel #86

Draft
arcodange wants to merge 1 commits from arcodange/urssaf-calendar into main
4 changed files with 379 additions and 27 deletions
@@ -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 @- <<JSON
{"filename":"URSSAF-appel-cotisations-2026.pdf","modulepart":"tax",
"subdir":"<ID DE LA CHARGE>","filecontent":"<BASE64>","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=<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
+19 -7
View File
@@ -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) ----------------------------------------
@@ -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`.
+146
View File
@@ -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<void> {
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<string> {
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();
}