diff --git a/server/freeproAutoImport.ts b/server/freeproAutoImport.ts index 5e4eb1d..17e60e0 100644 --- a/server/freeproAutoImport.ts +++ b/server/freeproAutoImport.ts @@ -269,67 +269,76 @@ async function loginToFreePro( // ── Récupération de la liste des factures ────────────────────────────────── +/** Endpoint API REST interne du portail FreePro (Angular) qui retourne la liste des factures en JSON. */ +const INVOICES_API_URL = `${FREEPRO_BASE_URL}/api/api_red_october/v1/invoices`; + /** - * Récupère la liste des factures disponibles sur le portail FreePro + * Récupère la liste des factures disponibles sur le portail FreePro. + * + * Le portail FreePro est une SPA Angular. L'API interne utilisée par Angular + * pour charger les données est : GET /api/api_red_october/v1/invoices + * Elle retourne un tableau JSON avec les champs : ref, billing_date, total_ht, total_ttc, invoice_status + * + * Cette API nécessite les cookies de session obtenus après connexion via /account/security/do_login. */ async function fetchInvoiceList(cookies: string): Promise { - const endpoints = [ - `${FREEPRO_BASE_URL}/account/api/billing/invoices`, - `${FREEPRO_BASE_URL}/account/billing/api/invoices`, - `${FREEPRO_BASE_URL}/api/billing/invoices`, - ]; + try { + const resp = await fetchWithTimeout(INVOICES_API_URL, { + headers: { + "Cookie": cookies, + "Accept": "application/json", + "X-Requested-With": "XMLHttpRequest", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36", + "Referer": BILLING_URL, + "Accept-Language": "fr-FR,fr;q=0.9", + }, + redirect: "follow", + }, 15_000); - for (const endpoint of endpoints) { - try { - const resp = await fetchWithTimeout(endpoint, { - headers: { - "Cookie": cookies, - "Accept": "application/json", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36", - "Referer": BILLING_URL, - "X-Requested-With": "XMLHttpRequest", - }, - }, 10_000); + if (!resp.ok) return []; - if (resp.ok) { - const data = await resp.json(); - const invoices = parseInvoiceList(data); - if (invoices.length > 0) return invoices; - } - } catch { - continue; - } + const data = await resp.json(); + return parseInvoiceListFromJson(data); + } catch { + return []; } - - return []; } /** - * Parse la réponse JSON de l'API de facturation FreePro + * Parse la réponse JSON de l'API interne FreePro /api/api_red_october/v1/invoices. + * + * Structure d'un élément : + * { ref: "F202606003800", billing_date: "2026-06-01T00:00:00", total_ht: 1287.37, + * total_ttc: 1544.84, invoice_status: "waiting"|"paid", ... } */ -function parseInvoiceList(data: any): FreeproInvoice[] { +function parseInvoiceListFromJson(data: any): FreeproInvoice[] { + if (!Array.isArray(data)) return []; + const invoices: FreeproInvoice[] = []; - const items = Array.isArray(data) ? data : (data?.invoices ?? data?.data ?? []); - for (const item of items) { - const invoiceNumber = item.ref_piece ?? item.invoiceNumber ?? item.id ?? ""; - const date = item.date ?? item.invoiceDate ?? ""; - const amount = parseFloat(item.total ?? item.amount ?? item.ttc ?? "0"); + for (const item of data) { + const invoiceNumber: string = item.ref ?? ""; + const billingDate: string = item.billing_date ?? ""; + const amount: number = typeof item.total_ttc === "number" ? item.total_ttc : 0; - if (!invoiceNumber || !date) continue; + if (!invoiceNumber || !billingDate) continue; + // billing_date est au format ISO : "2026-06-01T00:00:00" + // On extrait YYYY et MM pour construire le label du mois let month = ""; - if (date.includes("-")) { - const parts = date.split("-"); - if (parts.length >= 2) month = `${parts[1].padStart(2, "0")}/${parts[0]}`; - } else if (date.includes("/")) { - const parts = date.split("/"); - if (parts.length >= 3) month = `${parts[1].padStart(2, "0")}/${parts[2]}`; + const dateMatch = billingDate.match(/^(\d{4})-(\d{2})/); + if (dateMatch) { + month = `${dateMatch[2]}/${dateMatch[1]}`; // "06/2026" } - if (month) invoices.push({ invoiceNumber, date, amount, month }); + if (month) { + invoices.push({ invoiceNumber, date: billingDate, amount, month }); + } } + // Trier par numéro de facture décroissant (plus récente en premier) + invoices.sort((a, b) => b.invoiceNumber.localeCompare(a.invoiceNumber)); + return invoices; } @@ -453,7 +462,7 @@ export async function runFreeproAutoImport(userId: number): Promise