107 lines
4.0 KiB
TypeScript
107 lines
4.0 KiB
TypeScript
/*
|
|
Fait produire à Dolibarr le PDF d'une facture client, puis l'enregistre.
|
|
|
|
POURQUOI CE CHEMIN EXISTE. Valider une facture par l'API **ne génère aucun
|
|
PDF** : le fichier n'existe sur le disque que lorsque quelqu'un a demandé sa
|
|
production. `PUT /documents/builddoc` le ferait, mais ce droit n'est accordé à
|
|
aucun scope agent (voir test/scopes.ts) et répond 403. Le seul chemin ouvert
|
|
est donc l'interface — celui que l'opérateur emprunterait lui-même.
|
|
|
|
Deux pièges :
|
|
- le PDF n'est PAS servi par la page : on demande sa production par l'UI,
|
|
puis on le relit par l'API `/documents/download`, qui prouve du même coup
|
|
qu'il est bien déposé et lisible par un tiers ;
|
|
- `action=builddoc` exige le jeton CSRF de session. On ne le devine pas : on
|
|
le relit sur un lien de la fiche.
|
|
|
|
Usage :
|
|
deno run -A test/buildInvoicePdf.ts --ref FAC009-CL0001009 --out dossier/
|
|
*/
|
|
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 ref = pick("--ref");
|
|
const out = pick("--out", ".");
|
|
const model = pick("--model", "sponge");
|
|
const dryRun = argv.includes("--dry-run");
|
|
|
|
if (!ref) {
|
|
console.error("--ref est requis (ex. FAC009-CL0001009)");
|
|
Deno.exit(2);
|
|
}
|
|
|
|
const dolibarrAddress = assertSandbox();
|
|
console.log(`cible : ${dolibarrAddress}`);
|
|
console.log(`facture: ${ref}`);
|
|
console.log(`modèle : ${model}`);
|
|
if (dryRun) {
|
|
console.log("\n--dry-run : rien n'est produit.");
|
|
Deno.exit(0);
|
|
}
|
|
|
|
const browser = await chromium.launch({ headless: true });
|
|
const context = await browser.newContext({ locale: "fr-FR" });
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
await login.doAdminLogin({
|
|
page,
|
|
dolibarrAddress,
|
|
adminCredentials: {
|
|
username: Deno.env.get("DOLI_ADMIN_LOGIN") || "undefined",
|
|
password: Deno.env.get("DOLI_ADMIN_PASSWORD") || "undefined",
|
|
},
|
|
});
|
|
|
|
const card = `${dolibarrAddress}/compta/facture/card.php?ref=${encodeURIComponent(ref)}`;
|
|
await page.goto(card);
|
|
const body = await page.locator("body").innerText();
|
|
if (!body.includes(ref)) {
|
|
console.error(`facture introuvable : ${card}`);
|
|
Deno.exit(1);
|
|
}
|
|
|
|
// Identifiant réel de la facture, tel que l'URL le porte après résolution du ref.
|
|
const idMatch = (await page.locator('a[href*="facid="]').first().getAttribute("href")
|
|
.catch(() => null))?.match(/facid=(\d+)/);
|
|
const anyToken = (await page.locator('a[href*="token="]').first().getAttribute("href")
|
|
.catch(() => null))?.match(/token=([a-zA-Z0-9]+)/);
|
|
if (!anyToken) {
|
|
console.error("aucun jeton de session lisible sur la fiche — droits insuffisants ?");
|
|
Deno.exit(1);
|
|
}
|
|
const token = anyToken[1];
|
|
const id = idMatch?.[1];
|
|
|
|
const buildUrl = `${dolibarrAddress}/compta/facture/card.php` +
|
|
`?${id ? `facid=${id}` : `ref=${encodeURIComponent(ref)}`}` +
|
|
`&action=builddoc&token=${token}&model=${model}`;
|
|
await page.goto(buildUrl);
|
|
|
|
// On ne croit pas la page de retour : on relit le fichier par l'API.
|
|
const apiUrl = Deno.env.get("DOLIBARR_URL") || dolibarrAddress;
|
|
const apiKey = Deno.env.get("DOLIBARR_API_KEY") || "";
|
|
const res = await fetch(
|
|
`${apiUrl}/api/index.php/documents/download` +
|
|
`?modulepart=facture&original_file=${encodeURIComponent(`${ref}/${ref}.pdf`)}`,
|
|
{ headers: { DOLAPIKEY: apiKey, Accept: "application/json" } },
|
|
);
|
|
const payload = await res.json();
|
|
if (!res.ok || !payload.content) {
|
|
console.error(`le PDF n'est pas déposé : HTTP ${res.status} ${JSON.stringify(payload).slice(0, 200)}`);
|
|
Deno.exit(1);
|
|
}
|
|
|
|
const bytes = Uint8Array.from(atob(payload.content), (c) => c.charCodeAt(0));
|
|
const dest = out.endsWith(".pdf") ? out : `${out.replace(/\/$/, "")}/${ref}.pdf`;
|
|
await Deno.writeFile(dest, bytes);
|
|
console.log(`ok — ${dest} (${bytes.length} octets)`);
|
|
} finally {
|
|
await browser.close();
|
|
}
|