/* Sandbox legal-mentions setup — rehearsal for two settings the REST API cannot reach (the write agent is deliberately non-admin: 403 on /setup/conf). 1. Capital social = 1 000 € — a mandatory invoice mention (C. com. R.123-238) missing from every Arcodange invoice to date. 2. Multi-currency module — so the KM cycle can be invoiced in USD, which is the actual contractual obligation ($2,500 / $3,000), instead of a EUR counter-value that drifts with the rate (decision of 2026-06-30). SAFETY: this script drives a browser as an ADMIN user, so it calls assertSandbox() first — production is never touched by a script. Note that test/.env ships DOLIBARR_ADDRESS pointing at PRODUCTION and test/main.ts defaults to it too, which is exactly why the guard exists. Run: DOLIBARR_ADDRESS=https://erp-sandbox.arcodange.lab \ deno run -A test/sandboxLegalSetup.ts [--capital 1000] [--no-multicurrency] */ import "load_dotenv"; import { chromium } from "playwright"; import login from "./scripts/login.ts"; import forms from "./scripts/forms.ts"; import { assertSandbox } from "./scripts/guard.ts"; const args = new Set(Deno.args); const capitalArg = Deno.args.includes("--capital") ? Deno.args[Deno.args.indexOf("--capital") + 1] : "1000"; const doMulticurrency = !args.has("--no-multicurrency"); const shotDir = Deno.env.get("SHOT_DIR") ?? "/tmp"; // The guard reads the ambient env — an unqualified run dies here, by design. const dolibarrAddress = assertSandbox(); console.log(`target: ${dolibarrAddress} (guard passed)`); const adminCredentials = { username: Deno.env.get("DOLI_ADMIN_LOGIN") || "undefined", password: Deno.env.get("DOLI_ADMIN_PASSWORD") || "undefined", }; const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ locale: "fr-FR" }); const page = await context.newPage(); const globalCtx = { page, dolibarrAddress, adminCredentials }; try { await login.doAdminLogin(globalCtx); console.log(`logged in as: ${await login.whoAmI({ page })}`); // --- 1. Capital social ----------------------------------------------------- // company.php renders one pre-filled form per block; filling a single field // and submitting its block preserves every other value already in the DOM. await page.goto(`${dolibarrAddress}/admin/company.php`); const before = await page.inputValue('input[name="capital"]').catch(() => ""); console.log(`capital before: '${before}'`); await forms.fillForm( { page, imgFolderPath: "" }, { capital: capitalArg }, new Map([["capital", "capital"]]) as Map<"capital", string>, 2, // the identity block's submit button ); await page.waitForLoadState("networkidle"); await page.goto(`${dolibarrAddress}/admin/company.php`); const after = await page.inputValue('input[name="capital"]').catch(() => ""); console.log(`capital after : '${after}'`); await page.screenshot({ path: `${shotDir}/capital.png`, fullPage: false }); // --- 2. Multi-currency module --------------------------------------------- if (doMulticurrency) { await page.goto(`${dolibarrAddress}/admin/modules.php?mode=commonkanban`); // The fr_FR card label is not pinned in this repo yet — match tolerantly on // "devise" and report what was found rather than guessing a title. const cards = page.locator(".info-box", { has: page.locator(".info-box-title", { hasText: /devise/i }), }); const n = await cards.count(); console.log(`module cards matching /devise/i: ${n}`); for (let i = 0; i < n; i++) { const title = (await cards.nth(i).locator(".info-box-title").textContent()) ?.trim(); console.log(` [${i}] ${title}`); } if (n === 0) { console.warn("multicurrency: no matching card — skipped (report only)"); } else { await forms.toggleOnOff(cards.first(), true); await page.waitForLoadState("networkidle"); await page.screenshot({ path: `${shotDir}/multicurrency.png`, fullPage: false, }); console.log("multicurrency: toggled on"); } } } finally { await context.close(); await browser.close(); }