/* eslint-disable jsx-a11y/label-has-associated-control, jsx-a11y/no-autofocus */
"use client";

import { FormEvent, useEffect, useMemo, useRef, useState } from "react";
import { flushSync } from "react-dom";
import * as XLSX from "xlsx";

type Flow = {
  id: number;
  kind: "client" | "fournisseur";
  party: string;
  beneficiaryName?: string | null;
  operationLabel?: string | null;
  partyCode?: string | null;
  reference: string;
  amount: number;
  dueDate: string;
  invoiceDate?: string | null;
  plannedDate: string;
  status: string;
  manualOverride?: boolean;
  sourceFile?: string | null;
  excluded?: boolean;
  isForecast?: boolean;
  note?: string | null;
  companyId: string;
  reconciled?: boolean;
  reconciledAt?: string | null;
  settledAt?: string | null;
  parentFlowId?: number | null;
  installmentNumber?: number | null;
  installmentCount?: number | null;
  blockedSupplier?: boolean;
};

type Company = { id:string; name:string; shortName:string };
type Bank = { id:number; name:string; accountLabel:string; balance:number; minimumBalance:number; companyId:string; accountHolder?:string|null; rib?:string|null; iban?:string|null; swift?:string|null; branchAddress?:string|null; isPrimary?:boolean };
type BankTransaction = { id:number; bankAccountId:number; cashFlowId?:number|null; companyId:string; kind:"client"|"fournisseur"|"ajustement"; direction:"credit"|"debit"; party:string; reference:string; amount:number; balanceBefore:number; balanceAfter:number; transactionDate:string; paymentMethod?:string|null; paymentReference?:string|null; paymentPurpose?:string|null; createdAt:string; cancelledAt?:string|null; cancellationReason?:string|null };
type ForecastTemplate = { id:number; companyId:string; label:string; kind:"client"|"fournisseur"; defaultAmount:number; note?:string|null };
type SupplierBank = { id:number; companyId:string; supplierName:string; beneficiaryName:string; bankName:string; rib:string; iban?:string|null; swift?:string|null };
type TransferOrder = { id:number; internalNumber?:string|null; companyId:string; cashFlowId?:number|null; companyBankId:number; supplierBankId:number; amount:number; purpose:string; comment?:string|null; orderDate:string; createdAt:string; status:string };
type InternalTransfer = { id:number; reference:string; fromCompanyId:string; toCompanyId:string; outgoingFlowId:number; incomingFlowId:number; amount:number; transferDate:string; status:"pending"|"issued"|"completed"|"cancelled"; requestedBy:string; createdAt:string };
type AccountTransfer = { id:number; reference:string; companyId:string; sourceBankId:number; destinationBankId:number; amount:number; purpose:string; debitDate:string; receptionDate:string; status:"in_transit"|"received"|"cancelled"; debitTransactionId:number; creditTransactionId?:number|null; createdAt:string; receivedAt?:string|null };
type BlockedSupplier = { id:number; companyId:string; supplierName:string; supplierKey:string; createdAt:string };
type SupplierPaymentPolicy = { id:number; companyId:string; supplierName:string; supplierKey:string; priorityRank:number; splittable:boolean; maxInstallmentMonths:number; paymentDeadline?:string|null; active:boolean; createdAt:string; updatedAt:string };
type ReserveAllocation = { id:number; companyId:string; bankAccountId:number; category:"reserve"|"epargne"; label:string; amount:number; transferDate:string; includedInTreasury:boolean; note?:string|null; active:boolean; createdAt:string };
type PaymentApproval = { id:number; cashFlowId:number; companyId:string; requestedBy:string; requestedAt:string; status:"pending"|"approved"|"rejected"; decidedBy?:string|null; decidedAt?:string|null; decisionNote?:string|null; plannedDateBefore?:string|null; plannedDateAfter?:string|null; dateChanged?:boolean; beneficiaryNameBefore?:string|null; beneficiaryNameAfter?:string|null; beneficiaryChanged?:boolean };
type ReceiptReserve = { flow:Flow; bankId:number; transactionDate:string };
type PrintOrder = { internalNumber:string; company:string; companyBank:Bank; supplierBank:SupplierBank; amount:number; purpose:string; operationLabel:string; comment?:string; orderDate:string; references:{reference:string;operationLabel:string;amount:number}[] };
type InternalTransferPrintOrder = { reference:string; fromCompany:string; toCompany:string; sourceBank:Bank; destinationBank:Bank; amount:number; transferDate:string };
type AccountFundingPrintOrder = { reference:string; company:string; bank:Bank; sourceCompany:string; sourceBank:Bank; amount:number; transactionDate:string; description:string; balanceBefore:number; balanceAfter:number };
type SupplierPaymentAdvice = { documentKind?:"advice"|"order"; adviceNumber:string; operationLabel?:string|null; payerCompany:string; payerBank:string; payerAccount:string; supplierName:string; beneficiaryName:string; beneficiaryBank:string; beneficiaryAccount:string; paymentDate:string; paymentMethod:string; paymentReference?:string|null; executionReference:string; orderReference?:string|null; amount:number; invoices:Array<{reference:string;invoiceDate?:string|null;amount:number}>; generatedAt:string };
type ImportItem = Omit<Flow,"id"|"manualOverride"|"companyId"> & {
  sourceKey:string; invoiceDate:string|null; legalDueDate:string|null; originalAmount:number;
  remainingAmount:number; sourceFile:string; sourceRow:number;
};
type ImportBatch = { companyId:string; kind:"client"|"fournisseur"; fileName:string; sourceRows:number; items:ImportItem[] };
type ImportLog = { id:number; companyId:string; kind:string; fileName:string; openRows:number; totalRemaining:number; importedAt:string; importMode?:"replace"|"append" };
type ImportPreview = { received:number; imported:number; skipped:number; details:Array<{companyId:string;kind:"client"|"fournisseur";received:number;imported:number;skipped:number}> };
type SmartMonthSetting = { target:number; reservePercent:number; disbursementPercent:number };
type SmartSupplierAllocation = { flowId:number; companyId:string; supplier:string; reference:string; dueDate:string; invoiceAmount:number; proposedAmount:number; remainingAfter:number; paymentDate:string; monthKey:string; monthLabel:string; overdueDays:number; priority:"Critique"|"Haute"|"Normale"; score:number; reason:string; isFraction:boolean; policyRank?:number; policyDeadline?:string|null };
type SmartSupplierReportRow = { flow:Flow; supplier:string; invoiceAmount:number; planned:number; remaining:number; overdueDays:number; priority:"Critique"|"Haute"|"Normale"; score:number; policy?:SupplierPaymentPolicy; items:SmartSupplierAllocation[]; status:string; fundingObjective:number };
type ScheduleSortField = "reference"|"invoiceDate"|"dueDate"|"plannedDate"|"amount";
type ScheduleFilterPreset = { id:string; name:string; company:string; search:string; dateFrom:string; dateTo:string; amountMin:string; amountMax:string; type:"tous"|"client"|"fournisseur"; status:string; payment:string; included:string; sort:"oldest"|"newest"; sortField:ScheduleSortField };

const defaultSmartMonthSettings = () => {
  const today=new Date();
  return Object.fromEntries(Array.from({length:6},(_,index)=>{const date=new Date(today.getFullYear(),today.getMonth()+index,1);const key=`${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,"0")}`;return [key,{target:0,reservePercent:15,disbursementPercent:65}];})) as Record<string,SmartMonthSetting>;
};

const fallbackCompanies: Company[] = [
  { id:"moventra", name:"Société principale", shortName:"SOCIÉTÉ" },
];

const fallbackBanks: Bank[] = [];

const fallbackFlows: Flow[] = [];

const money = (value:number) => new Intl.NumberFormat("fr-MA", { maximumFractionDigits:0 }).format(value) + " MAD";
const moneyExact = (value:number) => new Intl.NumberFormat("fr-MA", { minimumFractionDigits:2, maximumFractionDigits:2 }).format(value) + " MAD";
const shortMoney = (value:number) => new Intl.NumberFormat("fr-MA", { notation:"compact", maximumFractionDigits:1 }).format(value) + " MAD";
const moneyValue = (value:number) => Math.round((value+Number.EPSILON)*100)/100;
const paymentMethodLabel = (value?:string|null) => ({mobile:"Application mobile bancaire",bank:"Virement bancaire",cheque:"Chèque",traite:"Traite",especes:"Espèces",carte:"Carte professionnelle",other:"Autre mode de règlement"}[String(value||"").toLowerCase()]||String(value||"Règlement bancaire"));
const formatFullRib = (value?:string|null) => { const digits=String(value||"").replace(/\D/g,""); if(!digits)return "Non renseigné"; return digits.length===24?`${digits.slice(0,3)} ${digits.slice(3,6)} ${digits.slice(6,22)} ${digits.slice(22)}`:digits; };
const frenchUnderHundred = (value:number):string => {
  const units=["zéro","un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix","onze","douze","treize","quatorze","quinze","seize"];
  if(value<=16)return units[value];
  if(value<20)return `dix-${units[value-10]}`;
  if(value<70){const tens=["","","vingt","trente","quarante","cinquante","soixante"][Math.floor(value/10)];const unit=value%10;return unit===0?tens:unit===1?`${tens} et un`:`${tens}-${units[unit]}`;}
  if(value<80)return value===71?"soixante et onze":`soixante-${frenchUnderHundred(value-60)}`;
  if(value===80)return "quatre-vingts";
  return `quatre-vingt-${frenchUnderHundred(value-80)}`;
};
const integerInFrench = (value:number):string => {
  const integer=Math.floor(Math.abs(value));
  if(integer<100)return frenchUnderHundred(integer);
  if(integer<1000){const hundreds=Math.floor(integer/100);const rest=integer%100;const prefix=hundreds===1?"cent":`${frenchUnderHundred(hundreds)} cent${rest===0?"s":""}`;return rest?`${prefix} ${frenchUnderHundred(rest)}`:prefix;}
  if(integer<1_000_000){const thousands=Math.floor(integer/1000);const rest=integer%1000;const thousandsWords=integerInFrench(thousands).replace(/vingts$/,"vingt").replace(/cents$/,"cent");const prefix=thousands===1?"mille":`${thousandsWords} mille`;return rest?`${prefix} ${integerInFrench(rest)}`:prefix;}
  if(integer<1_000_000_000){const millions=Math.floor(integer/1_000_000);const rest=integer%1_000_000;const prefix=`${integerInFrench(millions)} million${millions>1?"s":""}`;return rest?`${prefix} ${integerInFrench(rest)}`:prefix;}
  const billions=Math.floor(integer/1_000_000_000);const rest=integer%1_000_000_000;const prefix=`${integerInFrench(billions)} milliard${billions>1?"s":""}`;return rest?`${prefix} ${integerInFrench(rest)}`:prefix;
};
const amountInFrench = (value:number):string => {
  const totalCentimes=Math.round(Math.abs(value)*100);const dirhams=Math.floor(totalCentimes/100);const centimes=totalCentimes%100;
  const words=`${integerInFrench(dirhams)} dirham${dirhams===1?"":"s"}${centimes?` et ${integerInFrench(centimes)} centime${centimes===1?"":"s"}`:""}`;
  return words.charAt(0).toUpperCase()+words.slice(1);
};
function TransferOrderDocument({order}:{order:PrintOrder}) {
  return <article className="print-order">
    <header><div><strong>{order.company}</strong><span>LETTRE DE VIREMENT</span><small>N° interne : {order.internalNumber}</small></div><div className="print-order-meta"><span>Réception prévue chez {order.supplierBank.beneficiaryName}</span><time>{new Date(order.orderDate+"T00:00:00").toLocaleDateString("fr-FR")}</time><b>{order.companyBank.name}</b></div></header>
    <section className="print-addressee"><b>À l’attention de Monsieur le Directeur</b><span>{order.companyBank.name}</span>{order.companyBank.branchAddress&&<span>Agence : {order.companyBank.branchAddress}</span>}</section>
    <p><b>Objet : {order.purpose}</b></p><section className="print-order-operation"><span>Libellé de l’opération</span><b>{order.operationLabel}</b></section><p>Monsieur le Directeur,</p>
    <p>Nous vous prions de bien vouloir effectuer, par le débit de notre compte indiqué ci-dessous, le virement suivant au profit du bénéficiaire désigné :</p>
    <dl><div><dt>Titulaire du compte</dt><dd>{order.companyBank.accountHolder||order.company}</dd></div><div><dt>Notre banque</dt><dd>{order.companyBank.name} · {order.companyBank.accountLabel}</dd></div><div><dt>Notre RIB</dt><dd><b>{order.companyBank.rib||"RIB non renseigné"}</b>{order.companyBank.iban&&<small>IBAN : {order.companyBank.iban}</small>}</dd></div><div><dt>Bénéficiaire</dt><dd>{order.supplierBank.beneficiaryName}</dd></div><div><dt>Banque bénéficiaire</dt><dd>{order.supplierBank.bankName}</dd></div><div><dt>RIB / IBAN bénéficiaire</dt><dd>{order.supplierBank.iban||order.supplierBank.rib}</dd></div><div><dt>Montant en chiffres</dt><dd><b className="outgoing-value">{moneyExact(order.amount)}</b></dd></div><div><dt>Montant en lettres</dt><dd><b className="amount-in-words">{amountInFrench(order.amount)}</b></dd></div></dl>
    <table className="print-order-lines"><colgroup><col style={{width:"27%"}}/><col style={{width:"53%"}}/><col style={{width:"20%"}}/></colgroup><thead><tr><th>Référence bon de commande</th><th>Libellé de l’opération</th><th>Montant</th></tr></thead><tbody>{order.references.map((line,index)=><tr key={`${line.reference}-${index}`}><td>{line.reference}</td><td>{line.operationLabel}</td><td>{moneyExact(line.amount)}</td></tr>)}</tbody></table>
    {order.comment&&<section className="print-order-comment"><b>Commentaire / Instructions particulières</b><p>{order.comment}</p></section>}
    <p>Nous vous remercions de bien vouloir donner suite à la présente instruction et vous prions d’agréer, Monsieur le Directeur, l’expression de nos salutations distinguées.</p>
    <footer><span>Signature autorisée</span><span>Cachet de la société</span></footer>
  </article>;
}
function SupplierPaymentAdviceDocument({advice}:{advice:SupplierPaymentAdvice}) {
  const isOrder=advice.documentKind==="order";
  return <article className="print-remittance-advice">
    <header><div><strong>{advice.payerCompany}</strong><span>{isOrder?"ORDRE DE RÈGLEMENT":"AVIS / JUSTIFICATIF DE RÈGLEMENT"}</span><small>N° {advice.adviceNumber}</small></div><div><b>{isOrder?"Réimpression · règlement exécuté":"Règlement exécuté"}</b><time>{new Date(`${advice.paymentDate}T00:00:00`).toLocaleDateString("fr-FR")}</time></div></header>
    <p className="remittance-intro">{isOrder?"Le présent document reprend les instructions et références du règlement exécuté ci-dessous. Il est édité pour contrôle et archivage.":"Nous vous informons que le règlement ci-dessous a été exécuté. Merci de rapprocher les factures mentionnées dans ce justificatif."}</p>
    <section className="remittance-parties"><div><em>ÉMETTEUR</em><b>{advice.payerCompany}</b><span><strong>Banque :</strong> {advice.payerBank}</span><small><strong>Compte / RIB :</strong> {advice.payerAccount}</small></div><i>→</i><div><em>BÉNÉFICIAIRE</em><b>{advice.beneficiaryName}</b><span><strong>Banque :</strong> {advice.beneficiaryBank}</span><small><strong>Compte / RIB :</strong> {advice.beneficiaryAccount}</small></div></section>
    <dl><div><dt>Fournisseur concerné</dt><dd>{advice.supplierName}</dd></div>{advice.operationLabel&&<div><dt>Libellé de l’opération</dt><dd><b>{advice.operationLabel}</b></dd></div>}<div><dt>Mode de règlement</dt><dd>{advice.paymentMethod}</dd></div>{advice.paymentReference&&<div><dt>N° / référence du règlement</dt><dd><b>{advice.paymentReference}</b></dd></div>}<div><dt>Référence d’exécution</dt><dd>{advice.executionReference}</dd></div>{advice.orderReference&&<div><dt>Ordre de virement</dt><dd>{advice.orderReference}</dd></div>}<div><dt>Date du règlement</dt><dd>{new Date(`${advice.paymentDate}T00:00:00`).toLocaleDateString("fr-FR")}</dd></div><div><dt>Montant total réglé</dt><dd><b>{moneyExact(advice.amount)}</b></dd></div></dl>
    <h2>Détail des pièces réglées</h2><table className="remittance-invoices"><colgroup><col style={{width:"46%"}}/><col style={{width:"24%"}}/><col style={{width:"30%"}}/></colgroup><thead><tr><th>Référence bon de commande</th><th>Date de facture</th><th>Montant réglé</th></tr></thead><tbody>{advice.invoices.map((invoice,index)=><tr key={`${invoice.reference}-${index}`}><td>{invoice.reference}</td><td>{invoice.invoiceDate?new Date(`${invoice.invoiceDate}T00:00:00`).toLocaleDateString("fr-FR"):"—"}</td><td>{moneyExact(invoice.amount)}</td></tr>)}</tbody><tfoot><tr><th colSpan={2}>Total du règlement</th><th>{moneyExact(advice.amount)}</th></tr></tfoot></table>
    <section className="remittance-note"><b>Information importante</b><p>{isOrder?"Réimpression d’un ordre déjà exécuté : ce document est destiné au contrôle et à l’archivage et ne doit pas provoquer un nouveau paiement.":"Ce document atteste le règlement enregistré dans Moventra ERP et sert au rapprochement comptable. Il ne constitue pas un nouvel ordre de paiement."}</p></section>
    <section className="remittance-signatures"><div><span>Établi par</span><b>Service financier</b><small>Date et signature</small></div><div><span>Visa de contrôle</span><b>Responsable habilité</b><small>Cachet et signature</small></div></section>
    <footer><span>Document généré par Moventra ERP</span><span>{advice.generatedAt}</span></footer>
  </article>;
}
function InternalTransferOrderDocument({order}:{order:InternalTransferPrintOrder}) {
  return <article className="print-order">
    <header><div><strong>{order.fromCompany}</strong><span>ORDRE DE VIREMENT INTERNE</span><small>N° interne : {order.reference}</small></div><div className="print-order-meta"><time>{new Date(`${order.transferDate}T00:00:00`).toLocaleDateString("fr-FR")}</time><b>{order.sourceBank.name}</b></div></header>
    <section className="print-addressee"><b>À l’attention de Monsieur le Directeur</b><span>{order.sourceBank.name}</span>{order.sourceBank.branchAddress&&<span>Agence : {order.sourceBank.branchAddress}</span>}</section>
    <p>Nous vous prions d’effectuer le transfert compte-à-compte suivant :</p>
    <dl><div><dt>Société émettrice</dt><dd>{order.fromCompany}</dd></div><div><dt>Compte à débiter</dt><dd>{order.sourceBank.name} · {order.sourceBank.accountLabel}<small>RIB : {order.sourceBank.rib||"Non renseigné"}</small></dd></div><div><dt>Société bénéficiaire</dt><dd>{order.toCompany}</dd></div><div><dt>Compte à créditer</dt><dd>{order.destinationBank.name} · {order.destinationBank.accountLabel}<small>RIB : {order.destinationBank.rib||"Non renseigné"}</small></dd></div><div><dt>Montant en chiffres</dt><dd><b className="outgoing-value">{moneyExact(order.amount)}</b></dd></div><div><dt>Montant en lettres</dt><dd><b className="amount-in-words">{amountInFrench(order.amount)}</b></dd></div><div><dt>Motif</dt><dd>Transfert interne de trésorerie · {order.reference}</dd></div></dl>
    <p>La présente réimpression reprend les références du transfert initial et ne constitue pas une nouvelle instruction de débit.</p><footer><span>Signature autorisée</span><span>Cachet de la société</span></footer>
  </article>;
}
function AccountFundingDocument({order}:{order:AccountFundingPrintOrder}) {
  return <article className="print-order">
    <header><div><strong>{order.sourceCompany}</strong><span>ORDRE / JUSTIFICATIF D’ALIMENTATION BANCAIRE</span><small>Référence : {order.reference}</small></div><div className="print-order-meta"><time>{new Date(`${order.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</time><b>{order.sourceBank.name}</b></div></header>
    <section className="print-addressee"><b>Compte bancaire source</b><span>{order.sourceCompany} · {order.sourceBank.name} · {order.sourceBank.accountLabel}</span><span>RIB : {order.sourceBank.rib||"Non renseigné"}</span></section>
    <p><b>Objet : alimentation de trésorerie enregistrée</b></p><p>Le présent document certifie l’enregistrement de l’alimentation bancaire ci-dessous dans Moventra ERP.</p>
    <dl><div><dt>Société source du transfert</dt><dd>{order.sourceCompany}</dd></div><div><dt>Compte à débiter</dt><dd>{order.sourceBank.name} · {order.sourceBank.accountLabel}<small>RIB : {order.sourceBank.rib||"Non renseigné"}</small></dd></div><div><dt>Société bénéficiaire</dt><dd>{order.company}</dd></div><div><dt>Compte crédité</dt><dd>{order.bank.name} · {order.bank.accountLabel}<small>RIB : {order.bank.rib||"Non renseigné"}</small></dd></div><div><dt>Montant en chiffres</dt><dd><b className="incoming-value">{moneyExact(order.amount)}</b></dd></div><div><dt>Montant en lettres</dt><dd><b className="amount-in-words">{amountInFrench(order.amount)}</b></dd></div><div><dt>Solde avant</dt><dd>{moneyExact(order.balanceBefore)}</dd></div><div><dt>Solde après</dt><dd>{moneyExact(order.balanceAfter)}</dd></div><div><dt>Motif / traçabilité</dt><dd>{order.description}</dd></div></dl>
    <p>Cette réimpression restitue l’écriture historique existante et ne crée aucun nouveau mouvement bancaire.</p><footer><span>Visa du responsable</span><span>Cachet de la société</span></footer>
  </article>;
}
const normalized = (value:unknown) => String(value ?? "").normalize("NFD").replace(/[\u0300-\u036f]/g,"").trim().toLowerCase();
const normalizedRib = (value:unknown) => String(value ?? "" ).replace(/\s+/g,"" );
const validRib = (value:unknown) => /^\d{24}$/.test(normalizedRib(value));
const ribValidationMessage = "Le RIB doit contenir exactement 24 chiffres";
const normalizeScheduleFilterPreset = (value:Partial<ScheduleFilterPreset>&Record<string,unknown>):ScheduleFilterPreset => {
  const name=String(value.name||"Modèle de filtre").trim()||"Modèle de filtre";
  const normalizedName=normalized(name);
  const included=value.included==="included"||value.included==="excluded"||value.included==="all"
    ? value.included
    : normalizedName.includes("non pris")||normalizedName.includes("exclu") ? "excluded"
      : normalizedName.includes("pris en compte")||normalizedName.includes("inclu") ? "included" : "all";
  return {
    id:String(value.id||`filter-${Date.now()}`), name,
    company:typeof value.company==="string"&&value.company?value.company:"all",
    search:typeof value.search==="string"?value.search:"",
    dateFrom:typeof value.dateFrom==="string"?value.dateFrom:"",
    dateTo:typeof value.dateTo==="string"?value.dateTo:"",
    amountMin:typeof value.amountMin==="string"?value.amountMin:"",
    amountMax:typeof value.amountMax==="string"?value.amountMax:"",
    type:value.type==="client"||value.type==="fournisseur"?value.type:"tous",
    status:typeof value.status==="string"&&value.status?value.status:"all",
    payment:typeof value.payment==="string"&&value.payment?value.payment:"all",
    included,
    sort:value.sort==="newest"?"newest":"oldest",
    sortField:value.sortField==="reference"||value.sortField==="invoiceDate"||value.sortField==="dueDate"||value.sortField==="amount"?value.sortField:"plannedDate",
  };
};
const supplierNamesMatch = (flowParty:unknown,bankParty:unknown) => {
  const flowName=normalized(flowParty); const bankName=normalized(bankParty);
  if(!flowName||!bankName)return false;
  if(flowName===bankName)return true;
  const flowTokens=flowName.split(/[^a-z0-9]+/).filter(Boolean);
  return (bankName.length>=5&&flowName.includes(bankName))||(bankName.length>=3&&flowTokens.includes(bankName));
};
const bankInitials = (name:string) => {
  const key=normalized(name);
  const known:Array<[string,string]>=[["cfg","CFG"],["attijari","AWB"],["banque populaire","BP"],["bank of africa","BOA"],["bmce","BOA"],["credit du maroc","CDM"],["societe generale","SG"],["cih","CIH"],["bmci","BMCI"],["al barid","ABB"]];
  const match=known.find(([token])=>key.includes(token)); if(match)return match[1];
  const words=name.replace(/[^A-Za-zÀ-ÿ0-9 ]/g," ").split(/\s+/).filter(word=>word&&!/^(banque|bank|de|du|la|le|of)$/i.test(word));
  return (words.length>1?words.map(word=>word[0]).join(""):words[0]?.slice(0,3)||"BK").toUpperCase();
};

function isoDate(value:unknown): string | null {
  if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString().slice(0,10);
  if (typeof value === "number") {
    const parsed = XLSX.SSF.parse_date_code(value);
    if (parsed) return `${parsed.y}-${String(parsed.m).padStart(2,"0")}-${String(parsed.d).padStart(2,"0")}`;
  }
  if (typeof value === "string" && value.trim()) {
    const parsed = new Date(value);
    if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().slice(0,10);
  }
  return null;
}

async function readModel(file:File, kind:"client"|"fournisseur", companyId:string): Promise<ImportBatch> {
  const workbook = XLSX.read(await file.arrayBuffer(), { type:"array", cellDates:true });
  const sheet = workbook.Sheets[workbook.SheetNames[0]];
  const rows = XLSX.utils.sheet_to_json<unknown[]>(sheet, { header:1, defval:null, raw:true });
  if (!rows.length) throw new Error("Le fichier est vide");
  const headers = new Map((rows[0] as unknown[]).map((value,index)=>[normalized(value),index]));
  const required = kind === "client"
    ? ["piece","date","intitule client","echeance prevue","montant","reste a payer","e"]
    : ["piece","date","intitule fournisseur","echeance legale","montant","reste a payer","e"];
  const missing = required.filter(name=>!headers.has(name));
  if (missing.length) throw new Error(`Colonnes manquantes : ${missing.join(", ")}`);
  const at = (row:unknown[], name:string) => row[headers.get(name) ?? -1];
  const today = new Date().toISOString().slice(0,10);
  const items:ImportItem[] = [];

  rows.slice(1).forEach((raw,rowIndex)=>{
    const row = raw as unknown[];
    const remaining = Number(at(row,"reste a payer") ?? 0);
    if (!Number.isFinite(remaining) || Math.abs(remaining) < .005 || normalized(at(row,"e")).includes("totalement pay")) return;
    const reference = String(at(row,"piece") ?? "").trim();
    if (!reference) return;
    const partyLabel = kind === "client" ? "intitule client" : "intitule fournisseur";
    const partyCodeLabel = kind === "client" ? "client" : "fournisseur";
    const due = isoDate(at(row,kind === "client" ? "echeance prevue" : "echeance legale"))
      ?? isoDate(at(row,"echeance legale")) ?? isoDate(at(row,"date")) ?? today;
    items.push({
      sourceKey:`${kind}:${reference}`, kind,
      party:String(at(row,partyLabel) ?? at(row,partyCodeLabel) ?? "Sans intitulé").trim(),
      partyCode:String(at(row,partyCodeLabel) ?? "").trim(), reference,
      invoiceDate:isoDate(at(row,"date")), legalDueDate:isoDate(at(row,"echeance legale")),
      dueDate:due, plannedDate:due, originalAmount:Number(at(row,"montant") ?? 0),
      amount:remaining, remainingAmount:remaining, sourceFile:file.name, sourceRow:rowIndex+2,
      status:due < today ? "en retard" : "planifié",
    });
  });
  return { companyId, kind, fileName:file.name, sourceRows:Math.max(rows.length-1,0), items };
}

const treasuryPanelSections=["forecast","banks","paymentApprovals","settlements","executedTransfers","accountTransfer","blockedSuppliers","reserves","reports","settings","import"] as const;
type TreasuryInitialPanel=(typeof treasuryPanelSections)[number];

function initialTreasuryPanel(section?:string|null):TreasuryInitialPanel|null {
  return treasuryPanelSections.includes(section as TreasuryInitialPanel)?section as TreasuryInitialPanel:null;
}

export function TreasuryDashboard({initialEmbedded=false,initialUserName="Utilisateur connecté",initialPlanPage=null,initialSection=null}:{initialEmbedded?:boolean;initialUserName?:string;initialPlanPage?:"real"|"intelligent"|null;initialSection?:string|null}={}) {
  const [embedded,setEmbedded]=useState(initialEmbedded);
  const [navigationReady]=useState(true);
  const [companies,setCompanies] = useState<Company[]>(fallbackCompanies);
  const [flows,setFlows] = useState<Flow[]>(fallbackFlows);
  const [historyFlows,setHistoryFlows] = useState<Flow[]>([]);
  const [banks,setBanks] = useState<Bank[]>(fallbackBanks);
  const [bankTransactions,setBankTransactions] = useState<BankTransaction[]>([]);
  const [reserves,setReserves] = useState<ReserveAllocation[]>([]);
  const [paymentApprovals,setPaymentApprovals] = useState<PaymentApproval[]>([]);
  const [approvalPlannedDates,setApprovalPlannedDates] = useState<Record<number,string>>({});
  const [approvalBeneficiaryNames,setApprovalBeneficiaryNames] = useState<Record<number,string>>({});
  const [imports,setImports] = useState<ImportLog[]>([]);
  const [templates,setTemplates] = useState<ForecastTemplate[]>([]);
  const [supplierBanks,setSupplierBanks] = useState<SupplierBank[]>([]);
  const [transferOrders,setTransferOrders] = useState<TransferOrder[]>([]);
  const [internalTransfers,setInternalTransfers] = useState<InternalTransfer[]>([]);
  const [accountTransfers,setAccountTransfers] = useState<AccountTransfer[]>([]);
  const [blockedSuppliers,setBlockedSuppliers] = useState<BlockedSupplier[]>([]);
  const [supplierPolicies,setSupplierPolicies] = useState<SupplierPaymentPolicy[]>([]);
  const [filter,setFilter] = useState<"tous"|"client"|"fournisseur">("tous");
  const [partySearch,setPartySearch] = useState("");
  const [scheduleDateFrom,setScheduleDateFrom] = useState("");
  const [scheduleDateTo,setScheduleDateTo] = useState("");
  const [scheduleAmountMin,setScheduleAmountMin] = useState("");
  const [scheduleAmountMax,setScheduleAmountMax] = useState("");
  const [scheduleStatusFilter,setScheduleStatusFilter] = useState("all");
  const [schedulePaymentFilter,setSchedulePaymentFilter] = useState("all");
  const [scheduleIncludedFilter,setScheduleIncludedFilter] = useState("all");
  const [scheduleDateSort,setScheduleDateSort] = useState<"oldest"|"newest">("oldest");
  const [scheduleSortField,setScheduleSortField] = useState<ScheduleSortField>("plannedDate");
  const [savedScheduleFilters,setSavedScheduleFilters] = useState<ScheduleFilterPreset[]>([]);
  const [selectedScheduleFilterId,setSelectedScheduleFilterId] = useState("");
  const [approvalCompanyFilter,setApprovalCompanyFilter] = useState("all");
  const [approvalSupplierSearch,setApprovalSupplierSearch] = useState("");
  const [approvalAmountMin,setApprovalAmountMin] = useState("");
  const [approvalAmountMax,setApprovalAmountMax] = useState("");
  const [approvalStatusFilter,setApprovalStatusFilter] = useState("all");
  const [approvalDecisionFilter,setApprovalDecisionFilter] = useState("all");
  const [smartMonthSettings,setSmartMonthSettings] = useState<Record<string,SmartMonthSetting>>(defaultSmartMonthSettings);
  const [smartMonthDrafts,setSmartMonthDrafts] = useState<Record<string,SmartMonthSetting>>(defaultSmartMonthSettings);
  const [smartSavedSettings,setSmartSavedSettings] = useState<Record<string,SmartMonthSetting>>(defaultSmartMonthSettings);
  const [smartScenarioPendingDecision,setSmartScenarioPendingDecision] = useState(false);
  const [smartPlanExpanded,setSmartPlanExpanded] = useState(initialPlanPage==="intelligent");
  const [smartSettingsExpanded,setSmartSettingsExpanded] = useState(false);
  const [smartSupplierPoliciesExpanded,setSmartSupplierPoliciesExpanded] = useState(true);
  const [companyFilter,setCompanyFilter] = useState<string>("all");
  const [importCompany,setImportCompany] = useState<string>("moventra");
  const [panel,setPanel] = useState<"invoice"|"import"|"banks"|"bankHistory"|"settlements"|"executedTransfers"|"paymentApprovals"|"blockedSuppliers"|"reserves"|"fundAccount"|"accountTransfer"|"settlement"|"receiptReserve"|"forecast"|"settings"|"transfer"|"split"|"editFlow"|"reports"|null>(()=>initialTreasuryPanel(initialSection));
  const [pendingImports,setPendingImports] = useState<Partial<Record<"client"|"fournisseur",ImportBatch>>>({});
  const [importErrors,setImportErrors] = useState<Partial<Record<"client"|"fournisseur",string>>>({});
  const [busy,setBusy] = useState(false);
  const [importDecisionOpen,setImportDecisionOpen] = useState(false);
  const [importPreview,setImportPreview] = useState<ImportPreview|null>(null);
  const [toast,setToast] = useState("");
  const [selectedTemplateId,setSelectedTemplateId] = useState<number>(0);
  const [transferFlow,setTransferFlow] = useState<Flow|null>(null);
  const [transferReceptionBeneficiaryName,setTransferReceptionBeneficiaryName] = useState("");
  const [transferPaymentMethod,setTransferPaymentMethod] = useState<"mobile"|"bank"|"other">("mobile");
  const [supplierBankCreationFromTransfer,setSupplierBankCreationFromTransfer] = useState(false);

  const [selectedTransferIds,setSelectedTransferIds] = useState<number[]>([]);
  const [splitFlow,setSplitFlow] = useState<Flow|null>(null);
  const [editingFlow,setEditingFlow] = useState<Flow|null>(null);
  const [splitLines,setSplitLines] = useState<Array<{amount:number;plannedDate:string}>>([]);
  const [settlementFlow,setSettlementFlow] = useState<Flow|null>(null);
  const [settlementBankId,setSettlementBankId] = useState<number>(0);
  const [settlementDate,setSettlementDate] = useState(new Date().toISOString().slice(0,10));
  const [receiptReserve,setReceiptReserve] = useState<ReceiptReserve|null>(null);
  const [receiptReservePercent,setReceiptReservePercent] = useState(10);
  const [historyBankId,setHistoryBankId] = useState<number>(0);
  const [historyDateFrom,setHistoryDateFrom] = useState("");
  const [historyDateTo,setHistoryDateTo] = useState("");
  const [settlementHistoryDateFrom,setSettlementHistoryDateFrom] = useState("");
  const [settlementHistoryDateTo,setSettlementHistoryDateTo] = useState("");
  const [accountTransferHistoryDateFrom,setAccountTransferHistoryDateFrom] = useState("");
  const [accountTransferHistoryDateTo,setAccountTransferHistoryDateTo] = useState("");
  const [settlementHistoryCompany,setSettlementHistoryCompany] = useState<string>("all");
  const [settlementHistoryKind,setSettlementHistoryKind] = useState<"all"|"client"|"fournisseur">("all");
  const [settlementHistorySearch,setSettlementHistorySearch] = useState("");
  const [executedTransferDateFrom,setExecutedTransferDateFrom] = useState("");
  const [executedTransferDateTo,setExecutedTransferDateTo] = useState("");
  const [executedTransferCompany,setExecutedTransferCompany] = useState("all");
  const [executedTransferSearch,setExecutedTransferSearch] = useState("");
  const [blockedCompanyId,setBlockedCompanyId] = useState<string>("moventra");
  const [supplierBankCompanyId,setSupplierBankCompanyId] = useState<string>("moventra");
  const [supplierBankSupplierName,setSupplierBankSupplierName] = useState("");
  const [supplierBankBeneficiaryName,setSupplierBankBeneficiaryName] = useState("");
  const [supplierBankName,setSupplierBankName] = useState("");
  const [supplierBankNameMode,setSupplierBankNameMode] = useState<"existing"|"new">("existing");
  const [reserveCompanyId,setReserveCompanyId] = useState<string>("moventra");
  const [accountTransferCompanyId,setAccountTransferCompanyId] = useState<string>("moventra");
  const [accountTransferSourceId,setAccountTransferSourceId] = useState<number>(0);
  const [accountTransferDestinationId,setAccountTransferDestinationId] = useState<number>(0);
  const [accountTransferAmount,setAccountTransferAmount] = useState<number>(0);
  const [accountTransferDebitDate,setAccountTransferDebitDate] = useState(new Date().toISOString().slice(0,10));
  const [accountTransferReceptionDate,setAccountTransferReceptionDate] = useState(new Date().toISOString().slice(0,10));
  const [fundingCompanyId,setFundingCompanyId] = useState<string>("moventra");
  const [fundingSourceCompanyId,setFundingSourceCompanyId] = useState<string>("");
  const [fundingBankId,setFundingBankId] = useState<number>(0);
  const [fundingAmount,setFundingAmount] = useState<number>(0);
  const [fundingDate,setFundingDate] = useState(new Date().toISOString().slice(0,10));
  const [fundingReason,setFundingReason] = useState("Apport de trésorerie");
  const [printOrder,setPrintOrder] = useState<PrintOrder|null>(null);
  const transferPrintLock = useRef(false);
  const [internalTransferPrintOrder,setInternalTransferPrintOrder] = useState<InternalTransferPrintOrder|null>(null);
  const [accountFundingPrintOrder,setAccountFundingPrintOrder] = useState<AccountFundingPrintOrder|null>(null);
  const [supplierPaymentAdvice,setSupplierPaymentAdvice] = useState<SupplierPaymentAdvice|null>(null);
  const [legacyFundingSourceCompanyId,setLegacyFundingSourceCompanyId] = useState<string>("ctl");
  const [legacyFundingSourceBankId,setLegacyFundingSourceBankId] = useState<number>(0);
  const [printMode,setPrintMode] = useState<"schedule"|"order"|"report"|"approvals"|"intelligent"|"smartReport"|"bankHistory"|"accountTransfers"|"settlements"|"executedTransfers"|"remittanceAdvice"|null>(null);
  const [bankEditor,setBankEditor] = useState<number|"new"|null>(null);
  const [supplierBankEditor,setSupplierBankEditor] = useState<number|null>(null);
  const [showAllSupplierBanks,setShowAllSupplierBanks] = useState(false);
  const [chartPeriod,setChartPeriod] = useState<"day"|"week"|"month">("week");
  const [view,setView] = useState<"overview"|"planning">(()=>initialSection==="planning"?"planning":"overview");
  const stableFinancialSection = useRef("overview");
  const [planningScale,setPlanningScale] = useState<"day"|"week"|"month">("week");
  const [planningSelection,setPlanningSelection] = useState<{index:number;kind:"all"|"client"|"fournisseur";forecast?:boolean}|null>(null);
  const [schedulePage,setSchedulePage] = useState(0);

  const refresh = () => fetch("/api/treasury").then(r=>r.ok?r.json():Promise.reject()).then(data=>{
    if (Array.isArray(data.companies) && data.companies.length) setCompanies(data.companies);
    if (Array.isArray(data.flows)) setFlows(data.flows);
    if (Array.isArray(data.historyFlows)) setHistoryFlows(data.historyFlows);
    if (Array.isArray(data.banks)) setBanks(data.banks);
    if (Array.isArray(data.imports)) setImports(data.imports);
    if (Array.isArray(data.transactions)) setBankTransactions(data.transactions);
    if (Array.isArray(data.reserves)) setReserves(data.reserves);
    if (Array.isArray(data.approvals)) setPaymentApprovals(data.approvals);
    if (Array.isArray(data.internalTransfers)) setInternalTransfers(data.internalTransfers);
    if (Array.isArray(data.accountTransfers)) setAccountTransfers(data.accountTransfers);
  }).catch(()=>undefined);
  const refreshSettings = () => fetch("/api/treasury/settings").then(r=>r.ok?r.json():Promise.reject()).then(data=>{
    if (Array.isArray(data.templates)) setTemplates(data.templates);
    if (Array.isArray(data.supplierBanks)) setSupplierBanks(data.supplierBanks);
    if (Array.isArray(data.orders)) setTransferOrders(data.orders);
    if (Array.isArray(data.blockedSuppliers)) setBlockedSuppliers(data.blockedSuppliers);
    if (Array.isArray(data.supplierPolicies)) setSupplierPolicies(data.supplierPolicies);
  }).catch(()=>undefined);
  useEffect(()=>{setEmbedded(initialEmbedded||new URLSearchParams(window.location.search).get("embedded")==="1"||window.parent!==window)},[initialEmbedded]);
  useEffect(()=>{ refresh(); refreshSettings(); },[]);
  useEffect(()=>{
    const defaultCompanyId=companies[0]?.id;
    if(!defaultCompanyId)return;
    const valid=(companyId:string)=>companies.some(company=>company.id===companyId);
    if(!valid(importCompany))setImportCompany(defaultCompanyId);
    if(!valid(blockedCompanyId))setBlockedCompanyId(defaultCompanyId);
    if(!valid(supplierBankCompanyId))setSupplierBankCompanyId(defaultCompanyId);
    if(!valid(reserveCompanyId))setReserveCompanyId(defaultCompanyId);
    if(!valid(accountTransferCompanyId))setAccountTransferCompanyId(defaultCompanyId);
  },[companies,importCompany,blockedCompanyId,supplierBankCompanyId,reserveCompanyId,accountTransferCompanyId]);

  const findFlow = (flowId:number|null|undefined) => flows.find(flow=>flow.id===flowId)||historyFlows.find(flow=>flow.id===flowId);
  const scopedFlows = flows.filter(flow=>companyFilter==="all"||flow.companyId===companyFilter);
  const scopedBanks = banks.filter(bank=>companyFilter==="all"||bank.companyId===companyFilter);
  const scopedReserves = reserves.filter(reserve=>companyFilter==="all"||reserve.companyId===companyFilter);
  const grossBankTotal = scopedBanks.reduce((sum,bank)=>sum+Number(bank.balance),0);
  const excludedReserveTotal = scopedReserves.filter(reserve=>!reserve.includedInTreasury).reduce((sum,reserve)=>sum+Number(reserve.amount),0);
  const bankTotal = grossBankTotal-excludedReserveTotal;
  const latestPrintedOrdersByFlow=useMemo(()=>{
    const activeFlowIds=new Set(flows.map(flow=>flow.id));
    const result=new Map<number,TransferOrder>();
    transferOrders.filter(order=>order.status==="imprimé"&&order.cashFlowId&&activeFlowIds.has(order.cashFlowId)).sort((a,b)=>a.id-b.id).forEach(order=>result.set(Number(order.cashFlowId),order));
    return result;
  },[flows,transferOrders]);
  const preparedTransferOrders=Array.from(latestPrintedOrdersByFlow.values());
  const pendingDecisionFlowIds=new Set(paymentApprovals.filter(approval=>approval.status==="pending").map(approval=>approval.cashFlowId));
  const approvedAwaitingOrderFlowIds=new Set(paymentApprovals.filter(approval=>approval.status==="approved"&&!latestPrintedOrdersByFlow.has(approval.cashFlowId)).map(approval=>approval.cashFlowId));
  const preparedTransferSummaries=companies.map(company=>{
    const available=moneyValue(banks.filter(bank=>bank.companyId===company.id).reduce((sum,bank)=>sum+Number(bank.balance),0)-reserves.filter(reserve=>reserve.companyId===company.id&&!reserve.includedInTreasury).reduce((sum,reserve)=>sum+Number(reserve.amount),0));
    const prepared=moneyValue(preparedTransferOrders.filter(order=>order.companyId===company.id).reduce((sum,order)=>sum+Number(order.amount),0));
    const pendingDecision=moneyValue(flows.filter(flow=>flow.companyId===company.id&&pendingDecisionFlowIds.has(flow.id)).reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0));
    const approvedAwaitingOrder=moneyValue(flows.filter(flow=>flow.companyId===company.id&&approvedAwaitingOrderFlowIds.has(flow.id)).reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0));
    const committed=moneyValue(prepared+approvedAwaitingOrder);
    const incomingTransferPending=moneyValue(internalTransfers.filter(transfer=>transfer.toCompanyId===company.id&&(transfer.status==="pending"||transfer.status==="issued")).reduce((sum,transfer)=>sum+Number(transfer.amount),0));
    const remaining=moneyValue(available-committed);
    const deficit=Math.max(0,-remaining);
    const ratio=available>0?committed/available:committed>0?Infinity:0;
    const remainingRatio=available>0?Math.max(0,Math.min(1,remaining/available)):0;
    return {companyId:company.id,available,prepared,pendingDecision,approvedAwaitingOrder,committed,incomingTransferPending,remaining,deficit,remainingRatio,need:Math.max(0,moneyValue(deficit-incomingTransferPending)),ratio,alert:ratio>.9&&committed>0};
  });
  const preparedTransferAlerts=preparedTransferSummaries.filter(summary=>summary.alert&&(companyFilter==="all"||summary.companyId===companyFilter));
  const totals = useMemo(()=>{
    const calculated=scopedFlows.filter(f=>!f.excluded);
    const incoming=calculated.filter(f=>f.kind==="client").reduce((s,f)=>s+Number(f.amount),0);
    const outgoing=calculated.filter(f=>f.kind==="fournisseur").reduce((s,f)=>s+Number(f.amount),0);
    return { incoming,outgoing,projected:bankTotal+incoming-outgoing };
  },[scopedFlows,bankTotal]);
  const partyQuery=normalized(partySearch); const amountQuery=partySearch.replace(/[^\d]/g,"");
  const scheduleStateForFlow=(flow:Flow)=>{const approval=flow.kind==="fournisseur"?paymentApprovals.find(item=>item.cashFlowId===flow.id):undefined;if(flow.excluded)return "ignored";if(latestPrintedOrdersByFlow.has(flow.id))return "printed";if(approval?.status==="pending")return "pending";if(approval?.status==="approved")return "approved";if(approval?.status==="rejected")return "rejected";return flow.status==="en retard"?"late":"planned";};
  const schedulePaymentForFlow=(flow:Flow)=>{const approval=flow.kind==="fournisseur"?paymentApprovals.find(item=>item.cashFlowId===flow.id):undefined;if(flow.excluded)return "excluded";if(flow.kind==="client")return flow.isForecast?"forecast":"receipt";if(latestPrintedOrdersByFlow.has(flow.id))return "printed";if(approval?.status==="pending")return "validation";if(approval?.status==="approved")return "to_print";return "prepare";};
  const compareScheduleFlows=(a:Flow,b:Flow)=>{const value=(flow:Flow)=>scheduleSortField==="amount"?Math.abs(Number(flow.amount)):scheduleSortField==="reference"?normalized(flow.reference):scheduleSortField==="invoiceDate"?(flow.invoiceDate||""):scheduleSortField==="dueDate"?flow.dueDate:flow.plannedDate;const left=value(a),right=value(b);const comparison=typeof left==="number"&&typeof right==="number"?left-right:String(left).localeCompare(String(right),"fr");return (scheduleDateSort==="oldest"?comparison:-comparison)||a.id-b.id;};
  const visible = scopedFlows.filter(flow=>{const absoluteAmount=Math.abs(Number(flow.amount));const textMatch=!partyQuery||normalized(flow.party).includes(partyQuery)||normalized(flow.partyCode).includes(partyQuery)||normalized(flow.reference).includes(partyQuery)||(amountQuery.length>0&&String(absoluteAmount).replace(/[^\d]/g,"").includes(amountQuery));return (filter==="tous"||flow.kind===filter)&&textMatch&&(!scheduleDateFrom||flow.plannedDate>=scheduleDateFrom)&&(!scheduleDateTo||flow.plannedDate<=scheduleDateTo)&&(!scheduleAmountMin||absoluteAmount>=Number(scheduleAmountMin))&&(!scheduleAmountMax||absoluteAmount<=Number(scheduleAmountMax))&&(scheduleStatusFilter==="all"||scheduleStateForFlow(flow)===scheduleStatusFilter)&&(schedulePaymentFilter==="all"||schedulePaymentForFlow(flow)===schedulePaymentFilter)&&(scheduleIncludedFilter==="all"||(scheduleIncludedFilter==="included"?!flow.excluded:flow.excluded));}).sort(compareScheduleFlows);
  const partyOptions=Array.from(new Map(scopedFlows.filter(flow=>filter==="tous"||flow.kind===filter).map(flow=>[normalized(flow.party),flow.party])).values()).sort((a,b)=>a.localeCompare(b,"fr"));
  const schedulePageSize=12;
  const schedulePageCount=Math.max(1,Math.ceil(visible.length/schedulePageSize));
  const pagedVisible=visible.slice(schedulePage*schedulePageSize,(schedulePage+1)*schedulePageSize);
  const selectedTransferFlows=flows.filter(flow=>selectedTransferIds.includes(flow.id)&&flow.kind==="fournisseur");
  const selectedTransferTotal=selectedTransferFlows.reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0);
  const consolidationGroups=Array.from(flows.reduce((groups,flow)=>{
    const approval=paymentApprovals.find(item=>item.cashFlowId===flow.id);
    const alreadySettled=bankTransactions.some(item=>item.cashFlowId===flow.id&&!item.cancelledAt);
    const alreadyOrdered=transferOrders.some(item=>item.cashFlowId===flow.id&&(item.status==="brouillon"||item.status==="imprimé"));
    if(flow.kind!=="fournisseur"||flow.excluded||approval?.status!=="approved"||alreadySettled||alreadyOrdered)return groups;
    const key=`${flow.companyId}::${normalized(flow.party)}::${flow.plannedDate}`;
    const current=groups.get(key)||[];current.push(flow);groups.set(key,current);return groups;
  },new Map<string,Flow[]>()).values()).filter(group=>group.length>1).sort((a,b)=>a[0].plannedDate.localeCompare(b[0].plannedDate)||a[0].party.localeCompare(b[0].party,"fr"));
  const transferDraftOrder=transferFlow?transferOrders.filter(order=>order.cashFlowId===transferFlow.id&&order.status==="brouillon").sort((a,b)=>b.id-a.id)[0]:undefined;
  const transferDraftSupplierBank=transferDraftOrder?supplierBanks.find(bank=>bank.id===transferDraftOrder.supplierBankId):undefined;
  const matchingTransferSupplierBanks=transferFlow?supplierBanks.filter(bank=>supplierNamesMatch(transferFlow.party,bank.supplierName)||supplierNamesMatch(transferFlow.party,bank.beneficiaryName)||Boolean(transferFlow.beneficiaryName&&(supplierNamesMatch(transferFlow.beneficiaryName,bank.supplierName)||supplierNamesMatch(transferFlow.beneficiaryName,bank.beneficiaryName)))):[];
  const transferSupplierBankOptions=showAllSupplierBanks?supplierBanks.slice().sort((a,b)=>(a.companyId===transferFlow?.companyId?-1:1)-(b.companyId===transferFlow?.companyId?-1:1)||a.supplierName.localeCompare(b.supplierName,"fr")):Array.from(new Map([transferDraftSupplierBank,...matchingTransferSupplierBanks].filter((bank):bank is SupplierBank=>Boolean(bank)).map(bank=>[bank.id,bank])).values());
  const ownBeneficiaryBanks=banks.filter(bank=>Boolean(bank.rib)).slice().sort((a,b)=>(a.companyId===transferFlow?.companyId?-1:1)-(b.companyId===transferFlow?.companyId?-1:1)||a.companyId.localeCompare(b.companyId,"fr")||a.name.localeCompare(b.name,"fr"));



  const preferredTransferSupplierBank=transferDraftSupplierBank||(transferFlow?(matchingTransferSupplierBanks.find(bank=>bank.companyId===transferFlow.companyId)||matchingTransferSupplierBanks[0]):undefined);
  const forecasts = scopedFlows.filter(f=>f.isForecast);
  const companyName = (id:string) => companies.find(company=>company.id===id)?.shortName ?? id.toUpperCase();
  const selectedCompanyId = companyFilter==="all" ? companies[0]?.id??"moventra" : companyFilter;
  const settlementBank=banks.find(bank=>bank.id===settlementBankId);
  const historyBank=banks.find(bank=>bank.id===historyBankId);
  const receiptReserveBank=banks.find(bank=>bank.id===receiptReserve?.bankId);
  const receiptReserveAmount=receiptReserve?Math.round(Math.abs(receiptReserve.flow.amount)*receiptReservePercent)/100:0;
  const receiptBankReserved=receiptReserve?reserves.filter(reserve=>reserve.bankAccountId===receiptReserve.bankId&&!reserve.includedInTreasury).reduce((sum,reserve)=>sum+reserve.amount,0):0;
  const receiptAvailableBefore=receiptReserveBank?receiptReserveBank.balance-receiptBankReserved:0;
  const accountTransferBanks=banks.filter(bank=>bank.companyId===accountTransferCompanyId);
  const accountTransferSource=accountTransferBanks.find(bank=>bank.id===accountTransferSourceId);
  const accountTransferDestination=accountTransferBanks.find(bank=>bank.id===accountTransferDestinationId);
  const accountTransferProtected=accountTransferSource?reserves.filter(reserve=>reserve.bankAccountId===accountTransferSource.id&&!reserve.includedInTreasury).reduce((sum,reserve)=>sum+Number(reserve.amount),0):0;
  const fundingSourceCompanies=companies.filter(company=>company.id!==fundingCompanyId);
  const fundingBanks=banks.filter(bank=>bank.companyId===fundingCompanyId);
  const fundingBank=fundingBanks.find(bank=>bank.id===fundingBankId);
  const historyTransactions=bankTransactions.filter(transaction=>transaction.bankAccountId===historyBankId&&(!historyDateFrom||transaction.transactionDate>=historyDateFrom)&&(!historyDateTo||transaction.transactionDate<=historyDateTo));
  const accountTransferHistory=accountTransfers.filter(transfer=>transfer.companyId===accountTransferCompanyId&&(!accountTransferHistoryDateFrom||transfer.debitDate>=accountTransferHistoryDateFrom)&&(!accountTransferHistoryDateTo||transfer.debitDate<=accountTransferHistoryDateTo)).map(transfer=>({transfer,sourceBank:banks.find(bank=>bank.id===transfer.sourceBankId),destinationBank:banks.find(bank=>bank.id===transfer.destinationBankId)}));
  const settlementHistoryQuery=normalized(settlementHistorySearch);
  const settlementTransactions=bankTransactions.filter(transaction=>transaction.cashFlowId&&(transaction.kind==="client"||transaction.kind==="fournisseur")&&(settlementHistoryCompany==="all"||transaction.companyId===settlementHistoryCompany)&&(settlementHistoryKind==="all"||transaction.kind===settlementHistoryKind)&&(!settlementHistoryDateFrom||transaction.transactionDate>=settlementHistoryDateFrom)&&(!settlementHistoryDateTo||transaction.transactionDate<=settlementHistoryDateTo)&&(!settlementHistoryQuery||normalized(transaction.party).includes(settlementHistoryQuery)||normalized(transaction.reference).includes(settlementHistoryQuery)));
  const activeSettlementTransactions=settlementTransactions.filter(transaction=>!transaction.cancelledAt);
  const executedTransferQuery=normalized(executedTransferSearch);
  const executedTransferRows=bankTransactions.filter(transaction=>transaction.direction==="debit"&&!transaction.cancelledAt&&((transaction.cashFlowId&&transaction.kind==="fournisseur")||transaction.reference.startsWith("VC-")||transaction.reference.startsWith("SOURCE-"))&&(!transaction.reference.startsWith("VC-")||accountTransfers.some(transfer=>transfer.reference===transaction.reference&&transfer.status==="received"))).map(transaction=>{
    const flow=findFlow(transaction.cashFlowId);
    const approval=paymentApprovals.find(item=>item.cashFlowId===transaction.cashFlowId);
    const order=transferOrders.filter(item=>item.cashFlowId===transaction.cashFlowId).sort((a,b)=>b.id-a.id)[0];
    const sourceBank=banks.find(item=>item.id===transaction.bankAccountId);
    const internalTransfer=internalTransfers.find(item=>item.outgoingFlowId===transaction.cashFlowId&&item.status==="completed");
    const counterpartReference=transaction.reference.startsWith("SOURCE-")?transaction.reference.slice(7):transaction.reference;
    const pairedTransaction=internalTransfer?bankTransactions.find(item=>item.cashFlowId===internalTransfer.incomingFlowId&&item.direction==="credit"&&!item.cancelledAt):bankTransactions.find(item=>item.id!==transaction.id&&item.direction==="credit"&&!item.cancelledAt&&Math.abs(item.amount-transaction.amount)<.01&&(item.reference===transaction.reference||item.reference===counterpartReference));
    const destinationBank=pairedTransaction?banks.find(item=>item.id===pairedTransaction.bankAccountId):undefined;
    const supplierBank=order?supplierBanks.find(item=>item.id===order.supplierBankId):undefined;
    const beneficiaryName=internalTransfer?companyName(internalTransfer.toCompanyId):pairedTransaction?companyName(pairedTransaction.companyId):flow?.beneficiaryName||supplierBank?.beneficiaryName||flow?.party||transaction.party;
    const beneficiaryBank=destinationBank?.name||supplierBank?.bankName||(transaction.paymentMethod&&transaction.paymentMethod!=="bank"?paymentMethodLabel(transaction.paymentMethod):"Compte bénéficiaire non archivé");
    const beneficiaryAccount=destinationBank?`${destinationBank.accountLabel||"—"}${destinationBank.rib?` · RIB ${destinationBank.rib}`:""}`:supplierBank?`RIB ${supplierBank.rib}${supplierBank.iban?` · IBAN ${supplierBank.iban}`:""}`:transaction.paymentReference?`Référence ${transaction.paymentReference}`:"Sans compte bénéficiaire";
    return {transaction,flow,approval,order,sourceBank,internalTransfer,beneficiaryName,beneficiaryBank,beneficiaryAccount};
  }).filter(row=>(executedTransferCompany==="all"||row.transaction.companyId===executedTransferCompany)&&(!executedTransferDateFrom||row.transaction.transactionDate>=executedTransferDateFrom)&&(!executedTransferDateTo||row.transaction.transactionDate<=executedTransferDateTo)&&(!executedTransferQuery||normalized(`${row.transaction.reference} ${row.flow?.reference||""} ${row.beneficiaryName} ${row.sourceBank?.name||""} ${row.beneficiaryBank}`).includes(executedTransferQuery))).sort((a,b)=>b.transaction.transactionDate.localeCompare(a.transaction.transactionDate)||b.transaction.id-a.transaction.id);
  function printSupplierPaymentAdvice(row:(typeof executedTransferRows)[number],documentKind:"advice"|"order"="advice") {
    if(row.internalTransfer)return flash("L’avis fournisseur est réservé aux règlements de factures fournisseurs");
    const group=row.order?.internalNumber?executedTransferRows.filter(candidate=>candidate.order?.internalNumber===row.order?.internalNumber&&candidate.beneficiaryName===row.beneficiaryName):[row];
    const paidRows=group.length?group:[row];
    const supplierBank=row.order?supplierBanks.find(bank=>bank.id===row.order?.supplierBankId):undefined;
    const sourceAccount=row.sourceBank?`${row.sourceBank.accountLabel||"Compte bancaire"} · RIB ${formatFullRib(row.sourceBank.rib)}`:"Compte débité non archivé";
    const beneficiaryAccount=supplierBank?`RIB ${formatFullRib(supplierBank.rib)}`:row.beneficiaryAccount;
    const paymentDate=paidRows.map(item=>item.transaction.transactionDate).sort().slice(-1)[0]||row.transaction.transactionDate;
    const compactDate=paymentDate.replace(/-/g,"");
    setSupplierPaymentAdvice({
      documentKind,
      adviceNumber:`${documentKind==="order"?"OR":"AR"}-${row.transaction.companyId.toUpperCase()}-${compactDate}-${row.transaction.id}`,
      payerCompany:companies.find(company=>company.id===row.transaction.companyId)?.name||companyName(row.transaction.companyId),
      payerBank:row.sourceBank?.name||"Banque émettrice non archivée",
      payerAccount:sourceAccount,
      supplierName:(row.flow?.party&& !/^PRV-/i.test(row.flow.party))?row.flow.party:row.beneficiaryName,
      operationLabel:row.flow?.operationLabel||row.transaction.paymentPurpose||row.flow?.note||null,
      beneficiaryName:row.beneficiaryName,
      beneficiaryBank:row.beneficiaryBank,
      beneficiaryAccount,
      paymentDate,
      paymentMethod:paymentMethodLabel(row.transaction.paymentMethod||(row.order?"bank":"Mode non archivé (opération antérieure)")),
      paymentReference:row.transaction.paymentReference,
      executionReference:paidRows.map(item=>item.transaction.paymentReference||item.transaction.reference).filter((value,index,items)=>items.indexOf(value)===index).join(" · "),
      orderReference:row.order?.internalNumber,
      amount:paidRows.reduce((sum,item)=>sum+Number(item.transaction.amount),0),
      invoices:paidRows.map(item=>({reference:item.flow?.reference||item.transaction.reference,invoiceDate:item.flow?.invoiceDate,amount:Number(item.transaction.amount)})),
      generatedAt:new Date().toLocaleString("fr-FR")
    });
    setPrintMode("remittanceAdvice");
    window.setTimeout(()=>{window.print();setSupplierPaymentAdvice(null);setPrintMode(null);},100);
  }
  function printSettlementPaymentAdvice(transaction:BankTransaction) {
    const row=executedTransferRows.find(item=>item.transaction.id===transaction.id);
    if(!row)return flash("Justificatif indisponible pour cette opération");
    printSupplierPaymentAdvice(row,"advice");
  }
  function printSettlementPaymentOrder(transaction:BankTransaction) {
    const row=executedTransferRows.find(item=>item.transaction.id===transaction.id);
    if(!row)return flash("Ordre de règlement indisponible pour cette opération");
    printSupplierPaymentAdvice(row,"order");
  }
  const pendingPaymentApprovals=paymentApprovals.filter(approval=>approval.status==="pending");
  const approvalForFlow=(flowId:number)=>paymentApprovals.find(approval=>approval.cashFlowId===flowId);
  const printedOrderForFlow=(flowId:number)=>transferOrders.filter(order=>order.cashFlowId===flowId&&order.status==="imprimé").sort((a,b)=>b.id-a.id)[0];
  const internalTransferForFlow=(flowId:number)=>internalTransfers.find(transfer=>transfer.outgoingFlowId===flowId);
  const historyPartyDetails=(transaction:BankTransaction)=>{
    const selectedBank=banks.find(bank=>bank.id===transaction.bankAccountId);
    const selectedParty={name:companyName(transaction.companyId),detail:selectedBank?`${selectedBank.name} · ${selectedBank.accountLabel}${selectedBank.rib?` · RIB ${selectedBank.rib}`:""}`:"Compte bancaire"};
    const pairedReference=transaction.reference.startsWith("SOURCE-")?transaction.reference.slice(7):`SOURCE-${transaction.reference}`;
    const pairedTransaction=bankTransactions.find(item=>item.id!==transaction.id&&item.direction!==transaction.direction&&Math.abs(item.amount-transaction.amount)<.01&&(item.reference===transaction.reference||item.reference===pairedReference));
    const savedAccountTransfer=accountTransfers.find(transfer=>transfer.reference===transaction.reference);
    const pairedBank=pairedTransaction?banks.find(bank=>bank.id===pairedTransaction.bankAccountId):savedAccountTransfer?banks.find(bank=>bank.id===(transaction.direction==="debit"?savedAccountTransfer.destinationBankId:savedAccountTransfer.sourceBankId)):undefined;
    const savedOrder=transaction.cashFlowId?transferOrders.filter(order=>order.cashFlowId===transaction.cashFlowId).sort((a,b)=>b.id-a.id)[0]:undefined;
    const supplierBank=savedOrder?supplierBanks.find(bank=>bank.id===savedOrder.supplierBankId):undefined;
    const counterpart=pairedBank
      ? {name:companyName(pairedBank.companyId),detail:`${pairedBank.name} · ${pairedBank.accountLabel}${pairedBank.rib?` · RIB ${pairedBank.rib}`:""}`}
      : supplierBank&&transaction.direction==="debit"
        ? {name:supplierBank.beneficiaryName,detail:`${supplierBank.bankName} · RIB ${supplierBank.rib}`}
        : {name:transaction.party,detail:`${transaction.reference} · ${transaction.kind==="client"?"Origine de l’encaissement":transaction.kind==="fournisseur"?"Bénéficiaire du règlement":"Opération d’ajustement"}`};
    return transaction.direction==="credit"?{sender:counterpart,receiver:selectedParty}:{sender:selectedParty,receiver:counterpart};
  };
  const approvalSupplierQuery=normalized(approvalSupplierSearch);
  const filteredPaymentApprovals=paymentApprovals.filter(approval=>{
    const flow=findFlow(approval.cashFlowId); const transaction=bankTransactions.find(item=>item.cashFlowId===approval.cashFlowId&&!item.cancelledAt);
    const printedOrder=printedOrderForFlow(approval.cashFlowId); const internalTransfer=internalTransferForFlow(approval.cashFlowId); const amount=Math.abs(Number(flow?.amount||transaction?.amount||internalTransfer?.amount||0));
    const supplier=flow?.party||transaction?.party||(internalTransfer?`TRANSFERT INTERNE VERS ${companyName(internalTransfer.toCompanyId)}`:""); const state=internalTransfer?.status==="cancelled"?"cancelled":transaction||internalTransfer?.status==="completed"?"executed":approval.status;
    const decision=internalTransfer?.status==="cancelled"?"cancelled":transaction||internalTransfer?.status==="completed"?"executed":approval.status==="pending"?"required":approval.status==="rejected"?"rejected":printedOrder?"printed":"to_print";
    return (approvalCompanyFilter==="all"||approval.companyId===approvalCompanyFilter)&&(!approvalSupplierQuery||normalized(supplier).includes(approvalSupplierQuery)||normalized(flow?.reference||transaction?.reference).includes(approvalSupplierQuery))&&(!approvalAmountMin||amount>=Number(approvalAmountMin))&&(!approvalAmountMax||amount<=Number(approvalAmountMax))&&(approvalStatusFilter==="all"||state===approvalStatusFilter)&&(approvalDecisionFilter==="all"||decision===approvalDecisionFilter);
  });
  const accountFundingTransactions=bankTransactions.filter(transaction=>transaction.kind==="ajustement"&&transaction.direction==="credit"&&transaction.reference.startsWith("ALIM-")&&!transaction.cancelledAt);
  const legacyFundingSourceBanks=banks.filter(bank=>bank.companyId===legacyFundingSourceCompanyId);
  const legacyFundingSourceBank=legacyFundingSourceBanks.find(bank=>bank.id===legacyFundingSourceBankId)||legacyFundingSourceBanks.find(bank=>bank.isPrimary)||legacyFundingSourceBanks[0];
  const filteredAccountFundings=accountFundingTransactions.filter(transaction=>{
    const amount=Math.abs(Number(transaction.amount));
    return (approvalCompanyFilter==="all"||transaction.companyId===approvalCompanyFilter)&&(!approvalSupplierQuery||normalized(transaction.party).includes(approvalSupplierQuery)||normalized(transaction.reference).includes(approvalSupplierQuery)||normalized(companyName(transaction.companyId)).includes(approvalSupplierQuery))&&(!approvalAmountMin||amount>=Number(approvalAmountMin))&&(!approvalAmountMax||amount<=Number(approvalAmountMax))&&(approvalStatusFilter==="all"||approvalStatusFilter==="executed")&&(approvalDecisionFilter==="all"||approvalDecisionFilter==="executed");
  });
  const blockedSupplierCandidates=Array.from(new Map(flows.filter(flow=>flow.kind==="fournisseur"&&flow.companyId===blockedCompanyId).map(flow=>[normalized(flow.party),flow.party])).values()).sort((a,b)=>a.localeCompare(b,"fr"));
  const supplierBankCandidates=Array.from(new Map([
    ...flows.filter(flow=>flow.kind==="fournisseur"&&flow.companyId===supplierBankCompanyId).map(flow=>[normalized(flow.party),flow.party] as [string,string]),
    ...bankTransactions.filter(transaction=>transaction.kind==="fournisseur"&&transaction.companyId===supplierBankCompanyId).map(transaction=>[normalized(transaction.party),transaction.party] as [string,string]),
    ...supplierBanks.filter(bank=>bank.companyId===supplierBankCompanyId).map(bank=>[normalized(bank.supplierName),bank.supplierName] as [string,string]),
  ]).values()).sort((a,b)=>a.localeCompare(b,"fr"));
  const knownBankNames=Array.from(new Map([
    ...banks.map(bank=>[normalized(bank.name),bank.name] as [string,string]),
    ...supplierBanks.map(bank=>[normalized(bank.bankName),bank.bankName] as [string,string]),
  ]).values()).sort((a,b)=>a.localeCompare(b,"fr"));
  const selectedTemplate = templates.find(template=>template.id===selectedTemplateId);
  const chartData = useMemo(()=>{
    const today=new Date(); today.setHours(0,0,0,0); const day=86400000; const slotCount=7;
    const periods=Array.from({length:slotCount},(_,index)=>{
      let start:Date; let end:Date; let label:string;
      if(chartPeriod==="day") { start=new Date(today.getTime()+index*day); end=new Date(start.getTime()+day); label=start.toLocaleDateString("fr-FR",{day:"2-digit",month:"short"}); }
      else if(chartPeriod==="week") { start=new Date(today.getTime()+index*7*day); end=new Date(start.getTime()+7*day); const last=new Date(end.getTime()-day); label=`${start.toLocaleDateString("fr-FR",{day:"2-digit",month:"short"})}–${last.toLocaleDateString("fr-FR",{day:"2-digit",month:"short"})}`; }
      else { start=new Date(today.getFullYear(),today.getMonth()+index,1); end=new Date(today.getFullYear(),today.getMonth()+index+1,1); label=start.toLocaleDateString("fr-FR",{month:"short",year:"2-digit"}); }
      return {start,end,label};
    });
    const horizon=chartPeriod==="day"?"Après 7 jours":chartPeriod==="week"?"Après 7 sem.":"Après 7 mois";
    const buckets=[{label:"Solde réel",incoming:0,outgoing:0,balance:bankTotal},{label:"Échus",incoming:0,outgoing:0,balance:0},...periods.map(period=>({label:period.label,incoming:0,outgoing:0,balance:0})),{label:horizon,incoming:0,outgoing:0,balance:0}];
    scopedFlows.filter(flow=>!flow.excluded).forEach(flow=>{
      const printedOrder=flow.kind==="fournisseur"?latestPrintedOrdersByFlow.get(flow.id):undefined;
      const effectiveDate=printedOrder?.orderDate||flow.plannedDate;
      const date=new Date(`${effectiveDate}T00:00:00`); const periodIndex=periods.findIndex(period=>date>=period.start&&date<period.end);
      const index=date<today?1:periodIndex>=0?periodIndex+2:buckets.length-1; const amount=Number(flow.amount);
      if(flow.kind==="client") buckets[index].incoming+=amount; else buckets[index].outgoing+=amount;
    });
    let balance=bankTotal; buckets.slice(1).forEach(bucket=>{balance+=bucket.incoming-bucket.outgoing;bucket.balance=balance;});
    const maxFlow=Math.max(1,...buckets.flatMap(bucket=>[Math.abs(bucket.incoming),Math.abs(bucket.outgoing)]));
    const balances=buckets.map(bucket=>bucket.balance); const minBalance=Math.min(...balances); const maxBalance=Math.max(...balances); const range=Math.max(1,maxBalance-minBalance);
    return buckets.map(bucket=>({...bucket,inHeight:bucket.incoming?Math.max(2,Math.abs(bucket.incoming)/maxFlow*88):0,outHeight:bucket.outgoing?Math.max(2,Math.abs(bucket.outgoing)/maxFlow*88):0,balanceY:8+(maxBalance-bucket.balance)/range*72}));
  },[scopedFlows,bankTotal,chartPeriod,latestPrintedOrdersByFlow]);
  const intelligentPlan = useMemo(()=>{
    const horizon=6;
    const today=new Date(); today.setHours(0,0,0,0);
    const periods=Array.from({length:horizon},(_,index)=>{
      const start=new Date(today.getFullYear(),today.getMonth()+index,1); const end=new Date(today.getFullYear(),today.getMonth()+index+1,1);
      const monthKey=`${start.getFullYear()}-${String(start.getMonth()+1).padStart(2,"0")}`;
      return {start,end,monthKey,label:start.toLocaleDateString("fr-FR",{month:"long",year:"numeric"})};
    });
    const clientLines=scopedFlows.filter(flow=>flow.kind==="client"&&!flow.excluded);
    const supplierLines=scopedFlows.filter(flow=>flow.kind==="fournisseur"&&!flow.excluded);
    const historicalReceipts=bankTransactions.filter(transaction=>transaction.kind==="client"&&!transaction.cancelledAt&&(companyFilter==="all"||transaction.companyId===companyFilter));
    let balance=bankTotal;
    const rows=periods.map((period,index)=>{
      const isCurrentMonth=index===0;
      const setting=smartMonthSettings[period.monthKey]??{target:0,reservePercent:15,disbursementPercent:65}; const configuredTarget=Math.max(0,Number(setting.target)||0); const configuredReserveRate=Math.max(0,Math.min(100,Number(setting.reservePercent)||0))/100; const configuredDisbursementRate=Math.max(0,Math.min(100,Number(setting.disbursementPercent??65)||0))/100;
      const inPeriod=(flow:Flow)=>{const date=new Date(`${flow.plannedDate}T00:00:00`);return index===0?date<period.end:date>=period.start&&date<period.end;};
      const clientMonth=clientLines.filter(inPeriod); const supplierMonth=supplierLines.filter(inPeriod);
      const plannedReceipts=moneyValue(clientMonth.reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0));
      const monthlyTarget=isCurrentMonth?plannedReceipts:configuredTarget; const reserveRate=isCurrentMonth?0:configuredReserveRate; const disbursementRate=isCurrentMonth?0:configuredDisbursementRate;
      const receiptForecast=isCurrentMonth?0:moneyValue(Math.max(0,monthlyTarget-plannedReceipts));
      const receipts=moneyValue(plannedReceipts+receiptForecast);
      const manualReceipts=moneyValue(clientMonth.filter(flow=>flow.isForecast).reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0));
      const scheduledSuppliers=moneyValue(supplierMonth.reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0));
      const manualSuppliers=moneyValue(supplierMonth.filter(flow=>flow.isForecast).reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0));
      const disbursementBudget=isCurrentMonth?scheduledSuppliers:moneyValue(receipts*disbursementRate);
      const complement=isCurrentMonth?0:moneyValue(Math.max(0,disbursementBudget-scheduledSuppliers));
      const supplierPayments=moneyValue(scheduledSuppliers+complement);
      const disbursementOverrun=isCurrentMonth?0:moneyValue(Math.max(0,scheduledSuppliers-disbursementBudget));
      const reserve=isCurrentMonth?0:moneyValue(monthlyTarget*reserveRate); const opening=balance;
      balance=moneyValue(opening+receipts-reserve-supplierPayments);
      return {...period,isCurrentMonth,monthlyTarget,reserveRate,disbursementRate,opening,plannedReceipts,receiptForecast,receipts,manualReceipts,scheduledSuppliers,manualSuppliers,disbursementBudget,complement,supplierPayments,disbursementOverrun,reserve,balance,fundingNeed:Math.max(0,-balance),supplierLineCount:supplierMonth.length};
    });
    const reserveTotal=rows.reduce((sum,row)=>sum+row.reserve,0);
    const totalComplements=rows.reduce((sum,row)=>sum+row.complement,0);
    const totalSupplierPayments=rows.reduce((sum,row)=>sum+row.supplierPayments,0);
    const totalReceipts=rows.reduce((sum,row)=>sum+row.receipts,0);
    const totalReceiptForecast=rows.reduce((sum,row)=>sum+row.receiptForecast,0);
    const totalDisbursementOverrun=rows.reduce((sum,row)=>sum+row.disbursementOverrun,0);
    const manualForecastCount=[...clientLines,...supplierLines].filter(flow=>flow.isForecast).length;
    const confidence=Math.min(94,55+Math.min(20,clientLines.length*2)+Math.min(12,supplierLines.length)+Math.min(7,historicalReceipts.length));
    const maxBar=Math.max(1,...rows.flatMap(row=>[row.receipts,row.reserve,row.supplierPayments]));
    const balances=rows.map(row=>row.balance); const minBalance=Math.min(...balances); const maxBalance=Math.max(...balances); const balanceRange=Math.max(1,maxBalance-minBalance);
    return {
      rows:rows.map(row=>({...row,receiptsHeight:row.receipts/maxBar*88,plannedReceiptsShare:row.receipts?row.plannedReceipts/row.receipts*100:0,receiptForecastShare:row.receipts?row.receiptForecast/row.receipts*100:0,reserveHeight:row.reserve/maxBar*88,supplierHeight:row.supplierPayments/maxBar*88,plannedSupplierShare:row.supplierPayments?row.scheduledSuppliers/row.supplierPayments*100:0,supplierForecastShare:row.supplierPayments?row.complement/row.supplierPayments*100:0,balanceY:8+(maxBalance-row.balance)/balanceRange*72})),
      reserveTotal,totalComplements,totalSupplierPayments,totalReceipts,totalReceiptForecast,totalDisbursementOverrun,manualForecastCount,confidence,endingBalance:rows.at(-1)?.balance??bankTotal,lowestBalance:minBalance,
    };
  },[scopedFlows,bankTotal,bankTransactions,companyFilter,smartMonthSettings]);
  const smartSupplierPlan = useMemo(()=>{
    const day=86_400_000; const today=new Date(); today.setHours(0,0,0,0);
    const supplierInvoices=scopedFlows.filter(flow=>flow.kind==="fournisseur"&&!flow.excluded&&!flow.isForecast&&!flow.blockedSupplier);
    const blockedInvoices=scopedFlows.filter(flow=>flow.kind==="fournisseur"&&!flow.excluded&&!flow.isForecast&&flow.blockedSupplier);
    const policyMap=new Map(supplierPolicies.map(policy=>[`${policy.companyId}::${normalized(policy.supplierKey||policy.supplierName)}`,policy]));
    const policyFor=(flow:Flow)=>policyMap.get(`${flow.companyId}::${normalized(flow.party)}`);
    const remaining=new Map(supplierInvoices.map(flow=>[flow.id,moneyValue(Math.abs(Number(flow.amount)))]));
    const allocations:SmartSupplierAllocation[]=[]; let projectedCash=bankTotal;
    const addAllocation=(flow:Flow,row:(typeof intelligentPlan.rows)[number],amount:number)=>{
      const policy=policyFor(flow); const previous=allocations.filter(item=>item.flowId===flow.id);
      if(policy?.paymentDeadline&&row.start.getTime()>new Date(`${policy.paymentDeadline}T23:59:59`).getTime())return 0;
      if(policy&&!policy.splittable&&previous.length)return 0;
      if(policy?.splittable&&previous.length){const [firstYear,firstMonth]=previous[0].monthKey.split("-").map(Number);const [rowYear,rowMonth]=row.monthKey.split("-").map(Number);if((rowYear-firstYear)*12+rowMonth-firstMonth>=policy.maxInstallmentMonths)return 0;}
      const before=remaining.get(flow.id)||0; const invoiceAmount=moneyValue(Math.abs(Number(flow.amount))); const existingMonth=allocations.find(item=>item.flowId===flow.id&&item.monthKey===row.monthKey); let permittedAmount=Math.max(0,amount);
      if(policy?.splittable&&policy.maxInstallmentMonths>1){const firstMonthKey=previous[0]?.monthKey||row.monthKey;const [firstYear,firstMonth]=firstMonthKey.split("-").map(Number);const [rowYear,rowMonth]=row.monthKey.split("-").map(Number);const monthOffset=(rowYear-firstYear)*12+rowMonth-firstMonth;const monthlyTarget=moneyValue(invoiceAmount/policy.maxInstallmentMonths);const monthAlready=existingMonth?.proposedAmount||0;const monthCapacity=monthOffset>=policy.maxInstallmentMonths-1?before:Math.max(0,moneyValue(monthlyTarget-monthAlready));permittedAmount=Math.min(permittedAmount,monthCapacity);}
      const paid=moneyValue(Math.min(before,permittedAmount)); if(paid<=0)return 0;
      if(policy&&!policy.splittable&&paid+0.01<before)return 0;
      const after=moneyValue(before-paid); remaining.set(flow.id,after);
      const due=new Date(`${flow.dueDate}T00:00:00`); const overdueDays=Math.max(0,Math.floor((today.getTime()-due.getTime())/day));
      const score=Math.round(overdueDays*2+Math.min(80,Math.abs(Number(flow.amount))/10_000)+(flow.status==="en retard"?30:0)+(policy?Math.max(0,1000-policy.priorityRank):0));
      const priority:SmartSupplierReportRow["priority"]=policy&&policy.priorityRank<=3?"Critique":policy&&policy.priorityRank<=10?"Haute":overdueDays>=60?"Critique":overdueDays>=15?"Haute":"Normale";
      const earliest=new Date(today.getTime()+2*day); const preferred=new Date(row.start.getFullYear(),row.start.getMonth(),Math.min(25,5+allocations.filter(item=>item.monthKey===row.monthKey).length%3*10)); const latest=new Date(row.end.getTime()-day); const deadline=policy?.paymentDeadline?new Date(`${policy.paymentDeadline}T23:59:59`):null; const latestAllowed=deadline&&deadline<latest?deadline:latest; const paymentDate=new Date(Math.max(today.getTime(),Math.min(latestAllowed.getTime(),Math.max(earliest.getTime(),preferred.getTime())))).toISOString().slice(0,10);
      const existing=existingMonth;
      if(existing){existing.proposedAmount=moneyValue(existing.proposedAmount+paid);existing.remainingAfter=after;existing.isFraction=after>0;return paid;}
      const policyReason=policy?`priorité ${policy.priorityRank} · ${policy.splittable?`fractionnable sur ${policy.maxInstallmentMonths} mois`:"paiement intégral"}${policy.paymentDeadline?` · limite ${new Date(`${policy.paymentDeadline}T00:00:00`).toLocaleDateString("fr-FR")}`:""}`:"simulation libre";
      allocations.push({flowId:flow.id,companyId:flow.companyId,supplier:flow.beneficiaryName||flow.party,reference:flow.reference,dueDate:flow.dueDate,invoiceAmount:Math.abs(Number(flow.amount)),proposedAmount:paid,remainingAfter:after,paymentDate,monthKey:row.monthKey,monthLabel:row.label,overdueDays,priority,score,reason:`${overdueDays>0?`${overdueDays} j de retard`:"Échéance proche"} · ${policyReason}`,isFraction:after>0,policyRank:policy?.priorityRank,policyDeadline:policy?.paymentDeadline}); return paid;
    };
    intelligentPlan.rows.forEach((row,index)=>{
      const setting=smartMonthSettings[row.monthKey]??{target:0,reservePercent:15,disbursementPercent:65}; const rate=Math.max(0,Math.min(100,Number(setting.disbursementPercent)||65))/100;
      const safetyFloor=scopedBanks.reduce((sum,bank)=>sum+Number(bank.minimumBalance||0),0)+Math.max(0,row.reserve||0);
      const cashAfterReceipts=moneyValue(projectedCash+row.receipts); const liquidCapacity=Math.max(0,cashAfterReceipts-safetyFloor);
      const policyCapacity=moneyValue(Math.max(0,row.receipts*rate+Math.max(0,projectedCash-safetyFloor)*(index===0?.35:.15)));
      let available=moneyValue(Math.min(liquidCapacity,policyCapacity));
      const dueQueue=supplierInvoices.filter(flow=>(remaining.get(flow.id)||0)>0&&new Date(`${flow.dueDate}T00:00:00`)<row.end).sort((a,b)=>{const aPolicy=policyFor(a),bPolicy=policyFor(b);if(aPolicy&&bPolicy&&aPolicy.priorityRank!==bPolicy.priorityRank)return aPolicy.priorityRank-bPolicy.priorityRank;if(aPolicy&&!bPolicy)return -1;if(!aPolicy&&bPolicy)return 1;const deadlineOrder=String(aPolicy?.paymentDeadline||"9999-12-31").localeCompare(String(bPolicy?.paymentDeadline||"9999-12-31"));return deadlineOrder||a.dueDate.localeCompare(b.dueDate)||Math.abs(Number(b.amount))-Math.abs(Number(a.amount));});
      const firstBySupplier=[...new Map(dueQueue.map(flow=>[(flow.beneficiaryName||flow.party).trim().toLowerCase(),flow])).values()];
      const fairnessPool=available*.35; const fairShare=firstBySupplier.length?fairnessPool/firstBySupplier.length:0;
      firstBySupplier.forEach(flow=>{if(available<=0)return;const remainingAmount=remaining.get(flow.id)||0;const amount=Math.min(available,remainingAmount,Math.max(1_000,fairShare));available=moneyValue(available-addAllocation(flow,row,amount));});
      dueQueue.forEach(flow=>{if(available<=0)return;available=moneyValue(available-addAllocation(flow,row,Math.min(available,remaining.get(flow.id)||0)));});
      const monthPaid=allocations.filter(item=>item.monthKey===row.monthKey).reduce((sum,item)=>sum+item.proposedAmount,0); projectedCash=moneyValue(cashAfterReceipts-monthPaid);
    });
    const byInvoice=new Map<number,SmartSupplierAllocation[]>(); allocations.forEach(item=>byInvoice.set(item.flowId,[...(byInvoice.get(item.flowId)||[]),item]));
    allocations.forEach(item=>{const count=byInvoice.get(item.flowId)?.length||1;item.isFraction=count>1||item.proposedAmount<item.invoiceAmount;});
    const invoiceTotal=moneyValue(supplierInvoices.reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0)); const allocatedTotal=moneyValue(allocations.reduce((sum,item)=>sum+item.proposedAmount,0));
    return {allocations,byInvoice,invoiceTotal,allocatedTotal,unallocatedTotal:moneyValue(Math.max(0,invoiceTotal-allocatedTotal)),supplierCount:new Set(supplierInvoices.map(flow=>(flow.beneficiaryName||flow.party).trim().toLowerCase())).size,servedSupplierCount:new Set(allocations.map(item=>item.supplier.trim().toLowerCase())).size,fractionCount:[...byInvoice.values()].filter(items=>items.length>1||items[0]?.proposedAmount<items[0]?.invoiceAmount).length,blockedTotal:moneyValue(blockedInvoices.reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0)),projectedCash};
  },[scopedFlows,scopedBanks,bankTotal,intelligentPlan,smartMonthSettings,supplierPolicies]);
  const currentMonthLabel=new Date().toLocaleDateString("fr-FR",{month:"long",year:"numeric"});
  const supplierPolicyCandidates=useMemo(()=>Array.from(new Map(scopedFlows.filter(flow=>flow.kind==="fournisseur"&&!flow.isForecast).map(flow=>[`${flow.companyId}::${normalized(flow.party)}`,{companyId:flow.companyId,supplierName:flow.party}])).values()).sort((a,b)=>{const aPolicy=supplierPolicies.find(policy=>policy.companyId===a.companyId&&policy.supplierKey===normalized(a.supplierName));const bPolicy=supplierPolicies.find(policy=>policy.companyId===b.companyId&&policy.supplierKey===normalized(b.supplierName));if(aPolicy&&bPolicy)return aPolicy.priorityRank-bPolicy.priorityRank;if(aPolicy)return -1;if(bPolicy)return 1;return a.supplierName.localeCompare(b.supplierName,"fr");}),[scopedFlows,supplierPolicies]);
  const supplierPolicyFor=(companyId:string,supplierName:string)=>supplierPolicies.find(policy=>policy.companyId===companyId&&policy.supplierKey===normalized(supplierName));
  const smartSupplierReportRows=useMemo<SmartSupplierReportRow[]>(()=>{
    const day=86_400_000,today=new Date();today.setHours(0,0,0,0);
    // Le rapport est exhaustif pour les factures fournisseurs actives et consolidé :
    // il n'hérite pas du filtre LOTREX/CTL, mais exclut strictement les lignes ignorées.
    return flows.filter(flow=>flow.kind==="fournisseur"&&!flow.isForecast&&!flow.excluded).map((flow,rowIndex)=>{
      const allocatedItems=smartSupplierPlan.byInvoice.get(flow.id)||[],invoiceAmount=moneyValue(Math.abs(Number(flow.amount))),planned=moneyValue(allocatedItems.reduce((sum,item)=>sum+item.proposedAmount,0)),remaining=moneyValue(Math.max(0,invoiceAmount-planned));
      const supplier=flow.beneficiaryName||flow.party,policy=supplierPolicies.find(item=>item.companyId===flow.companyId&&item.supplierKey===normalized(flow.party));
      const due=new Date(`${flow.dueDate}T00:00:00`),overdueDays=Math.max(0,Math.floor((today.getTime()-due.getTime())/day));
      const score=Math.round(overdueDays*2+Math.min(80,invoiceAmount/10_000)+(flow.status==="en retard"?30:0)+(policy?Math.max(0,1000-policy.priorityRank):0));
      const priority:SmartSupplierReportRow["priority"]=policy&&policy.priorityRank<=3?"Critique":policy&&policy.priorityRank<=10?"Haute":overdueDays>=60?"Critique":overdueDays>=15?"Haute":"Normale";
      const installmentCount=policy?.splittable?Math.max(1,policy.maxInstallmentMonths):1;
      const pendingInstallmentCount=remaining>.01?Math.max(1,installmentCount-allocatedItems.length):0;
      const indicativeItems=Array.from({length:pendingInstallmentCount},(_,installmentIndex)=>{
        const lastAllocatedDate=allocatedItems.at(-1)?.paymentDate;
        const firstMonth=lastAllocatedDate?new Date(`${lastAllocatedDate}T00:00:00`):new Date(today.getFullYear(),today.getMonth()+1+Math.floor(rowIndex/24),1);
        const preferred=new Date(firstMonth.getFullYear(),firstMonth.getMonth()+(lastAllocatedDate?1:0)+installmentIndex,5+(rowIndex%3)*10);
        const deadline=policy?.paymentDeadline?new Date(`${policy.paymentDeadline}T23:59:59`):null;
        const scheduled=deadline&&deadline>today&&preferred>deadline?deadline:preferred;
        const paidBefore=moneyValue(remaining/pendingInstallmentCount*installmentIndex);
        const proposedAmount=installmentIndex===pendingInstallmentCount-1?moneyValue(remaining-paidBefore):moneyValue(remaining/pendingInstallmentCount);
        const paymentDate=scheduled.toISOString().slice(0,10);
        return {flowId:flow.id,companyId:flow.companyId,supplier,reference:flow.reference,dueDate:flow.dueDate,invoiceAmount,proposedAmount,remainingAfter:moneyValue(Math.max(0,remaining-paidBefore-proposedAmount)),paymentDate,monthKey:paymentDate.slice(0,7),monthLabel:scheduled.toLocaleDateString("fr-FR",{month:"long",year:"numeric"}),overdueDays,priority,score,reason:"Échéancier indicatif à financer",isFraction:installmentCount>1||allocatedItems.length>0,policyRank:policy?.priorityRank,policyDeadline:policy?.paymentDeadline};
      });
      const reportItems=[...allocatedItems,...indicativeItems];
      const status=flow.blockedSupplier?"Bloquée · échéancier indicatif":remaining<=.01?"Financée":planned>0?"Partiellement financée":"À financer";
      return {flow,supplier,invoiceAmount,planned,remaining,overdueDays,priority,score,policy,items:reportItems,status,fundingObjective:remaining};
    }).sort((a,b)=>{const rankA=a.policy?.priorityRank??9999,rankB=b.policy?.priorityRank??9999;if(rankA!==rankB)return rankA-rankB;const priorityOrder={Critique:0,Haute:1,Normale:2};if(priorityOrder[a.priority]!==priorityOrder[b.priority])return priorityOrder[a.priority]-priorityOrder[b.priority];return b.overdueDays-a.overdueDays||a.supplier.localeCompare(b.supplier,"fr")||a.flow.reference.localeCompare(b.flow.reference,"fr");});
  },[flows,smartSupplierPlan,supplierPolicies]);
  const smartSupplierInvoiceTotal=moneyValue(smartSupplierReportRows.reduce((sum,row)=>sum+row.invoiceAmount,0));
  const smartSupplierPlannedTotal=moneyValue(smartSupplierReportRows.reduce((sum,row)=>sum+row.planned,0));
  const smartSupplierUnpaidTotal=moneyValue(smartSupplierReportRows.reduce((sum,row)=>sum+row.remaining,0));
  const smartSupplierSettlementRate=smartSupplierInvoiceTotal?Math.min(100,smartSupplierPlannedTotal/smartSupplierInvoiceTotal*100):0;
  const smartSupplierFiscalSummary=useMemo(()=>{
    const fiscalMap=new Map<string,{exercise:string;invoiceCount:number;invoiceTotal:number;plannedTotal:number;unpaidTotal:number}>();
    smartSupplierReportRows.forEach(row=>{
      const exercise=String(row.flow.invoiceDate||row.flow.dueDate||"Non daté").slice(0,4);
      const current=fiscalMap.get(exercise)||{exercise,invoiceCount:0,invoiceTotal:0,plannedTotal:0,unpaidTotal:0};
      current.invoiceCount+=1;current.invoiceTotal=moneyValue(current.invoiceTotal+row.invoiceAmount);current.plannedTotal=moneyValue(current.plannedTotal+row.planned);current.unpaidTotal=moneyValue(current.unpaidTotal+row.remaining);fiscalMap.set(exercise,current);
    });
    return [...fiscalMap.values()].sort((a,b)=>a.exercise.localeCompare(b.exercise)).map(row=>({...row,settlementRate:row.invoiceTotal?Math.min(100,row.plannedTotal/row.invoiceTotal*100):0}));
  },[smartSupplierReportRows]);
  const smartPayerNames=Array.from(new Set(smartSupplierReportRows.map(row=>companyName(row.flow.companyId)))).join(" · ")||companies.map(company=>company.shortName).join(" · ");
  const smartSupplierFundingGap=moneyValue(smartSupplierReportRows.reduce((sum,row)=>sum+row.fundingObjective,0));
  const futureScenarioRows=intelligentPlan.rows.filter(row=>!row.isCurrentMonth);
  const averageDisbursementRate=futureScenarioRows.length?futureScenarioRows.reduce((sum,row)=>sum+row.disbursementRate,0)/futureScenarioRows.length:.65;
  const scenarioCashObjective=moneyValue(intelligentPlan.totalReceiptForecast);
  const supplierGapCashObjective=moneyValue(smartSupplierFundingGap>0?smartSupplierFundingGap/Math.max(.01,averageDisbursementRate):0);
  // Un échéancier fournisseur n'est pas un financement acquis. Le besoin d'encaissement
  // du scénario reste donc un objectif commercial/recouvrement, même si chaque facture
  // a déjà reçu une date de règlement proposée.
  const additionalCommercialObjective=moneyValue(Math.max(scenarioCashObjective,supplierGapCashObjective));
  const decisionSupportAnalysis=useMemo(()=>{
    const overdueClientFlows=scopedFlows.filter(flow=>flow.kind==="client"&&!flow.excluded&&!flow.isForecast&&flow.status==="en retard").sort((a,b)=>Math.abs(Number(b.amount))-Math.abs(Number(a.amount)));
    const overdueClientPotential=moneyValue(overdueClientFlows.reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0));
    const collectionObjective=moneyValue(Math.min(overdueClientPotential,additionalCommercialObjective*.6));
    const salesObjective=moneyValue(Math.max(0,additionalCommercialObjective-collectionObjective));
    const criticalRows=smartSupplierReportRows.filter(row=>row.priority==="Critique");
    const criticalExposure=moneyValue(criticalRows.reduce((sum,row)=>sum+row.invoiceAmount,0));
    const configuredRows=smartSupplierReportRows.filter(row=>row.policy);
    const lowestRow=intelligentPlan.rows.reduce((lowest,row)=>row.balance<lowest.balance?row:lowest,intelligentPlan.rows[0]);
    const projectedGain=moneyValue(Math.max(0,intelligentPlan.endingBalance));
    const liquidityGain=moneyValue(projectedGain*.5);
    const supplierAccelerationGain=moneyValue(projectedGain*.3);
    const investmentGain=moneyValue(projectedGain-liquidityGain-supplierAccelerationGain);
    const prioritySuppliers=Array.from(new Set(criticalRows.slice(0,4).map(row=>row.supplier))).join(", ")||"aucun fournisseur critique";
    const recoveryAccounts=Array.from(new Set(overdueClientFlows.slice(0,4).map(flow=>flow.party))).join(", ")||"aucun compte client échu disponible";
    const diagnosis=`Le plan propose un calendrier pour ${smartSupplierReportRows.length} facture(s) fournisseur(s), mais ce calendrier doit encore être financé par des encaissements réels. L'effort complémentaire à produire sur l'horizon est de ${money(additionalCommercialObjective)}, dont ${money(collectionObjective)} par recouvrement et ${money(salesObjective)} par ventes nouvelles encaissées. Le point bas de trésorerie est estimé à ${money(intelligentPlan.lowestBalance)}${lowestRow?` en ${lowestRow.label}`:""}${smartSupplierFundingGap>0?`; ${money(smartSupplierFundingGap)} restent en outre non financés dans l'échéancier fournisseur`:""}.`;
    const supplierStrategy=`Traiter d'abord les factures paramétrées par rang, puis les ${criticalRows.length} facture(s) critique(s) représentant ${money(criticalExposure)}. Les fournisseurs à sécuriser en priorité sont : ${prioritySuppliers}. Conserver le fractionnement uniquement lorsqu'il respecte la durée maximale et la date limite convenues.`;
    const commercialStrategy=`Recouvrement : sécuriser ${money(collectionObjective)} en promesses datées et encaissements, ciblés en priorité sur ${recoveryAccounts}. Commercial : produire ${money(salesObjective)} de ventes nouvelles encaissées. L'objectif total de ${money(additionalCommercialObjective)} doit être ventilé par mois et par responsable ; une facture client ou une promesse non encaissée ne finance pas encore une facture fournisseur.`;
    const reserveStrategy=`La réserve cumulée de ${money(intelligentPlan.reserveTotal)} doit être isolée au fil des encaissements sur un compte bancaire ou sous-compte intitulé « Réserve de trésorerie », avec rapprochement mensuel et double validation avant toute reprise. ${projectedGain>0?`Le gain disponible après fournisseurs et réserve est estimé à ${money(projectedGain)}. Proposition d'affectation : ${money(liquidityGain)} en coussin de liquidité (50 %), ${money(supplierAccelerationGain)} en règlement anticipé ou désendettement (30 %) et ${money(investmentGain)} en investissement validé (20 %).`:`Aucun gain distribuable n'est reconnu tant que le déficit fournisseur et le niveau de réserve ne sont pas couverts.`}`;
    const actions=[
      `Finance : figer la liste priorisée et valider les politiques des fournisseurs (${configuredRows.length} facture(s) actuellement rattachée(s) à une politique).`,
      `Recouvrement : transformer l'objectif de ${money(collectionObjective)} en promesses datées, responsables nommés et suivi hebdomadaire des encaissements.`,
      `Commercial : convertir ${money(salesObjective)} en objectif de ventes encaissées, ventilé par commercial et par mois du scénario.`,
      `Trésorerie : transférer la réserve dès réception des fonds, puis libérer les règlements fournisseurs selon l'échéancier et les validations.`,
      `Direction : revoir chaque semaine les écarts entre encaissements réels, objectifs, réserve constituée et factures effectivement réglées.`
    ];
    return {diagnosis,supplierStrategy,commercialStrategy,reserveStrategy,actions,overdueClientPotential,collectionObjective,salesObjective,criticalExposure,projectedGain,liquidityGain,supplierAccelerationGain,investmentGain};
  },[scopedFlows,smartSupplierReportRows,smartSupplierFundingGap,additionalCommercialObjective,intelligentPlan]);
  const smartScenarioDirty=JSON.stringify(smartMonthDrafts)!==JSON.stringify(smartMonthSettings);
  const planningData = useMemo(()=>{
    const today=new Date(); today.setHours(0,0,0,0); const day=86400000;
    const count=planningScale==="day"?14:planningScale==="week"?13:12;
    const periods=Array.from({length:count},(_,index)=>{
      let start:Date; let end:Date; let label:string;
      if(planningScale==="day") { start=new Date(today.getTime()+index*day); end=new Date(start.getTime()+day); label=start.toLocaleDateString("fr-FR",{day:"2-digit",month:"short"}); }
      else if(planningScale==="week") { start=new Date(today.getTime()+index*7*day); end=new Date(start.getTime()+7*day); const last=new Date(end.getTime()-day); label=`${start.toLocaleDateString("fr-FR",{day:"2-digit",month:"short"})}–${last.toLocaleDateString("fr-FR",{day:"2-digit",month:"short"})}`; }
      else { start=new Date(today.getFullYear(),today.getMonth()+index,1); end=new Date(today.getFullYear(),today.getMonth()+index+1,1); label=start.toLocaleDateString("fr-FR",{month:"short",year:"2-digit"}); }
      return {start,end,label:index===0?`Échus + ${label}`:label};
    });
    let opening=bankTotal;
    return periods.map((period,index)=>{
      const items=scopedFlows.filter(flow=>!flow.excluded&&new Date(`${flow.plannedDate}T00:00:00`)<period.end&&(index===0||new Date(`${flow.plannedDate}T00:00:00`)>=period.start));
      const clientInvoices=items.filter(flow=>flow.kind==="client"&&!flow.isForecast).reduce((sum,flow)=>sum+Number(flow.amount),0);
      const clientForecasts=items.filter(flow=>flow.kind==="client"&&flow.isForecast).reduce((sum,flow)=>sum+Number(flow.amount),0);
      const supplierInvoices=items.filter(flow=>flow.kind==="fournisseur"&&!flow.isForecast).reduce((sum,flow)=>sum+Number(flow.amount),0);
      const supplierForecasts=items.filter(flow=>flow.kind==="fournisseur"&&flow.isForecast).reduce((sum,flow)=>sum+Number(flow.amount),0);
      const incoming=clientInvoices+clientForecasts; const outgoing=supplierInvoices+supplierForecasts; const balance=opening+incoming-outgoing;
      const result={...period,items,opening,clientInvoices,clientForecasts,supplierInvoices,supplierForecasts,incoming,outgoing,net:incoming-outgoing,balance}; opening=balance; return result;
    });
  },[scopedFlows,bankTotal,planningScale]);
  const minimumCash=scopedBanks.reduce((sum,bank)=>sum+Number(bank.minimumBalance||0),0);
  const selectedPlanningPeriod=planningSelection?planningData[planningSelection.index]:null;
  const selectedPlanningItems=selectedPlanningPeriod?.items.filter(flow=>planningSelection?.kind==="all"||(flow.kind===planningSelection?.kind&&(planningSelection.forecast===undefined||Boolean(flow.isForecast)===planningSelection.forecast)))??[];
  useEffect(()=>{ setSchedulePage(0); },[filter,companyFilter,partySearch,scheduleDateFrom,scheduleDateTo,scheduleAmountMin,scheduleAmountMax,scheduleStatusFilter,schedulePaymentFilter,scheduleIncludedFilter,scheduleDateSort,scheduleSortField]);
  useEffect(()=>{try{const saved=window.localStorage.getItem("moventra-treasury-smart-month-settings");if(saved){const parsed=JSON.parse(saved) as Record<string,Partial<SmartMonthSetting>>;const normalized=Object.fromEntries(Object.entries(parsed).map(([key,value])=>[key,{target:Number(value.target)||0,reservePercent:Number(value.reservePercent)||0,disbursementPercent:Number(value.disbursementPercent??65)}])) as Record<string,SmartMonthSetting>;setSmartMonthSettings(current=>({...current,...normalized}));setSmartMonthDrafts(current=>({...current,...normalized}));setSmartSavedSettings(current=>({...current,...normalized}));}}catch{/* Les anciens paramètres locaux invalides sont ignorés. */}},[]);
  useEffect(()=>{ if(companyFilter!=="all")setSupplierBankCompanyId(companyFilter); },[companyFilter]);
  useEffect(()=>{try{const stored=window.localStorage.getItem("moventra-treasury-schedule-filters");if(stored){const parsed=JSON.parse(stored);if(Array.isArray(parsed)){const migrated=parsed.map(item=>normalizeScheduleFilterPreset(item));setSavedScheduleFilters(migrated);window.localStorage.setItem("moventra-treasury-schedule-filters",JSON.stringify(migrated));}}}catch{/* Modèles locaux invalides ignorés. */}},[]);
  useEffect(()=>{ if(schedulePage>=schedulePageCount)setSchedulePage(schedulePageCount-1); },[schedulePage,schedulePageCount]);

  function flash(message:string) { setToast(message); window.setTimeout(()=>setToast(""),2800); }
  function printTransferWorkflow() {
    const printClass="printing-transfer-workflow";
    const cleanup=()=>document.body.classList.remove(printClass);
    document.body.classList.add(printClass);
    window.addEventListener("afterprint",cleanup,{once:true});
    window.setTimeout(()=>window.print(),80);
    window.setTimeout(cleanup,1800);
  }
  function returnToTransferOrder() {
    if(!transferFlow)return;
    setSupplierBankCreationFromTransfer(false);
    setPanel("transfer");
  }
  function exitPanel() {
    if(panel==="receiptReserve")setReceiptReserve(null);
    if(panel==="editFlow")setEditingFlow(null);
    setSelectedTransferIds([]);
    setTransferFlow(null);
    setTransferReceptionBeneficiaryName("");
    setShowAllSupplierBanks(false);
    setSupplierBankCreationFromTransfer(false);
    setPanel(null);
    setView("overview");
  }
  function returnToMoventra() {
    try {
      window.sessionStorage.setItem("moventra.active-view","overview");
      window.sessionStorage.setItem("moventra.open-views",JSON.stringify(["overview"]));
    } catch { /* Le stockage de session peut être désactivé par le navigateur. */ }
    window.location.replace("/?module=maintenance&from=treasury");
  }
  function persistScheduleFilters(items:ScheduleFilterPreset[]) { setSavedScheduleFilters(items);window.localStorage.setItem("moventra-treasury-schedule-filters",JSON.stringify(items)); }
  function toggleScheduleSort(field:ScheduleSortField) { if(scheduleSortField===field)setScheduleDateSort(current=>current==="oldest"?"newest":"oldest");else{setScheduleSortField(field);setScheduleDateSort("oldest")} }
  const scheduleSortIcon=(field:ScheduleSortField)=>scheduleSortField!==field?"↕":scheduleDateSort==="oldest"?"▲":"▼";
  function saveScheduleFilterPreset() {
    const suggested=selectedScheduleFilterId?savedScheduleFilters.find(item=>item.id===selectedScheduleFilterId)?.name||"":"";
    const name=window.prompt("Nom du modèle de filtre",suggested||"Mon filtre"); if(!name?.trim())return;
    const existing=savedScheduleFilters.find(item=>item.name.toLocaleLowerCase("fr")===name.trim().toLocaleLowerCase("fr"));
    const preset:ScheduleFilterPreset={id:existing?.id||`filter-${Date.now()}`,name:name.trim(),company:companyFilter,search:partySearch,dateFrom:scheduleDateFrom,dateTo:scheduleDateTo,amountMin:scheduleAmountMin,amountMax:scheduleAmountMax,type:filter,status:scheduleStatusFilter,payment:schedulePaymentFilter,included:scheduleIncludedFilter,sort:scheduleDateSort,sortField:scheduleSortField};
    const items=existing?savedScheduleFilters.map(item=>item.id===existing.id?preset:item):[...savedScheduleFilters,preset];persistScheduleFilters(items);setSelectedScheduleFilterId(preset.id);flash(existing?"Modèle de filtre mis à jour":"Modèle de filtre enregistré");
  }
  function applyScheduleFilterPreset(id:string) {
    setSelectedScheduleFilterId(id);const saved=savedScheduleFilters.find(item=>item.id===id);if(!saved)return;
    const preset=normalizeScheduleFilterPreset(saved); const validCompany=preset.company==="all"||companies.some(company=>company.id===preset.company)?preset.company:"all";
    setCompanyFilter(validCompany);setPartySearch(preset.search);setScheduleDateFrom(preset.dateFrom);setScheduleDateTo(preset.dateTo);setScheduleAmountMin(preset.amountMin);setScheduleAmountMax(preset.amountMax);setFilter(preset.type);setScheduleStatusFilter(preset.status);setSchedulePaymentFilter(preset.payment);setScheduleIncludedFilter(preset.included);setScheduleDateSort(preset.sort);setScheduleSortField(preset.sortField);setSchedulePage(0);
    const migrated=savedScheduleFilters.map(item=>item.id===id?preset:item);persistScheduleFilters(migrated);flash(`Filtre « ${preset.name} » appliqué · ${preset.included==="included"?"pris en compte":preset.included==="excluded"?"non pris en compte":"toutes les prises en compte"}`);
  }
  function deleteScheduleFilterPreset() {
    const preset=savedScheduleFilters.find(item=>item.id===selectedScheduleFilterId);if(!preset)return;
    if(!window.confirm(`Supprimer le modèle de filtre « ${preset.name} » ?`))return;persistScheduleFilters(savedScheduleFilters.filter(item=>item.id!==preset.id));setSelectedScheduleFilterId("");flash("Modèle de filtre supprimé");
  }
  function updateSmartMonthSetting(monthKey:string,field:keyof SmartMonthSetting,value:number) { setSmartMonthDrafts(current=>({...current,[monthKey]:{...(current[monthKey]??{target:0,reservePercent:15,disbursementPercent:65}),[field]:field==="target"?Math.max(0,value||0):Math.max(0,Math.min(100,value||0))}})); }
  function applySmartScenario() { if(!window.confirm("Valider ce scénario pour prévisualiser toutes les projections ?\n\nLe plan ne sera pas conservé avant votre confirmation explicite."))return;setSmartMonthSettings({...smartMonthDrafts});setSmartSettingsExpanded(false);setSmartScenarioPendingDecision(true);flash("Prévisualisation validée · Enregistrez le plan intelligent ou revenez au plan réel"); }
  function saveSmartScenario() { if(!window.confirm("Enregistrer ce plan intelligent comme scénario de référence ?\n\nLe plan réel reste inchangé tant que vous n’appliquez pas les règlements proposés."))return;window.localStorage.setItem("moventra-treasury-smart-month-settings",JSON.stringify(smartMonthSettings));setSmartSavedSettings({...smartMonthSettings});setSmartScenarioPendingDecision(false);flash("Plan intelligent enregistré comme scénario de référence"); }
  function cancelSmartScenario() { if(!window.confirm("Annuler cette prévisualisation et revenir au plan réel ?\n\nAucune échéance réelle ne sera modifiée."))return;setSmartMonthSettings({...smartSavedSettings});setSmartMonthDrafts({...smartSavedSettings});setSmartScenarioPendingDecision(false);setSmartSettingsExpanded(false);window.location.assign(`/treasury/plan-reel${embedded?"?embedded=1":""}`); }
  function changeDate(id:number,plannedDate:string) {
    const flow=flows.find(item=>item.id===id);
    if(!window.confirm(`Modifier la date planifiée${flow?` de « ${flow.party} · ${flow.reference} »`:""} vers le ${new Date(`${plannedDate}T00:00:00`).toLocaleDateString("fr-FR")} ?`)){setFlows(items=>[...items]);return;}
    setFlows(items=>items.map(item=>item.id===id?{...item,plannedDate,manualOverride:true,status:"planifié"}:item));
    flash("Date modifiée · Prévision recalculée");
    fetch("/api/treasury",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({id,plannedDate})}).catch(()=>undefined);
  }
  function flowLockReason(flow:Flow) {
    const approval=flow.kind==="fournisseur"?approvalForFlow(flow.id):undefined;
    const activeOrder=transferOrders.some(order=>order.cashFlowId===flow.id&&order.status!=="annulé");
    const hasExecution=bankTransactions.some(transaction=>transaction.cashFlowId===flow.id&&!transaction.cancelledAt);
    if(hasExecution||flow.reconciled||flow.settledAt)return "Réglée / exécutée";
    if(activeOrder)return "Ordre bancaire actif";
    if(approval?.status==="pending")return "En validation";
    if(approval?.status==="approved")return "Validée par la hiérarchie";
    return null;
  }
  function canEditFlow(flow:Flow) { return !flowLockReason(flow); }
  function canDeleteFlow(flow:Flow) { return !flowLockReason(flow); }
  async function deleteFlow(flow:Flow) {
    if(!canDeleteFlow(flow))return flash("Suppression impossible : ligne validée, imprimée ou exécutée");
    if(!window.confirm(flow.isForecast?`Supprimer définitivement la prévision « ${flow.party} · ${flow.reference} » ?`:`Supprimer définitivement la ligne « ${flow.party} · ${flow.reference} » du plan des règlements ?`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({id:flow.id})});
      const result=await response.json().catch(()=>({}));
      if(!response.ok)throw new Error(result.error||"Suppression impossible");
      setSelectedTransferIds(ids=>ids.filter(id=>id!==flow.id));
      await Promise.all([refresh(),refreshSettings()]);
      flash(flow.isForecast?"Prévision supprimée":"Ligne supprimée du plan des règlements");
    } catch(error) { flash(error instanceof Error?error.message:"Suppression impossible"); }
    finally { setBusy(false); }
  }
  function openFlowEditor(flow:Flow) {
    if(!canEditFlow(flow))return flash("Modification impossible : cette ligne est déjà validée par la hiérarchie");
    setEditingFlow(flow); setPanel("editFlow");
  }
  function isFlowEditorDirty(form:HTMLFormElement|null) {
    if(!form)return false;
    return Array.from(form.elements).some(element=>{
      if(element instanceof HTMLInputElement) {
        if(element.type==="submit"||element.type==="button"||element.type==="reset")return false;
        if(element.type==="checkbox"||element.type==="radio")return element.checked!==element.defaultChecked;
        return element.value!==element.defaultValue;
      }
      if(element instanceof HTMLTextAreaElement)return element.value!==element.defaultValue;
      if(element instanceof HTMLSelectElement) {
        if(element.name==="companyId")return element.value!==(editingFlow?.companyId||"");
        if(element.name==="excluded")return element.value!==String(Boolean(editingFlow?.excluded));
        return Array.from(element.options).some(option=>option.selected!==option.defaultSelected);
      }
      return false;
    });
  }
  function confirmCloseFlowEditor(form:HTMLFormElement|null) {
    if(!isFlowEditorDirty(form)) {
      setEditingFlow(null);
      setPanel(null);
      flash("Formulaire fermé · aucune modification à enregistrer");
      return;
    }
    if(window.confirm("Voulez-vous enregistrer les modifications avant de fermer ?")) {
      const saveAndClose=form?.querySelector<HTMLButtonElement>('[data-save-and-close="true"]');
      if(form&&saveAndClose)form.requestSubmit(saveAndClose);
      return;
    }
    if(window.confirm("Décliner les modifications et fermer sans enregistrer ?")) {
      setEditingFlow(null);
      setPanel(null);
      flash("Modifications déclinées · aucune donnée enregistrée");
    }
  }
  async function saveFlowChanges(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); if(!editingFlow)return;
    const submitter=(event.nativeEvent as SubmitEvent).submitter as HTMLButtonElement|null; const closeAfterSave=submitter?.value==="save-close";
    const data=new FormData(event.currentTarget); setBusy(true);
    try {
      const response=await fetch("/api/treasury",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({id:editingFlow.id,action:"editFlow",companyId:data.get("companyId"),party:data.get("party"),beneficiaryName:data.get("beneficiaryName"),operationLabel:data.get("operationLabel"),reference:data.get("reference"),amount:Number(data.get("amount")),dueDate:data.get("dueDate"),plannedDate:data.get("plannedDate"),invoiceDate:data.get("invoiceDate"),note:data.get("note"),excluded:data.get("excluded")==="true"})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Modification impossible");
      await refresh(); flash(result.approvalStatus==="pending"?"Ligne modifiée · demande hiérarchique actualisée":"Ligne modifiée et enregistrée");
      if(closeAfterSave){setEditingFlow(null);setPanel(null);}
    } catch(error) { flash(error instanceof Error?error.message:"Modification impossible"); }
    finally { setBusy(false); }
  }
  function toggleExcluded(flow:Flow) {
    const excluded=!flow.excluded;
    if(!window.confirm(`${excluded?"Exclure":"Réintégrer"} « ${flow.party} · ${flow.reference} » ${excluded?"du":"dans le"} calcul de trésorerie ?`))return;
    setFlows(items=>items.map(item=>item.id===flow.id?{...item,excluded}:item));
    flash(excluded?"Ligne retirée du calcul":"Ligne réintégrée au calcul");
    fetch("/api/treasury",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({id:flow.id,excluded})}).catch(()=>undefined);
  }
  async function selectModel(kind:"client"|"fournisseur",file?:File) {
    if (!file) return;
    setImportDecisionOpen(false); setImportPreview(null);
    setImportErrors(errors=>({...errors,[kind]:undefined}));
    try { const parsed=await readModel(file,kind,importCompany); setPendingImports(items=>({...items,[kind]:parsed})); }
    catch(error) { setImportErrors(errors=>({...errors,[kind]:error instanceof Error?error.message:"Fichier non reconnu"})); }
  }
  async function previewImport() {
    const batches=Object.values(pendingImports).filter(Boolean) as ImportBatch[];
    if(!batches.length)return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/import",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({batches,preview:true})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Vérification impossible");
      setImportPreview(result as ImportPreview); setImportDecisionOpen(true);
    } catch(error) { flash(error instanceof Error?error.message:"La vérification des fichiers a échoué"); }
    finally { setBusy(false); }
  }
  async function confirmImport() {
    const batches=Object.values(pendingImports).filter(Boolean) as ImportBatch[];
    if (!batches.length) return;
    if(!window.confirm(`Importer définitivement ${importPreview?.imported??"les"} nouvelle(s) ligne(s) dans le plan de trésorerie ?`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/import",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({batches})});
      if (!response.ok) throw new Error("Import impossible");
      const result=await response.json(); await refresh(); setPendingImports({}); setImportDecisionOpen(false); setImportPreview(null);
      flash(`${result.imported} nouvelle(s) ligne(s) ajoutée(s) · ${result.skipped} ligne(s) déjà connue(s) ignorée(s) · planning existant conservé`);
    } catch { flash("La mise à jour n’a pas pu être enregistrée"); }
    finally { setBusy(false); }
  }
  async function updateBanks(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); const data=new FormData(event.currentTarget);
    const updates=scopedBanks.map(bank=>({id:bank.id,balance:Number(data.get(`bank-${bank.id}`))}));
    setBanks(items=>items.map(item=>({...item,balance:updates.find(u=>u.id===item.id)?.balance??item.balance})));
    await fetch("/api/treasury",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({banks:updates})}).catch(()=>undefined); await refresh();
    flash("Soldes bancaires mis à jour · vous restez sur cette page");
  }
  async function addFlow(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); const data=new FormData(event.currentTarget); const kind=data.get("kind") as "client"|"fournisseur";
    const created={id:Date.now(),companyId:String(data.get("companyId")||selectedCompanyId),kind,party:String(data.get("party")),beneficiaryName:kind==="fournisseur"?String(data.get("party")):null,reference:String(data.get("reference")),amount:Number(data.get("amount")),invoiceDate:String(data.get("invoiceDate")||"")||null,dueDate:String(data.get("dueDate")),plannedDate:String(data.get("dueDate")),status:"à valider"} as Flow;
    setFlows(items=>[...items,created]); await fetch("/api/treasury",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(created)}).catch(()=>undefined); await refresh();
    event.currentTarget.reset(); flash("Facture ajoutée au plan de trésorerie · vous restez sur cette page");
  }
  function addForecast(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); const data=new FormData(event.currentTarget); const kind=data.get("kind") as "client"|"fournisseur"; const plannedDate=String(data.get("plannedDate"));
    const created={id:Date.now(),companyId:String(data.get("companyId")||selectedCompanyId),kind,party:String(data.get("label")),beneficiaryName:kind==="fournisseur"?String(data.get("beneficiaryName")||data.get("label")):null,operationLabel:String(data.get("label")),reference:`PRV-${Date.now()}`,amount:Number(data.get("amount")),dueDate:plannedDate,plannedDate,status:"planifié",isForecast:true,excluded:false,note:String(data.get("note")||""),sourceFile:"Prévision manuelle"} as Flow;
    setFlows(items=>[...items,created]); fetch("/api/treasury",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(created)}).then(()=>refresh()).catch(()=>undefined);
    event.currentTarget.reset(); flash("Prévision ajoutée au plan de trésorerie · vous restez sur cette page");
  }
  async function addTemplate(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); const form=event.currentTarget; const data=new FormData(form);
    await fetch("/api/treasury/settings",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"template",companyId:data.get("companyId"),label:data.get("label"),kind:data.get("kind"),defaultAmount:Number(data.get("defaultAmount")),note:data.get("note")})});
    form.reset(); await refreshSettings(); flash("Modèle de prévision enregistré");
  }
  async function addSupplierBank(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); const form=event.currentTarget; const data=new FormData(form);
    const savedSupplierName=String(data.get("supplierName")||""); const rib=normalizedRib(data.get("rib"));
    if(!validRib(rib))return flash(ribValidationMessage);
    const returnToTransfer=Boolean(supplierBankCreationFromTransfer&&transferFlow);
    const response=await fetch("/api/treasury/settings",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"supplierBank",companyId:data.get("companyId"),supplierName:data.get("supplierName"),beneficiaryName:data.get("beneficiaryName"),bankName:data.get("bankName"),rib,iban:data.get("iban"),swift:data.get("swift")})});
    const result=await response.json().catch(()=>({})); if (!response.ok) return flash(result.error||"Coordonnées bancaires incomplètes");
    form.reset(); setSupplierBankSupplierName(""); setSupplierBankBeneficiaryName(""); setSupplierBankName(""); setSupplierBankNameMode("existing"); await refreshSettings();
    if(returnToTransfer)setTransferReceptionBeneficiaryName((result.supplierBank as SupplierBank|undefined)?.beneficiaryName||savedSupplierName);
    flash(returnToTransfer?`Banque de ${savedSupplierName} enregistree. Cliquez sur "Retour vers ordre de virement" pour continuer.`:"Banque fournisseur enregistree");
  }
  async function updateSupplierBank(event:FormEvent<HTMLFormElement>,bankId:number) {
    event.preventDefault(); const data=new FormData(event.currentTarget); const rib=normalizedRib(data.get("rib"));
    if(!validRib(rib))return flash(ribValidationMessage);
    const response=await fetch("/api/treasury/settings",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"supplierBank",id:bankId,supplierName:data.get("supplierName"),beneficiaryName:data.get("beneficiaryName"),bankName:data.get("bankName"),rib,iban:data.get("iban"),swift:data.get("swift")})});
    const result=await response.json().catch(()=>({})); if(!response.ok)return flash(result.error||"Modification impossible");
    await refreshSettings(); setSupplierBankEditor(null); flash("Banque fournisseur modifiée");
  }
  async function deleteSupplierBank(bank:SupplierBank) {
    if(!window.confirm(`Supprimer le compte ${bank.bankName} de ${bank.supplierName} ?\n\nLes anciens ordres de virement resteront conservés dans l’historique.`))return;
    const response=await fetch("/api/treasury/settings",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"supplierBank",id:bank.id})});
    const result=await response.json().catch(()=>({})); if(!response.ok)return flash(result.error||"Suppression impossible");
    await refreshSettings(); if(supplierBankEditor===bank.id)setSupplierBankEditor(null); flash("Banque fournisseur supprimée");
  }
  async function addBlockedSupplier(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); const form=event.currentTarget; const data=new FormData(form); const supplierName=String(data.get("supplierName")||"").trim();
    if(!supplierName)return flash("Sélectionnez un fournisseur");
    const response=await fetch("/api/treasury/settings",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"blockedSupplier",companyId:blockedCompanyId,supplierName})});
    const result=await response.json().catch(()=>({})); if(!response.ok)return flash(result.error||"Blocage impossible");
    form.reset(); await Promise.all([refresh(),refreshSettings()]); flash(`${supplierName} ajouté aux fournisseurs bloqués`);
  }
  async function deleteBlockedSupplier(item:BlockedSupplier) {
    if(!window.confirm(`Débloquer ${item.supplierName} pour ${companyName(item.companyId)} ?`))return;
    const response=await fetch("/api/treasury/settings",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"blockedSupplier",id:item.id})});
    const result=await response.json().catch(()=>({})); if(!response.ok)return flash(result.error||"Déblocage impossible");
    await Promise.all([refresh(),refreshSettings()]); flash(`${item.supplierName} débloqué`);
  }
  async function addReserveAllocation(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); const form=event.currentTarget; const data=new FormData(form);
    const response=await fetch("/api/treasury/reserves",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({companyId:reserveCompanyId,bankAccountId:Number(data.get("bankAccountId")),category:data.get("category"),label:data.get("label"),amount:Number(data.get("amount")),transferDate:data.get("transferDate"),includedInTreasury:data.get("includedInTreasury")==="true",note:data.get("note")})});
    const result=await response.json().catch(()=>({})); if(!response.ok)return flash(result.error||"Transfert de réserve impossible");
    form.reset(); await refresh(); flash("Montant affecté à la réserve");
  }
  async function toggleReserveInTreasury(reserve:ReserveAllocation) {
    const includedInTreasury=!reserve.includedInTreasury;
    if(!window.confirm(`${includedInTreasury?"Inclure":"Retirer"} la réserve « ${reserve.label} » ${includedInTreasury?"dans":"de"} la trésorerie disponible ?`))return;
    setReserves(items=>items.map(item=>item.id===reserve.id?{...item,includedInTreasury}:item));
    const response=await fetch("/api/treasury/reserves",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({id:reserve.id,includedInTreasury})});
    if(!response.ok){await refresh();return flash("Mise à jour de la réserve impossible");}
    flash(includedInTreasury?"Réserve incluse dans la trésorerie":"Réserve retirée de la trésorerie disponible");
  }
  async function deleteReserveAllocation(reserve:ReserveAllocation) {
    if(!window.confirm(`Supprimer l’affectation ${reserve.label} de ${money(reserve.amount)} ?`))return;
    const response=await fetch("/api/treasury/reserves",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({id:reserve.id})});
    if(!response.ok)return flash("Suppression impossible"); await refresh(); flash("Affectation de réserve supprimée");
  }
  async function updateCompanyBank(event:FormEvent<HTMLFormElement>,bankId:number) {
    event.preventDefault(); const data=new FormData(event.currentTarget); const rib=normalizedRib(data.get("rib"));
    if(!validRib(rib))return flash(ribValidationMessage);
    const response=await fetch("/api/treasury/settings",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"companyBank",id:bankId,name:data.get("name"),accountLabel:data.get("accountLabel"),accountHolder:data.get("accountHolder"),rib,iban:data.get("iban"),swift:data.get("swift"),branchAddress:data.get("branchAddress")})});
    const result=await response.json().catch(()=>({})); if(!response.ok)return flash(result.error||"Mise à jour impossible");
    await refresh(); setBankEditor(null); flash("Coordonnées de la banque mises à jour");
  }
  async function makePrimaryBank(bank:Bank) {
    if(bank.isPrimary)return;
    if(!window.confirm(`Définir ${bank.name} · ${bank.accountLabel} comme banque principale de ${companyName(bank.companyId)} ?`))return;
    const response=await fetch("/api/treasury/settings",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"companyBankPrimary",id:bank.id})});
    const result=await response.json().catch(()=>({})); if(!response.ok)return flash(result.error||"Mise à jour impossible");
    await refresh(); flash(`${bank.name} est maintenant la banque principale de ${companyName(bank.companyId)}`);
  }
  async function addCompanyBank(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); const data=new FormData(event.currentTarget); const rib=normalizedRib(data.get("rib"));
    if(!validRib(rib))return flash(ribValidationMessage);
    const response=await fetch("/api/treasury/settings",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"companyBank",companyId:data.get("companyId"),name:data.get("name"),accountLabel:data.get("accountLabel"),balance:Number(data.get("balance")||0),minimumBalance:0,accountHolder:data.get("accountHolder"),rib,iban:data.get("iban"),swift:data.get("swift"),branchAddress:data.get("branchAddress")})});
    const result=await response.json().catch(()=>({})); if(!response.ok)return flash(result.error||"La banque n’a pas pu être ajoutée");
    await refresh(); setBankEditor(null); flash("Nouvelle banque ajoutée");
  }
  async function deleteCompanyBank(bank:Bank) {
    if (!window.confirm(`Supprimer ${bank.name} · ${bank.accountLabel} ?`)) return;
    const response=await fetch("/api/treasury/settings",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"companyBank",id:bank.id})});
    if (!response.ok) return flash("Suppression impossible");
    await refresh(); setBankEditor(null); flash("Banque supprimée");
  }
  function openSettlement(flow:Flow) {
    if(flow.kind==="fournisseur"&&approvalForFlow(flow.id)?.status!=="approved")return flash("Le règlement doit d’abord être validé par le responsable hiérarchique");
    const companyBanks=banks.filter(bank=>bank.companyId===flow.companyId);
    setSettlementFlow(flow); setSettlementBankId(companyBanks[0]?.id??0); setSettlementDate(new Date().toISOString().slice(0,10)); setPanel("settlement");
  }
  async function preparePayment(flow:Flow) {
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/payment-approvals",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({cashFlowId:flow.id,requestedBy:"Utilisateur Moventra ERP"})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Préparation impossible");
      await refresh(); flash("Règlement préparé · demande transmise au responsable hiérarchique");
    } catch(error) { flash(error instanceof Error?error.message:"Préparation impossible"); }
    finally { setBusy(false); }
  }
  async function saveApprovalBeneficiary(approval:PaymentApproval) {
    const flow=findFlow(approval.cashFlowId);
    const beneficiaryName=String(approvalBeneficiaryNames[approval.id]??flow?.beneficiaryName??flow?.party??"").trim();
    if(!flow||!beneficiaryName){flash("Renseignez le bénéficiaire du règlement");return;}
    const previous=flow.beneficiaryName||flow.party;
    if(beneficiaryName===previous){flash("Le bénéficiaire est déjà à jour");return;}
    if(!window.confirm(`Modifier le bénéficiaire de ${previous} vers ${beneficiaryName} ?`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/payment-approvals",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({id:approval.id,action:"updateBeneficiary",beneficiaryName})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Modification impossible");
      setApprovalBeneficiaryNames(current=>{const next={...current};delete next[approval.id];return next});
      await refresh(); flash(`Bénéficiaire enregistré : ${beneficiaryName}`);
    } catch(error) { flash(error instanceof Error?error.message:"Modification impossible"); }
    finally { setBusy(false); }
  }
  async function decidePayment(approval:PaymentApproval,status:"approved"|"rejected") {
    const flow=findFlow(approval.cashFlowId); const verb=status==="approved"?"Valider":"Refuser";
    const plannedDate=status==="approved"?(approvalPlannedDates[approval.id]||flow?.plannedDate||""):(flow?.plannedDate||"");
    const beneficiaryName=status==="approved"?String(approvalBeneficiaryNames[approval.id]??flow?.beneficiaryName??flow?.party??"").trim():String(flow?.beneficiaryName||flow?.party||"").trim();
    if(status==="approved"&&!/^\d{4}-\d{2}-\d{2}$/.test(plannedDate)){flash("Renseignez une date planifiée valide avant de valider");return;}
    if(status==="approved"&&!beneficiaryName){flash("Renseignez le bénéficiaire avant de valider");return;}
    const dateChanged=Boolean(flow&&status==="approved"&&plannedDate!==flow.plannedDate);
    const beneficiaryChanged=Boolean(flow&&status==="approved"&&beneficiaryName!==(flow.beneficiaryName||flow.party));
    const dateMessage=dateChanged&&flow?`\n\nDate modifiée :\nAvant : ${new Date(`${flow.plannedDate}T00:00:00`).toLocaleDateString("fr-FR")}\nAprès : ${new Date(`${plannedDate}T00:00:00`).toLocaleDateString("fr-FR")}`:"";
    const beneficiaryMessage=beneficiaryChanged&&flow?`\n\nBénéficiaire modifié :\nAvant : ${flow.beneficiaryName||flow.party}\nAprès : ${beneficiaryName}`:"";
    if(!window.confirm(`${verb} le règlement de ${flow?money(flow.amount):"cette facture"}${flow?` pour ${beneficiaryName||flow.party}`:""} ?${dateMessage}${beneficiaryMessage}`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/payment-approvals",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({id:approval.id,status,plannedDate,beneficiaryName,decidedBy:"Responsable hiérarchique"})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Décision impossible");
      setApprovalPlannedDates(current=>{const next={...current};delete next[approval.id];return next});
      setApprovalBeneficiaryNames(current=>{const next={...current};delete next[approval.id];return next});
      await refresh();
      flash(result.beneficiaryChange?.changed
        ? `Règlement approuvé · bénéficiaire modifié de ${result.beneficiaryChange.before} à ${result.beneficiaryChange.after}`
        : result.dateChange?.changed
          ? `Règlement approuvé · date modifiée du ${new Date(`${result.dateChange.before}T00:00:00`).toLocaleDateString("fr-FR")} au ${new Date(`${result.dateChange.after}T00:00:00`).toLocaleDateString("fr-FR")}`
          : result.internalTransfer?.executed
            ? `Transfert interne exécuté · ${money(result.internalTransfer.amount)} transférés vers ${companyName(result.internalTransfer.toCompanyId)}`
            : status==="approved"?"Règlement approuvé · ordre de virement autorisé":"Règlement refusé · à préparer de nouveau");
    } catch(error) { flash(error instanceof Error?error.message:"Décision impossible"); }
    finally { setBusy(false); }
  }  async function cancelPaymentPreparation(approval:PaymentApproval) {
    const flow=findFlow(approval.cashFlowId); const printedOrder=printedOrderForFlow(approval.cashFlowId);
    const warning=printedOrder?`\n\nL’ordre ${printedOrder.internalNumber||"déjà imprimé"} sera invalidé.`:"";
    if(!window.confirm(`Annuler la préparation du règlement${flow?` de ${money(Math.abs(flow.amount))} pour ${flow.party}`:""} ?${warning}\n\nLa facture reviendra à l’état initial « Préparer règlement » dans la vue d’ensemble.`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/payment-approvals",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({id:approval.id})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Annulation impossible");
      await Promise.all([refresh(),refreshSettings()]); setSelectedTransferIds(ids=>ids.filter(id=>id!==approval.cashFlowId)); flash("Préparation annulée · facture revenue à l’état initial · vous restez sur les validations");
    } catch(error) { flash(error instanceof Error?error.message:"Annulation impossible"); }
    finally { setBusy(false); }
  }
  async function cancelInternalTransfer(transfer:InternalTransfer) {
    if(!window.confirm(`Annuler le transfert ${transfer.reference} de ${money(transfer.amount)} ?\n\nLes deux mouvements bancaires seront contrepassés et les soldes des sociétés seront restaurés. L’historique restera conservé pour le contrôle interne.`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/internal-transfers",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({id:transfer.id,reason:"Annulation du transfert exécuté"})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Annulation impossible");
      await refresh(); setSelectedTransferIds(ids=>ids.filter(id=>id!==transfer.outgoingFlowId)); flash(`Transfert ${transfer.reference} annulé · soldes bancaires restaurés`);
    } catch(error) { flash(error instanceof Error?error.message:"Annulation impossible"); }
    finally { setBusy(false); }
  }
  async function requestInternalTransfer(fromCompanyId:string,toCompanyId:string,amount:number) {
    if(amount<=0)return;
    if(!window.confirm(`Créer un transfert interne de ${money(amount)} de ${companyName(fromCompanyId)} vers ${companyName(toCompanyId)} ?\n\nDeux lignes liées seront ajoutées au plan global et le décaissement sera transmis au responsable pour validation.`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/internal-transfers",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({fromCompanyId,toCompanyId,amount,transferDate:new Date().toISOString().slice(0,10),requestedBy:"Utilisateur Moventra ERP"})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Transfert interne impossible");
      await refresh(); flash(`Transfert ${result.transfer?.reference||"interne"} créé · en attente de validation`);
    } catch(error) { flash(error instanceof Error?error.message:"Transfert interne impossible"); }
    finally { setBusy(false); }
  }
  async function confirmSettlement(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); if(!settlementFlow||!settlementBankId)return flash("Sélectionnez la banque concernée");
    const settledFlow=settlementFlow; const settledBankId=settlementBankId; const settledDate=settlementDate;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/settle",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({flowId:settledFlow.id,bankAccountId:settledBankId,transactionDate:settledDate})});
      const result=await response.json().catch(()=>({}));
      if(!response.ok)throw new Error(result.error||"Validation impossible");
      await refresh(); setSelectedTransferIds(ids=>ids.filter(id=>id!==settledFlow.id)); setSettlementFlow(null);
      if(settledFlow.kind==="client") { setReceiptReserve({flow:settledFlow,bankId:settledBankId,transactionDate:settledDate}); setReceiptReservePercent(10); setPanel("receiptReserve"); flash("Encaissement validé et compte bancaire crédité · l’affectation à l’épargne est facultative"); }
      else { setPanel("settlements"); flash("Règlement validé et banque débitée"); }
    } catch(error) { flash(error instanceof Error?error.message:"Validation impossible"); }
    finally { setBusy(false); }
  }
  async function executePrintedOrder(flow:Flow,order:TransferOrder) {
    const bank=banks.find(item=>item.id===order.companyBankId);
    if(!bank)return flash("Le compte bancaire de l’ordre de virement est introuvable");
    if(Math.abs(Number(order.amount)-Math.abs(Number(flow.amount)))>.01)return flash("Le montant imprimé ne couvre pas exactement cette échéance. Fractionnez d’abord la facture avant de l’exécuter.");
    const protectedReserve=reserves.filter(item=>item.bankAccountId===bank.id&&!item.includedInTreasury).reduce((sum,item)=>sum+Number(item.amount),0);
    const bankAvailable=moneyValue(Number(bank.balance)-protectedReserve);
    if(bankAvailable<Math.abs(Number(flow.amount)))return flash(`Solde insuffisant sur ${bank.name} après réserves : ${money(Math.max(0,bankAvailable))} disponibles`);
    if(!window.confirm(`Exécuter le règlement de ${money(Math.abs(flow.amount))} pour ${flow.party} ?\n\nLe compte ${bank.name} · ${bank.accountLabel} sera débité et la facture sera clôturée.`))return;
    setBusy(true);
    try {
      const transactionDate=new Date().toISOString().slice(0,10);
      const response=await fetch("/api/treasury/settle",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({flowId:flow.id,bankAccountId:order.companyBankId,transactionDate})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Exécution impossible");
      await Promise.all([refresh(),refreshSettings()]); setSelectedTransferIds(ids=>ids.filter(id=>id!==flow.id)); flash(`Règlement exécuté · avis fournisseur disponible dans « Virements exécutés »`);
    } catch(error) { flash(error instanceof Error?error.message:"Exécution impossible"); }
    finally { setBusy(false); }
  }
  async function saveReceiptReserve(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); if(!receiptReserve||!receiptReserveBank)return flash("Banque de l’encaissement introuvable");
    if(!Number.isFinite(receiptReservePercent)||receiptReservePercent<=0||receiptReservePercent>100)return flash("Le pourcentage doit être compris entre 0 et 100 %");
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/reserves",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({companyId:receiptReserve.flow.companyId,bankAccountId:receiptReserve.bankId,category:"epargne",label:`Épargne sur encaissement ${receiptReserve.flow.reference}`,amount:receiptReserveAmount,transferDate:receiptReserve.transactionDate,includedInTreasury:false,note:`${receiptReservePercent}% de l’encaissement client ${receiptReserve.flow.party}`})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Affectation à l’épargne impossible");
      await refresh(); setReceiptReserve(null); setPanel("settlements"); flash(`${money(receiptReserveAmount)} affectés à l’épargne et déduits de la trésorerie disponible`);
    } catch(error) { flash(error instanceof Error?error.message:"Affectation à l’épargne impossible"); }
    finally { setBusy(false); }
  }
  function openBankHistory() {
    setHistoryBankId(scopedBanks[0]?.id??banks[0]?.id??0); setPanel("bankHistory");
  }
  function openSettlements() {
    setSettlementHistoryCompany(companyFilter); setSettlementHistoryKind("all"); setSettlementHistorySearch(""); setPanel("settlements");
  }
  async function cancelSettlement(transaction:BankTransaction) {
    if(transaction.cancelledAt)return;
    const bank=banks.find(item=>item.id===transaction.bankAccountId);
    const impact=transaction.kind==="client"?"débité":"crédité";
    if(!window.confirm(`Annuler le ${transaction.kind==="client"?"règlement client":"paiement fournisseur"} de ${money(transaction.amount)} pour ${transaction.party} ?\n\nLe compte ${bank?.name||"bancaire"} sera ${impact}, la facture sera restaurée et une nouvelle validation sera exigée.`))return;
    const reason=window.prompt("Motif obligatoire du retour arrière", "Erreur de saisie ou d’exécution");
    if(reason===null)return;
    if(!reason.trim())return flash("Indiquez le motif du retour arrière");
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/settlements",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({transactionId:transaction.id,reason:reason.trim()})});
      const result=await response.json().catch(()=>({}));
      if(!response.ok)throw new Error(result.error||"Annulation impossible");
      await refresh(); flash("Retour arrière effectué · mouvement bancaire contre-passé · nouvelle validation requise");
    } catch(error) { flash(error instanceof Error?error.message:"Annulation impossible"); }
    finally { setBusy(false); }
  }
  function openBlockedSuppliers() {
    setBlockedCompanyId(companyFilter==="all"?companies[0]?.id??"moventra":companyFilter); setPanel("blockedSuppliers");
  }
  function openReserves() {
    setReserveCompanyId(companyFilter==="all"?companies[0]?.id??"moventra":companyFilter); setPanel("reserves");
  }
  function openAccountFunding(companyId:string,suggestedAmount:number) {
    const companyBanks=banks.filter(bank=>bank.companyId===companyId);
    const primary=companyBanks.find(bank=>bank.isPrimary)||companyBanks[0];
    const source=preparedTransferSummaries.filter(summary=>summary.companyId!==companyId).sort((a,b)=>b.remaining-a.remaining)[0];
    setFundingCompanyId(companyId);
    setFundingSourceCompanyId(source?.companyId??companies.find(company=>company.id!==companyId)?.id??"");
    setFundingBankId(primary?.id??0);
    setFundingAmount(Math.max(0,Math.round(suggestedAmount*100)/100));
    setFundingDate(new Date().toISOString().slice(0,10));
    setFundingReason("Apport de trésorerie");
    setPanel("fundAccount");
  }
  async function submitAccountFunding(event:FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if(!fundingSourceCompanyId)return flash("Sélectionnez la société qui finance l’alimentation");
    if(!fundingBankId||fundingAmount<=0)return flash("Sélectionnez le compte et renseignez un montant positif");
    if(!fundingReason.trim())return flash("Renseignez l’origine ou le motif des fonds");
    if(!window.confirm(`Créer une demande de financement de ${money(fundingAmount)} de ${companyName(fundingSourceCompanyId)} vers ${companyName(fundingCompanyId)} ?\n\nElle devra être validée puis imprimée avant que le compte ${fundingBank?.name||"bancaire"} soit crédité.`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/account-funding",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({fromCompanyId:fundingSourceCompanyId,companyId:fundingCompanyId,bankAccountId:fundingBankId,amount:fundingAmount,transactionDate:fundingDate,reason:fundingReason})});
      const result=await response.json().catch(()=>({}));
      if(!response.ok)throw new Error(result.error||"Alimentation impossible");
      await Promise.all([refresh(),refreshSettings()]); flash(`Demande ${result.transfer.reference} créée · à valider dans Validations règlements`);
    } catch(error) { flash(error instanceof Error?error.message:"Alimentation impossible"); }
    finally { setBusy(false); }
  }
  function selectAccountTransferCompany(companyId:string) {
    const companyBanks=banks.filter(bank=>bank.companyId===companyId);
    setAccountTransferCompanyId(companyId); setAccountTransferSourceId(companyBanks[0]?.id??0); setAccountTransferDestinationId(companyBanks[1]?.id??0);
  }
  function openAccountTransfer() {
    const today=new Date().toISOString().slice(0,10); setAccountTransferAmount(0); setAccountTransferDebitDate(today); setAccountTransferReceptionDate(today); selectAccountTransferCompany(companyFilter==="all"?companies[0]?.id??"moventra":companyFilter); setPanel("accountTransfer");
  }
  async function executeAccountTransfer(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); const form=event.currentTarget; const data=new FormData(form); const amount=Number(data.get("amount"));
    if(!accountTransferSource||!accountTransferDestination)return flash("Sélectionnez deux comptes différents");
    if(!window.confirm(`Débiter ${money(amount)} de ${accountTransferSource.name} le ${new Date(`${accountTransferDebitDate}T00:00:00`).toLocaleDateString("fr-FR")} et créditer ${accountTransferDestination.name} le ${new Date(`${accountTransferReceptionDate}T00:00:00`).toLocaleDateString("fr-FR")} ?\n\nEntre ces deux dates, le montant restera identifié comme en transit.`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/account-transfers",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({companyId:accountTransferCompanyId,sourceBankId:accountTransferSource.id,destinationBankId:accountTransferDestination.id,amount,transferDate:accountTransferDebitDate,receptionDate:accountTransferReceptionDate,purpose:data.get("purpose")})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Virement entre comptes impossible");
      form.reset(); await refresh(); flash(result.transfer.status==="received"?`Virement reçu · ${result.transfer.reference}`:`Virement en transit jusqu’au ${new Date(`${result.transfer.receptionDate}T00:00:00`).toLocaleDateString("fr-FR")} · ${result.transfer.reference}`);
    } catch(error) { flash(error instanceof Error?error.message:"Virement entre comptes impossible"); }
    finally { setBusy(false); }
  }
  function openTransfer(flow:Flow) {
    if(approvalForFlow(flow.id)?.status!=="approved")return flash("Validation hiérarchique obligatoire avant l’ordre de virement");
    setSupplierBankCreationFromTransfer(false); setTransferPaymentMethod(flow.isForecast?"mobile":"bank"); setSelectedTransferIds([flow.id]); setTransferFlow(flow); setTransferReceptionBeneficiaryName(""); setShowAllSupplierBanks(false); setPrintOrder(null); setPanel("transfer");
  }
  function openSupplierBankCreationForTransfer() {
    if(!transferFlow)return;
    setSupplierBankCreationFromTransfer(true); setSupplierBankCompanyId(transferFlow.companyId); setSupplierBankSupplierName(transferFlow.party); setSupplierBankBeneficiaryName(""); setSupplierBankEditor(null); setPanel("settings");
    window.setTimeout(()=>document.getElementById("supplier-bank-settings")?.scrollIntoView({block:"start"}),100);
    flash(`Ajoutez un bénéficiaire bancaire pour ${transferFlow.party}`);
  }
  function reprintTransferOrder(order:TransferOrder) {
    if(transferPrintLock.current)return flash("Une impression est déjà en cours");
    const companyBank=banks.find(bank=>bank.id===order.companyBankId);
    const supplierBank=supplierBanks.find(bank=>bank.id===order.supplierBankId);
    if(!companyBank||!supplierBank)return flash("Les coordonnées bancaires de cet ordre ne sont plus disponibles");
    const groupedOrders=order.internalNumber?transferOrders.filter(item=>item.internalNumber===order.internalNumber):[order];
    const groupedFlows=groupedOrders.map(item=>flows.find(flow=>flow.id===item.cashFlowId));
    const groupedOperationLabels=[...new Set(groupedFlows.map(flow=>flow?.operationLabel?.trim()).filter((label):label is string=>Boolean(label)))];
    transferPrintLock.current=true; setInternalTransferPrintOrder(null); setAccountFundingPrintOrder(null); setSupplierPaymentAdvice(null); setPrintMode("order");
    setPrintOrder({
      internalNumber:order.internalNumber||`OV-${order.companyId.toUpperCase()}-${order.id}`,
      company:companies.find(company=>company.id===order.companyId)?.name??order.companyId,
      companyBank,supplierBank,
      amount:groupedOrders.reduce((sum,item)=>sum+Number(item.amount),0),
      purpose:order.purpose,operationLabel:groupedOperationLabels.length===1?groupedOperationLabels[0]:groupedOperationLabels.length>1?`Règlement consolidé de ${groupedOperationLabels.length} opérations`:order.purpose,comment:order.comment||undefined,orderDate:order.orderDate,
      references:groupedOrders.map((item,index)=>({reference:groupedFlows[index]?.reference||`Flux n° ${item.cashFlowId}`,operationLabel:groupedFlows[index]?.operationLabel?.trim()||order.purpose,amount:Number(item.amount)})),
    });
    window.setTimeout(()=>{window.print();setPrintOrder(null);setPrintMode(null);transferPrintLock.current=false;},150);
  }
  function reprintInternalTransferOrder(transfer:InternalTransfer) {
    const sourceTransaction=bankTransactions.find(item=>item.cashFlowId===transfer.outgoingFlowId&&item.direction==="debit"&&!item.cancelledAt);
    const destinationTransaction=bankTransactions.find(item=>item.cashFlowId===transfer.incomingFlowId&&item.direction==="credit"&&!item.cancelledAt);
    const sourceBank=sourceTransaction?banks.find(bank=>bank.id===sourceTransaction.bankAccountId):undefined;
    const destinationBank=destinationTransaction?banks.find(bank=>bank.id===destinationTransaction.bankAccountId):undefined;
    if(!sourceBank||!destinationBank)return flash("Les comptes bancaires du transfert interne sont introuvables");
    setPrintOrder(null); setPrintMode("order");
    setInternalTransferPrintOrder({reference:transfer.reference,fromCompany:companies.find(company=>company.id===transfer.fromCompanyId)?.name??transfer.fromCompanyId,toCompany:companies.find(company=>company.id===transfer.toCompanyId)?.name??transfer.toCompanyId,sourceBank,destinationBank,amount:Number(transfer.amount),transferDate:transfer.transferDate});
    window.setTimeout(()=>window.print(),150);
  }
  function reprintAccountFunding(transaction:BankTransaction) {
    const bank=banks.find(item=>item.id===transaction.bankAccountId);
    const sourceExecution=bankTransactions.find(item=>item.direction==="debit"&&!item.cancelledAt&&item.reference===`SOURCE-${transaction.reference}`);
    const sourceBank=(sourceExecution?banks.find(item=>item.id===sourceExecution.bankAccountId):undefined)||legacyFundingSourceBank;
    const sourceCompanyId=sourceExecution?.companyId||legacyFundingSourceCompanyId;
    if(!bank)return flash("Le compte bancaire bénéficiaire de cette alimentation est introuvable");
    if(!sourceCompanyId||!sourceBank)return flash("Sélectionnez la société et le compte bancaire source avant de réimprimer");
    if(sourceCompanyId===transaction.companyId)return flash("La société source doit être différente de la société bénéficiaire");
    setPrintOrder(null); setInternalTransferPrintOrder(null); setPrintMode("order");
    setAccountFundingPrintOrder({reference:transaction.reference,company:companies.find(company=>company.id===transaction.companyId)?.name??companyName(transaction.companyId),bank,sourceCompany:companies.find(company=>company.id===sourceCompanyId)?.name??companyName(sourceCompanyId),sourceBank,amount:Number(transaction.amount),transactionDate:transaction.transactionDate,description:transaction.party,balanceBefore:Number(transaction.balanceBefore),balanceAfter:Number(transaction.balanceAfter)});
    window.setTimeout(()=>window.print(),150);
  }
  async function executeLegacyAccountFunding(transaction:BankTransaction) {
    if(!legacyFundingSourceCompanyId||!legacyFundingSourceBank)return flash("Sélectionnez la société et le compte bancaire source");
    if(legacyFundingSourceCompanyId===transaction.companyId)return flash("La société source doit être différente de la société bénéficiaire");
    if(!window.confirm(`Exécuter la contrepartie source de ${money(transaction.amount)} ?\n\nLe compte ${legacyFundingSourceBank.name} · ${legacyFundingSourceBank.accountLabel} de ${companyName(legacyFundingSourceCompanyId)} sera débité. Le compte ${companyName(transaction.companyId)} est déjà crédité et ne sera pas crédité une deuxième fois.`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/account-funding",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({transactionId:transaction.id,sourceCompanyId:legacyFundingSourceCompanyId,sourceBankId:legacyFundingSourceBank.id})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Exécution impossible");
      await refresh(); flash(`Contrepartie exécutée · ${companyName(legacyFundingSourceCompanyId)} débité de ${money(result.execution.amount)}`);
    } catch(error) { flash(error instanceof Error?error.message:"Exécution impossible"); }
    finally { setBusy(false); }
  }
  async function cancelLegacyAccountFunding(transaction:BankTransaction,sourceExecution:BankTransaction) {
    const sourceBank=banks.find(item=>item.id===sourceExecution.bankAccountId);
    if(!window.confirm(`Annuler l’exécution source de ${money(transaction.amount)} ?\n\nLe compte ${sourceBank?.name||"source"} de ${companyName(sourceExecution.companyId)} sera recrédité. Le crédit historique de ${companyName(transaction.companyId)} restera inchangé.`))return;
    setBusy(true);
    try {
      const response=await fetch("/api/treasury/account-funding",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({transactionId:transaction.id,reason:"Retour en arrière demandé depuis les validations"})});
      const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Annulation impossible");
      await refresh(); flash(`Exécution annulée · compte source recrédité de ${money(result.cancellation.amount)}`);
    } catch(error) { flash(error instanceof Error?error.message:"Annulation impossible"); }
    finally { setBusy(false); }
  }
  function toggleTransferSelection(flow:Flow) {
    if(flow.kind!=="fournisseur"||flow.excluded)return;
    if(approvalForFlow(flow.id)?.status!=="approved")return flash("Seules les factures approuvées peuvent être regroupées");
    if(printedOrderForFlow(flow.id))return flash("Cette facture possède déjà un ordre de virement imprimé");
    setSelectedTransferIds(current=>{
      if(current.includes(flow.id))return current.filter(id=>id!==flow.id);
      const selected=flows.filter(item=>current.includes(item.id));
      if(selected.some(item=>item.companyId!==flow.companyId||normalized(item.party)!==normalized(flow.party)||item.plannedDate!==flow.plannedDate)) {
        flash("Sélection réinitialisée : regroupez le même fournisseur, la même société et la même date de règlement");
        return [flow.id];
      }
      return [...current,flow.id];
    });
  }
  function startGroupedTransfer(group:Flow[]) {
    if(group.length<2)return flash("Sélectionnez au moins deux lignes consolidables");
    const first=group[0];
    if(group.some(flow=>approvalForFlow(flow.id)?.status!=="approved"))return flash("Toutes les factures sélectionnées doivent être approuvées");
    if(group.some(flow=>flow.companyId!==first.companyId||normalized(flow.party)!==normalized(first.party)||flow.plannedDate!==first.plannedDate))return flash("Les lignes doivent avoir la même société, le même fournisseur et la même date planifiée");
    setSupplierBankCreationFromTransfer(false);setSelectedTransferIds(group.map(flow=>flow.id));setTransferFlow(first);setTransferReceptionBeneficiaryName("");setShowAllSupplierBanks(false);setPrintOrder(null);setPanel("transfer");
  }
  function openGroupedTransfer() {
    startGroupedTransfer(selectedTransferFlows);
  }
  function openConsolidationProposal(group:Flow[]) {
    startGroupedTransfer(group);
  }
  async function createTransferOrder(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); if (!transferFlow) return;
    const submitter=(event.nativeEvent as SubmitEvent).submitter as HTMLButtonElement|null; const shouldPrint=submitter?.value!=="save"; let printScheduled=false;
    const orderFlows=selectedTransferFlows.length?selectedTransferFlows:[transferFlow];
    const data=new FormData(event.currentTarget);
    const companyBank=banks.find(bank=>bank.id===Number(data.get("companyBankId")));
    const paymentMethod=String(data.get("paymentMethod")||transferPaymentMethod);
    if(transferFlow.isForecast&&paymentMethod!=="bank"){
      if(!companyBank)return flash("Sélectionnez le compte de notre société à débiter");
      const paymentDate=String(data.get("orderDate")||transferFlow.plannedDate); const purpose=String(data.get("purpose")||transferFlow.party).trim();
      const otherPaymentMethod=String(data.get("otherPaymentMethod")||"").trim(); const paymentReference=String(data.get("paymentReference")||"").trim();
      if(paymentMethod==="other"&&!otherPaymentMethod)return flash("Sélectionnez le moyen de règlement");
      if(paymentMethod==="other"&&!paymentReference)return flash("Saisissez le numéro ou la référence du règlement");
      if(!/^\d{4}-\d{2}-\d{2}$/.test(paymentDate))return flash("La date du règlement est invalide");
      if(!window.confirm(`Confirmer le règlement ${paymentMethod==="mobile"?"par application bancaire":`par ${String(data.get("otherPaymentMethod")||"autre mode")}`} de ${money(Math.abs(transferFlow.amount))} depuis ${companyBank.name} ?\n\nMotif : ${purpose}`))return;
      setBusy(true);
      try{
        const response=await fetch("/api/treasury/settle",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({flowId:transferFlow.id,bankAccountId:companyBank.id,transactionDate:paymentDate,paymentMethod:paymentMethod==="other"?otherPaymentMethod:paymentMethod,paymentReference,paymentPurpose:purpose})});
        const result=await response.json().catch(()=>({})); if(!response.ok)throw new Error(result.error||"Règlement impossible");
        await refresh(); setSelectedTransferIds([]); setTransferFlow(null); setPanel("settlements"); flash(`Règlement ${paymentMethod==="mobile"?"mobile":"par autre mode"} exécuté · ${purpose}`);
      }catch(error){flash(error instanceof Error?error.message:"Règlement impossible");}finally{setBusy(false);}
      return;
    }
    const beneficiarySelection=String(data.get("supplierBankId")||"");
    let supplierBank=beneficiarySelection.startsWith("supplier:")?supplierBanks.find(bank=>bank.id===Number(beneficiarySelection.slice(9))):undefined;
    const ownBeneficiaryBank=beneficiarySelection.startsWith("company:")?banks.find(bank=>bank.id===Number(beneficiarySelection.slice(8))):undefined;
    if (!companyBank||(!supplierBank&&!ownBeneficiaryBank)) return flash("Sélectionnez le compte à débiter et le compte bénéficiaire");
    if (!validRib(companyBank.rib)) return flash("Le RIB du compte source doit contenir exactement 24 chiffres");
    if (ownBeneficiaryBank&&!validRib(ownBeneficiaryBank.rib)) return flash("Le RIB du compte interne bénéficiaire doit contenir exactement 24 chiffres");
    if (supplierBank&&!validRib(supplierBank.rib)) return flash("Le RIB du bénéficiaire doit contenir exactement 24 chiffres");
    if (ownBeneficiaryBank&&ownBeneficiaryBank.id===companyBank.id) return flash("Le compte à débiter et le compte bénéficiaire doivent être différents");
    if(ownBeneficiaryBank&&!window.confirm(`Le bénéficiaire choisi est notre compte ${companyName(ownBeneficiaryBank.companyId)} · ${ownBeneficiaryBank.name} · ${ownBeneficiaryBank.accountLabel}.\n\nConfirmez-vous ce virement vers un compte interne ?`))return;
    if(supplierBank&&!supplierNamesMatch(transferFlow.party,supplierBank.supplierName)&&!window.confirm(`Le compte sélectionné appartient à « ${supplierBank.supplierName} » alors que le règlement concerne « ${transferFlow.party} ».\n\nConfirmez-vous explicitement l’utilisation de ce compte bénéficiaire ?`))return;
    const orderDate=String(data.get("orderDate")); const amount=Number(data.get("amount")); const purpose=String(data.get("purpose")); const comment=String(data.get("comment")||"").trim();
    const companySummary=preparedTransferSummaries.find(summary=>summary.companyId===transferFlow.companyId); const companyAvailable=companySummary?.available??0;
    const selectedApprovedAmount=orderFlows.filter(flow=>approvedAwaitingOrderFlowIds.has(flow.id)).reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0);
    const committedAfterPrinting=Math.max(0,(companySummary?.committed??0)-selectedApprovedAmount+amount);
    const thresholdExceeded=committedAfterPrinting>companyAvailable*.9&&committedAfterPrinting>0;
    const originalTotal=Math.max(0.01,orderFlows.reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0)); const operationLabels=[...new Set(orderFlows.map(flow=>flow.operationLabel?.trim()).filter((label):label is string=>Boolean(label)))]; const printOperationLabel=operationLabels.length===1?operationLabels[0]:operationLabels.length>1?`Règlement consolidé de ${operationLabels.length} opérations`:purpose; let allocated=0; let internalNumber=""; const printedReferences:Array<{reference:string;operationLabel:string;amount:number}>=[];
    if(shouldPrint&&transferPrintLock.current)return flash("Une impression est déjà en cours");
    if(shouldPrint)transferPrintLock.current=true;
    setBusy(true);
    try {
      if(!supplierBank&&ownBeneficiaryBank) {
        const internalSupplierName=`Compte interne · ${companyName(ownBeneficiaryBank.companyId)} · ${ownBeneficiaryBank.accountLabel}`;
        supplierBank=supplierBanks.find(bank=>bank.companyId===transferFlow.companyId&&bank.rib===ownBeneficiaryBank.rib&&normalized(bank.supplierName)===normalized(internalSupplierName));
        if(!supplierBank) {
          const beneficiaryResponse=await fetch("/api/treasury/settings",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"supplierBank",companyId:transferFlow.companyId,supplierName:internalSupplierName,beneficiaryName:ownBeneficiaryBank.accountHolder||companies.find(company=>company.id===ownBeneficiaryBank.companyId)?.name||companyName(ownBeneficiaryBank.companyId),bankName:ownBeneficiaryBank.name,rib:ownBeneficiaryBank.rib,iban:ownBeneficiaryBank.iban,swift:ownBeneficiaryBank.swift})});
          const beneficiaryResult=await beneficiaryResponse.json().catch(()=>({}));
          if(!beneficiaryResponse.ok||!beneficiaryResult.supplierBank)throw new Error(beneficiaryResult.error||"Impossible d’enregistrer notre compte comme bénéficiaire");
          supplierBank=beneficiaryResult.supplierBank as SupplierBank;
        }
      }
      if(!supplierBank)throw new Error("Compte bénéficiaire introuvable");
      const resolvedSupplierBank=supplierBank;
      for(const [index,flow] of orderFlows.entries()) {
        const lineAmount=index===orderFlows.length-1?amount-allocated:Number((amount*Math.abs(Number(flow.amount))/originalTotal).toFixed(2)); allocated+=lineAmount;
        const response=await fetch("/api/treasury/settings",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"transferOrder",companyId:flow.companyId,cashFlowId:flow.id,companyBankId:companyBank.id,supplierBankId:resolvedSupplierBank.id,amount:lineAmount,purpose,comment,orderDate,internalNumber:internalNumber||undefined,status:shouldPrint?"imprimé":"brouillon"})});
        const result=await response.json().catch(()=>({}));
        if(!response.ok)throw new Error(result.error||"Ordre de virement non enregistré");
        internalNumber=String(result.order?.internalNumber||internalNumber);
        printedReferences.push({reference:flow.reference,operationLabel:flow.operationLabel?.trim()||purpose,amount:lineAmount});
      }
      await Promise.all([refresh(),refreshSettings()]);
      if(!shouldPrint){flash(`Ordre ${internalNumber} enregistre sans impression. Vous restez sur cette page.`);return;}
      setInternalTransferPrintOrder(null); setAccountFundingPrintOrder(null); setSupplierPaymentAdvice(null); setPrintMode("order"); setPrintOrder({internalNumber,company:companies.find(c=>c.id===transferFlow.companyId)?.name??transferFlow.companyId,companyBank,supplierBank:resolvedSupplierBank,amount,purpose,operationLabel:printOperationLabel,comment,orderDate,references:printedReferences});
      printScheduled=true; window.setTimeout(()=>{window.print();transferPrintLock.current=false;setPrintOrder(null);setPrintMode(null);if(thresholdExceeded)flash(`ALERTE - Engagements de virement ${money(committedAfterPrinting)} : plus de 90 % de la tresorerie disponible ${money(companyAvailable)}`);else flash(`Ordre ${internalNumber} imprime. Vous restez sur cette page.`)},150);
    } catch(error) { flash(error instanceof Error?error.message:"Ordre de virement non enregistré"); }
    finally { setBusy(false); if(shouldPrint&&!printScheduled)transferPrintLock.current=false; }
  }
  function openSplit(flow:Flow) {
    const total=Math.abs(Number(flow.amount)); const half=Math.floor(total*50)/100; const firstDate=flow.plannedDate||flow.dueDate; const secondDate=new Date(`${firstDate}T00:00:00`); secondDate.setDate(secondDate.getDate()+30);
    setSplitFlow(flow); setSplitLines([{amount:half,plannedDate:firstDate},{amount:Number((total-half).toFixed(2)),plannedDate:secondDate.toISOString().slice(0,10)}]); setPanel("split");
  }
  async function saveSplit(event:FormEvent<HTMLFormElement>) {
    event.preventDefault(); if(!splitFlow)return;
    const total=splitLines.reduce((sum,line)=>sum+Number(line.amount||0),0); const expected=Math.abs(Number(splitFlow.amount));
    if(splitLines.length<2)return flash("Ajoutez au moins deux fractions");
    if(splitLines.some(line=>line.amount<=0||!line.plannedDate))return flash("Chaque fraction doit avoir un montant et une date");
    if(Math.abs(total-expected)>0.01)return flash(`Le total des fractions doit être égal à ${money(expected)}`);
    const response=await fetch("/api/treasury/split",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({flowId:splitFlow.id,installments:splitLines})});
    if(!response.ok){const result=await response.json().catch(()=>({}));return flash(result.error||"Fractionnement impossible")}
    await refresh(); setSelectedTransferIds([]); setPanel("paymentApprovals"); setSplitFlow(null); flash(`${splitLines.length} échéances de paiement créées`);
  }
  async function cancelSplit(flow:Flow) {
    if(!flow.parentFlowId||!window.confirm(`Annuler le fractionnement de la facture ${flow.reference.split(" · ")[0]} ?`))return;
    const response=await fetch("/api/treasury/split",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({parentFlowId:flow.parentFlowId})});
    if(!response.ok){const result=await response.json().catch(()=>({}));return flash(result.error||"Annulation du fractionnement impossible");}
    await refresh(); setSelectedTransferIds([]); flash("Facture d’origine restaurée");
  }
  function exportReport() {
    const rows=scopedFlows.filter(flow=>!(flow.kind==="fournisseur"&&flow.excluded)).map(flow=>({Société:companyName(flow.companyId),Type:flow.kind==="client"?"Encaissement":"Règlement",Tiers:flow.party,Référence:flow.reference,"Date prévue":flow.plannedDate,Montant:flow.amount,"Pris en compte":flow.excluded?"Non":"Oui",Statut:flow.status}));
    const book=XLSX.utils.book_new(); XLSX.utils.book_append_sheet(book,XLSX.utils.json_to_sheet(rows),"Trésorerie");
    XLSX.writeFile(book,`Moventra ERP-Tresorerie-${companyFilter==="all"?"consolide":companyName(companyFilter)}-${new Date().toISOString().slice(0,10)}.xlsx`);
    flash("Rapport Excel généré");
  }
  function exportIntelligentPlan() {
    const rows=intelligentPlan.rows.map(row=>({Mois:row.label,Mode:row.isCurrentMonth?"Planning réel":"Scénario intelligent","Trésorerie initiale":row.opening,"Objectif mensuel":row.isCurrentMonth?"Non appliqué":row.monthlyTarget,"Encaissements déjà planifiés":row.plannedReceipts,"Complément d’encaissements à prévoir":row.receiptForecast,"Total encaissements du scénario":row.receipts,"Taux de décaissement (%)":row.isCurrentMonth?"Non appliqué":Math.round(row.disbursementRate*100),"Budget décaissements":row.disbursementBudget,"Taux de réserve (%)":row.isCurrentMonth?"Non appliqué":Math.round(row.reserveRate*100),"Réserve calculée":row.reserve,"Prévisions manuelles clients":row.manualReceipts,"Règlements existants":row.scheduledSuppliers,"Prévisions manuelles fournisseurs":row.manualSuppliers,"Complément intelligent":row.complement,"Dépassement du budget décaissements":row.disbursementOverrun,"Total règlements proposé":row.supplierPayments,"Solde projeté":row.balance,"Besoin de financement":row.fundingNeed}));
    const summary=[{Indicateur:"Société",Valeur:companyFilter==="all"?"Vue consolidée":companyName(companyFilter)},{Indicateur:"Date de génération",Valeur:new Date().toLocaleDateString("fr-FR")},{Indicateur:"Trésorerie de départ",Valeur:bankTotal},{Indicateur:"Total des réserves",Valeur:intelligentPlan.reserveTotal},{Indicateur:"Total des compléments fournisseurs",Valeur:intelligentPlan.totalComplements},{Indicateur:"Solde final projeté",Valeur:intelligentPlan.endingBalance},{Indicateur:"Point bas projeté",Valeur:intelligentPlan.lowestBalance},{Indicateur:"Prévisions manuelles incluses",Valeur:intelligentPlan.manualForecastCount}];
    const planSheet=XLSX.utils.json_to_sheet(rows); planSheet["!cols"]=[{wch:18},{wch:18},{wch:18},{wch:20},{wch:18},{wch:22},{wch:26},{wch:21},{wch:30},{wch:22},{wch:24},{wch:18},{wch:22}];
    const summarySheet=XLSX.utils.json_to_sheet(summary); summarySheet["!cols"]=[{wch:34},{wch:24}];
    const supplierRows=smartSupplierReportRows.map(row=>({Société:companyName(row.flow.companyId),Priorité:row.priority,"Rang paramétré":row.policy?.priorityRank||"Libre",Fournisseur:row.supplier,Facture:row.flow.reference,Échéance:row.flow.dueDate,"Retard (jours)":row.overdueDays,"Montant facture":row.invoiceAmount,"Échéancier proposé":row.items.map(item=>`${item.paymentDate} : ${item.proposedAmount}`).join(" | ")||"Non financé","Total planifié":row.planned,"Reste à financer":row.remaining,"Objectif vente / recouvrement":row.fundingObjective,État:row.status,"Politique fournisseur":row.policy?(row.policy.splittable?`${row.policy.maxInstallmentMonths} mois max.`:"Paiement intégral"):"Simulation libre"}));
    const supplierSheet=XLSX.utils.json_to_sheet(supplierRows); supplierSheet["!cols"]=[{wch:22},{wch:28},{wch:18},{wch:13},{wch:13},{wch:12},{wch:18},{wch:15},{wch:18},{wch:20},{wch:15},{wch:38}];
    const analysisRows=[
      {Rubrique:"Diagnostic explicable",Analyse:decisionSupportAnalysis.diagnosis},
      {Rubrique:"Décision fournisseurs",Analyse:decisionSupportAnalysis.supplierStrategy},
      {Rubrique:"Coordination commercial et recouvrement",Analyse:decisionSupportAnalysis.commercialStrategy},
      {Rubrique:"Stockage de la réserve et affectation du gain",Analyse:decisionSupportAnalysis.reserveStrategy},
      ...decisionSupportAnalysis.actions.map((action,index)=>({Rubrique:`Action ${index+1}`,Analyse:action}))
    ];
    const analysisSheet=XLSX.utils.json_to_sheet(analysisRows); analysisSheet["!cols"]=[{wch:42},{wch:140}];
    const fiscalSheet=XLSX.utils.json_to_sheet(smartSupplierFiscalSummary.map(row=>({Exercice:row.exercise,"Nombre de factures":row.invoiceCount,"Total factures":row.invoiceTotal,"Total planifié":row.plannedTotal,"Total non réglé":row.unpaidTotal,"Taux de règlement (%)":row.settlementRate}))); fiscalSheet["!cols"]=[{wch:12},{wch:20},{wch:20},{wch:20},{wch:20},{wch:22}];
    const book=XLSX.utils.book_new(); XLSX.utils.book_append_sheet(book,planSheet,"Plan intelligent"); XLSX.utils.book_append_sheet(book,supplierSheet,"Règlements fournisseurs"); XLSX.utils.book_append_sheet(book,fiscalSheet,"Contrôle par exercice"); XLSX.utils.book_append_sheet(book,analysisSheet,"Analyse décisionnelle"); XLSX.utils.book_append_sheet(book,summarySheet,"Synthèse");
    const scope=companyFilter==="all"?"consolide":companyName(companyFilter); XLSX.writeFile(book,`Plan-intelligent-${scope}-${new Date().toISOString().slice(0,10)}.xlsx`); flash("Plan intelligent téléchargé au format Excel");
  }
  function printSmartPlanReport() {
    document.querySelector<HTMLDetailsElement>(".smart-report-menu")?.removeAttribute("open");
    flushSync(()=>{
      setSmartPlanExpanded(true);
      setPrintMode("smartReport");
    });
    const report=document.querySelector<HTMLElement>(".print-smart-report");
    if(!report){setPrintMode(null);flash("Le rapport n’a pas pu être préparé");return;}
    const printFrame=document.createElement("iframe");
    printFrame.setAttribute("title","Impression du rapport du plan intelligent");
    printFrame.style.cssText="position:fixed;right:0;bottom:0;width:1px;height:1px;border:0;opacity:0;pointer-events:none";
    document.body.appendChild(printFrame);
    const printDocument=printFrame.contentDocument;
    const printWindow=printFrame.contentWindow;
    if(!printDocument||!printWindow){printFrame.remove();setPrintMode(null);flash("Service d’impression indisponible");return;}
    const styles=Array.from(document.head.querySelectorAll('link[rel="stylesheet"],style')).map(node=>node.outerHTML).join("");
    const objectiveCards=`<div class="report-objective-card"><span>Objectif recouvrement</span><b>${money(decisionSupportAnalysis.collectionObjective)}</b><small>Créances échues à transformer en encaissements</small></div><div class="report-objective-card"><span>Objectif commercial encaissé</span><b>${money(decisionSupportAnalysis.salesObjective)}</b><small>Ventes nouvelles encaissées à produire</small></div><div class="report-objective-card"><span>Effort complémentaire total</span><b>${money(additionalCommercialObjective)}</b><small>Recouvrement + ventes encaissées sur l’horizon</small></div>`;
    const reportMarkup=report.outerHTML.replace('<section class="smart-report-kpis">',`<section class="smart-report-kpis">${objectiveCards}`);
    printDocument.open();
    printDocument.write(`<!doctype html><html><head><base href="${window.location.origin}/"><meta charset="utf-8"><title>Rapport Moventra ERP</title>${styles}<style>.print-smart-report{display:block!important;position:static!important;inset:auto!important;width:100%!important;margin:0!important;padding:0!important;overflow:visible!important}.print-smart-report,.print-smart-report *{visibility:visible!important}.smart-report-kpis>.report-objective-card{background:#eaf5ef!important;border-bottom:1px solid #aebdca}.smart-report-kpis>.report-objective-card b{color:#12633f;font-size:12px}</style></head><body>${reportMarkup}</body></html>`);
    printDocument.close();
    setPrintMode(null);
    let printed=false;
    const cleanup=()=>window.setTimeout(()=>printFrame.remove(),500);
    printWindow.addEventListener("afterprint",cleanup,{once:true});
    const triggerPrint=()=>{if(printed)return;printed=true;try{printWindow.focus();printWindow.print();}catch{cleanup();flash("Impossible d’ouvrir l’impression. Utilisez le téléchargement Word.");}};
    window.setTimeout(triggerPrint,150);
  }
  function exportSmartPlanWord() {
    const escapeHtml=(value:unknown)=>String(value??"").replace(/[&<>"']/g,character=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"}[character]||character));
    const generatedAt=new Date();
    const scope=companyFilter==="all"?"Vue consolidée":companyName(companyFilter);
    const recoveryShare=scenarioCashObjective>0?Math.min(1,decisionSupportAnalysis.collectionObjective/scenarioCashObjective):0;
    const monthlyRows=intelligentPlan.rows.map(row=>{const recoveryTarget=moneyValue(row.receiptForecast*recoveryShare);const salesTarget=moneyValue(Math.max(0,row.receiptForecast-recoveryTarget));return `<tr><td>${escapeHtml(row.label)}</td><td>${escapeHtml(money(row.monthlyTarget))}</td><td>${escapeHtml(money(row.plannedReceipts))}</td><td>${escapeHtml(money(recoveryTarget))}</td><td>${escapeHtml(money(salesTarget))}</td><td>${escapeHtml(money(row.receiptForecast))}</td><td>${escapeHtml(money(row.reserve))}</td><td>${escapeHtml(money(row.supplierPayments))}</td><td>${escapeHtml(money(row.balance))}</td></tr>`;}).join("");
    const fiscalRows=smartSupplierFiscalSummary.map(row=>`<tr><td>${escapeHtml(row.exercise)}</td><td>${row.invoiceCount}</td><td>${escapeHtml(money(row.invoiceTotal))}</td><td>${escapeHtml(money(row.plannedTotal))}</td><td>${escapeHtml(money(row.unpaidTotal))}</td><td>${escapeHtml(row.settlementRate.toLocaleString("fr-FR",{maximumFractionDigits:1}))} %</td></tr>`).join("");
    const supplierRows=smartSupplierReportRows.map(row=>{const schedule=row.items.map(item=>`${new Date(`${item.paymentDate}T00:00:00`).toLocaleDateString("fr-FR")} : ${money(item.proposedAmount)}`).join("<br>")||"Non financé";return `<tr><td>${escapeHtml(row.priority)}<br><small>${row.policy?`Rang ${row.policy.priorityRank}`:`Score ${row.score}`}</small></td><td>${escapeHtml(row.supplier)}<br><small>${escapeHtml(row.flow.reference)} · ${escapeHtml(companyName(row.flow.companyId))}</small></td><td>${escapeHtml(new Date(`${row.flow.dueDate}T00:00:00`).toLocaleDateString("fr-FR"))}<br><small>${row.overdueDays?`${row.overdueDays} j de retard`:"Non échue"}</small></td><td>${escapeHtml(money(row.invoiceAmount))}</td><td>${row.policy?`Rang ${row.policy.priorityRank}<br><small>${row.policy.splittable?`${row.policy.maxInstallmentMonths} mois max.`:"Paiement intégral"}</small>`:"Simulation libre"}</td><td>${schedule}</td><td>${escapeHtml(money(row.planned))}</td><td>${escapeHtml(money(row.remaining))}</td><td>${escapeHtml(row.status)}${row.fundingObjective>0?`<br><small>Objectif additionnel : ${escapeHtml(money(row.fundingObjective))}</small>`:""}</td></tr>`;}).join("");
    const analysisActions=decisionSupportAnalysis.actions.map(action=>`<li>${escapeHtml(action)}</li>`).join("");
    const html=`<!DOCTYPE html><html><head><meta charset="utf-8"><title>Rapport du plan intelligent</title><style>@page Section1{size:841.9pt 595.3pt;mso-page-orientation:landscape;margin:28.35pt}div.Section1{page:Section1}body{font-family:Arial,sans-serif;color:#17364a;font-size:9pt}header{border-bottom:2pt solid #08789a;padding-bottom:8pt;margin-bottom:12pt}header h1{font-size:18pt;margin:0 0 3pt}header p{margin:0;color:#5f6f79}.kpis{width:100%;border-collapse:collapse;margin:10pt 0}.kpis td{width:25%;padding:7pt;border:1pt solid #aebdca;background:#eef4f8}.kpis small,.kpis b{display:block}.kpis b{font-size:12pt;margin-top:3pt}h2{font-size:12pt;margin:14pt 0 5pt;color:#075d7a}table.data{width:100%;border-collapse:collapse;page-break-inside:auto}table.data tr{page-break-inside:avoid}table.data th,table.data td{border:1pt solid #b7c4ce;padding:4pt;vertical-align:top;font-size:7.5pt}table.data th{background:#d9e5f3;text-transform:uppercase;font-size:7pt}table.data small{color:#647784}.ai{margin:9pt 0;padding:9pt;border:1pt solid #82a99a;border-left:4pt solid #247351;background:#f0f8f4;page-break-inside:avoid}.ai h2{margin:0 0 5pt;color:#165f43}.ai p{margin:4pt 0;line-height:1.35}.ai ul{margin:5pt 0 0 16pt;padding:0}.ai li{margin:2pt 0}.note{margin-top:10pt;padding:7pt;border-left:3pt solid #e6a23c;background:#fff8e7}.deficit{margin:10pt 0;padding:8pt;border:1pt solid #d3a24e;background:#fff4d6;color:#704b08}.signatures{width:100%;margin-top:18pt}.signatures td{width:33%;height:42pt;border-top:1pt solid #8ca0ad;text-align:center;padding-top:4pt}</style></head><body><div class="Section1"><header><h1>Moventra ERP · Rapport du plan intelligent de trésorerie</h1><p>${escapeHtml(scope)} · Édité le ${escapeHtml(generatedAt.toLocaleString("fr-FR"))} · Indice de confiance ${intelligentPlan.confidence} %</p></header><table class="kpis"><tr><td><small>Objectif additionnel vente / recouvrement</small><b>${escapeHtml(money(additionalCommercialObjective))}</b></td><td><small>Toutes factures fournisseurs</small><b>${escapeHtml(money(smartSupplierReportRows.reduce((sum,row)=>sum+row.invoiceAmount,0)))}</b></td><td><small>Règlements proposés</small><b>${escapeHtml(money(smartSupplierPlan.allocatedTotal))}</b></td><td><small>Reste à financer</small><b>${escapeHtml(money(smartSupplierFundingGap))}</b></td></tr></table><section class="ai"><h2>Analyse IA explicable et recommandation de décision</h2><p><b>Diagnostic.</b> ${escapeHtml(decisionSupportAnalysis.diagnosis)}</p><p><b>Ordre de règlement.</b> ${escapeHtml(decisionSupportAnalysis.supplierStrategy)}</p><p><b>Coordination commercial / recouvrement.</b> ${escapeHtml(decisionSupportAnalysis.commercialStrategy)}</p><p><b>Réserve et gain.</b> ${escapeHtml(decisionSupportAnalysis.reserveStrategy)}</p><b>Conditions de réussite</b><ul>${analysisActions}</ul></section><h2>1. Objectifs commerciaux et de recouvrement</h2><table class="data"><thead><tr><th>Mois</th><th>Objectif total</th><th>Déjà planifié</th><th>Vente / recouvrement additionnel requis</th><th>Réserve</th><th>Règlements fournisseurs</th><th>Solde projeté</th></tr></thead><tbody>${monthlyRows}</tbody></table>${smartSupplierFundingGap>0?`<div class="deficit"><b>Déficit de financement fournisseurs : ${escapeHtml(money(smartSupplierFundingGap))}</b><br>Objectif commercial brut additionnel recommandé : <b>${escapeHtml(money(additionalCommercialObjective))}</b> en ventes encaissées et/ou recouvrement, sur la base du taux moyen de décaissement du scénario.</div>`:""}<h2>2. Plan détaillé de toutes les factures fournisseurs</h2><table class="data"><thead><tr><th>Priorité</th><th>Fournisseur / facture</th><th>Échéance</th><th>Montant</th><th>Politique</th><th>Échéancier</th><th>Planifié</th><th>Reste</th><th>État / objectif</th></tr></thead><tbody>${supplierRows||'<tr><td colspan="9">Aucune facture fournisseur réelle dans cette sélection.</td></tr>'}</tbody></table><div class="note"><b>Consigne de pilotage :</b> le tableau couvre toutes les factures fournisseurs, y compris les lignes non financées, bloquées ou hors calcul. L’objectif additionnel indique les encaissements commerciaux ou de recouvrement nécessaires pour continuer le plan de règlement.</div><table class="signatures"><tr><td>Direction financière</td><td>Responsable commercial / recouvrement</td><td>Validation Direction générale</td></tr></table></div></body></html>`;
    const reportHtml=html
      .replace("Analyse IA explicable et recommandation de décision","Analyse explicable et recommandation de décision")
      .replace("2. Plan détaillé de toutes les factures fournisseurs","2. Annexe exhaustive des factures fournisseurs")
      .replaceAll("Non financé</td>","Non financé / hors horizon</td>")
      .replace("<th>Mois</th><th>Objectif total</th><th>Déjà planifié</th><th>Vente / recouvrement additionnel requis</th><th>Réserve</th><th>Règlements fournisseurs</th><th>Solde projeté</th>","<th>Mois</th><th>Objectif total</th><th>Déjà planifié</th><th>Objectif recouvrement</th><th>Objectif ventes encaissées</th><th>Effort complémentaire total</th><th>Réserve</th><th>Règlements fournisseurs</th><th>Solde projeté</th>")
      .replace("le tableau couvre toutes les factures fournisseurs, y compris les lignes non financées, bloquées ou hors calcul.","le tableau couvre toutes les factures fournisseurs actives, y compris les lignes non financées, bloquées ou hors horizon. Les lignes marquées ignorées sont exclues du rapport et de ses totaux.")
      .replace(`<p>${escapeHtml(scope)} · Édité le ${escapeHtml(generatedAt.toLocaleString("fr-FR"))} · Indice de confiance ${intelligentPlan.confidence} %</p>`,`<p>Payeur(s) : ${escapeHtml(smartPayerNames)} · Imprimé le ${escapeHtml(generatedAt.toLocaleString("fr-FR"))} · Utilisateur : ${escapeHtml(initialUserName)} · Indice de confiance ${intelligentPlan.confidence} %</p>`)
      .replace(/<table class="kpis">[\s\S]*?<\/table>/,`<table class="kpis"><tr><td><small>Objectif recouvrement</small><b>${escapeHtml(money(decisionSupportAnalysis.collectionObjective))}</b><small>Promesses datées puis encaissements bancaires</small></td><td><small>Objectif ventes encaissées</small><b>${escapeHtml(money(decisionSupportAnalysis.salesObjective))}</b><small>Ventes nouvelles réellement encaissées</small></td><td><small>Effort complémentaire total</small><b>${escapeHtml(money(additionalCommercialObjective))}</b><small>À produire sur l'horizon du plan</small></td></tr><tr><td><small>Factures fournisseurs actives</small><b>${escapeHtml(money(smartSupplierInvoiceTotal))}</b><small>${smartSupplierReportRows.length} facture(s)</small></td><td><small>Total non réglé après plan</small><b>${escapeHtml(money(smartSupplierUnpaidTotal))}</b><small>À surveiller jusqu'au paiement bancaire</small></td><td><small>Taux de règlement proposé</small><b>${escapeHtml(smartSupplierSettlementRate.toLocaleString("fr-FR",{maximumFractionDigits:1}))} %</b><small>Planifié / factures actives</small></td></tr></table>`)
      .replace(`</table><section class="ai">`,`</table><h2>Contrôle exhaustif par exercice</h2><table class="data"><thead><tr><th>Exercice</th><th>Factures incluses</th><th>Total factures</th><th>Total planifié</th><th>Total non réglé</th><th>Taux de règlement</th></tr></thead><tbody>${fiscalRows||'<tr><td colspan="6">Aucune facture fournisseur ouverte.</td></tr>'}</tbody></table><p class="note"><b>Contrôle de complétude :</b> ${smartSupplierReportRows.length} facture(s) fournisseur(s) actives sont présentes dans l'annexe détaillée, y compris les factures non financées, bloquées ou hors horizon. Les lignes marquées ignorées sont exclues.</p><section class="ai">`)
      .replace(".kpis td{width:25%;",".kpis td{width:33.333%;")
      .replace("background:#eef4f8","background:#d9e5f3")
      .replace("header h1{font-size:18pt","header h1{font-size:14pt")
      .replace("h2{font-size:12pt;margin:14pt 0 5pt;color:#075d7a}","h2{font-size:9pt;margin:10pt 0 5pt;padding:4pt 6pt;color:#123c56;background:#d9e5f3;border:1pt solid #afc4d5}");
    const payerDetailedReportHtml=companies.reduce((content,company)=>content.replaceAll(` · ${escapeHtml(company.shortName)}</small>`,` · Payeur ${escapeHtml(company.shortName)}</small>`),reportHtml);
    const blob=new Blob(["\ufeff",payerDetailedReportHtml],{type:"application/msword;charset=utf-8"});
    const url=URL.createObjectURL(blob);const link=document.createElement("a");link.href=url;link.download=`Rapport-plan-intelligent-${new Date().toISOString().slice(0,10)}.doc`;document.body.appendChild(link);link.click();link.remove();window.setTimeout(()=>URL.revokeObjectURL(url),1000);flash("Rapport Word généré");
  }
  async function saveSupplierPaymentPolicy(event:FormEvent<HTMLFormElement>,companyId:string,supplierName:string) {
    event.preventDefault(); const data=new FormData(event.currentTarget); const splittable=data.get("splittable")==="true";
    const payload={resource:"supplierPaymentPolicy",companyId,supplierName,priorityRank:Number(data.get("priorityRank")),splittable,maxInstallmentMonths:splittable?Number(data.get("maxInstallmentMonths")):1,paymentDeadline:String(data.get("paymentDeadline")||"")};
    if(!window.confirm(`Enregistrer cette politique de règlement pour ${supplierName} ?\n\nPriorité : ${payload.priorityRank}\nFractionnement : ${splittable?`oui, sur ${payload.maxInstallmentMonths} mois maximum`:"non, paiement intégral"}\nDate limite : ${payload.paymentDeadline?new Date(`${payload.paymentDeadline}T00:00:00`).toLocaleDateString("fr-FR"):"aucune"}`))return;
    setBusy(true);try{const response=await fetch("/api/treasury/settings",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(payload)});const result=await response.json().catch(()=>({}));if(!response.ok)throw new Error(result.error||"Politique non enregistrée");await refreshSettings();flash(`Politique de ${supplierName} enregistrée · le plan intelligent a été recalculé`);}catch(error){flash(error instanceof Error?error.message:"Politique non enregistrée");}finally{setBusy(false);}
  }
  async function removeSupplierPaymentPolicy(policy:SupplierPaymentPolicy) {
    if(!window.confirm(`Supprimer les contraintes de règlement de ${policy.supplierName} ?\n\nCe fournisseur repassera en simulation libre.`))return;
    setBusy(true);try{const response=await fetch("/api/treasury/settings",{method:"DELETE",headers:{"content-type":"application/json"},body:JSON.stringify({resource:"supplierPaymentPolicy",id:policy.id})});const result=await response.json().catch(()=>({}));if(!response.ok)throw new Error(result.error||"Suppression impossible");await refreshSettings();flash(`${policy.supplierName} est désormais simulé librement`);}catch(error){flash(error instanceof Error?error.message:"Suppression impossible");}finally{setBusy(false);}
  }
  async function applySmartSupplierPlan() {
    if(!smartSupplierPlan.allocations.length)return flash("Aucune allocation fournisseur à appliquer");
    if(!window.confirm(`Appliquer ce plan intelligent au planning réel ?\n\n${smartSupplierPlan.byInvoice.size} facture(s) seront replanifiées ou fractionnées. Les fractions proposées seront enregistrées avec leurs dates. Cette action nécessite ensuite le circuit normal de validation des règlements.`))return;
    setBusy(true); let updated=0,split=0,skipped=0;
    try{
      for(const [flowId,rawItems] of smartSupplierPlan.byInvoice){
        const flow=flows.find(item=>item.id===flowId); if(!flow){skipped++;continue;}
        const items=[...rawItems].sort((a,b)=>a.paymentDate.localeCompare(b.paymentDate)); const planned=moneyValue(items.reduce((sum,item)=>sum+item.proposedAmount,0)); const total=moneyValue(Math.abs(Number(flow.amount))); const remainder=moneyValue(Math.max(0,total-planned));
        if(flow.parentFlowId){
          if(items.length===1&&remainder<=.01){const response=await fetch("/api/treasury",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({id:flow.id,plannedDate:items[0].paymentDate})});if(response.ok)updated++;else skipped++;}else skipped++;
          continue;
        }
        const installments=items.map(item=>({amount:item.proposedAmount,plannedDate:item.paymentDate})); const policy=supplierPolicyFor(flow.companyId,flow.party);
        if(remainder>.01&&policy){skipped++;continue;}
        if(remainder>.01){const last=intelligentPlan.rows.at(-1);const deferredDate=last?new Date(last.end.getFullYear(),last.end.getMonth(),5).toISOString().slice(0,10):flow.plannedDate;installments.push({amount:remainder,plannedDate:deferredDate});}
        if(installments.length===1){const response=await fetch("/api/treasury",{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({id:flow.id,plannedDate:installments[0].plannedDate})});if(response.ok)updated++;else skipped++;continue;}
        const response=await fetch("/api/treasury/split",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({flowId:flow.id,installments})});if(response.ok)split++;else skipped++;
      }
      await refresh(); flash(`Plan appliqué · ${updated} facture(s) replanifiée(s) · ${split} facture(s) fractionnée(s)${skipped?` · ${skipped} à revoir`:""}`);
    }catch{flash("Le plan n’a pas pu être appliqué intégralement. Les opérations déjà validées sont conservées.");await refresh();}finally{setBusy(false);}
  }
  function exportSettlementPlan() {
    const rows=visible.map(flow=>{const approval=approvalForFlow(flow.id);return {Société:companyName(flow.companyId),"Client / Fournisseur":flow.party,Type:flow.kind==="client"?"Encaissement":"Règlement",Référence:flow.reference,"Date de facture":flow.invoiceDate||"","Échéance source":flow.dueDate,"Date planifiée":flow.plannedDate,"Reste à payer":flow.amount,"Fournisseur bloqué":flow.blockedSupplier?"Oui":"Non","Pris en compte":flow.excluded?"Non":"Oui","Phase règlement":flow.kind==="fournisseur"?(approval?.status==="pending"?"En validation":approval?.status==="approved"?"Approuvé":approval?.status==="rejected"?"Refusé":"À préparer"):"—",État:flow.excluded?"ignorée":flow.status}});
    const book=XLSX.utils.book_new(); XLSX.utils.book_append_sheet(book,XLSX.utils.json_to_sheet(rows),"Plan des règlements");
    const scope=companyFilter==="all"?"consolide":companyName(companyFilter); XLSX.writeFile(book,`Plan-reglements-${scope}-${filter}-${new Date().toISOString().slice(0,10)}.xlsx`);
    flash("État des règlements téléchargé");
  }
  function exportSettlementHistory() {
    const rows=settlementTransactions.map(transaction=>{const bank=banks.find(item=>item.id===transaction.bankAccountId);return {Date:transaction.transactionDate,Société:companyName(transaction.companyId),Type:transaction.kind==="client"?"Encaissement client":"Règlement fournisseur",Tiers:transaction.party,Référence:transaction.reference,Banque:bank?`${bank.name} · ${bank.accountLabel}`:"Banque inconnue",Montant:transaction.amount,État:transaction.cancelledAt?"Annulé":"Validé","Date d’annulation":transaction.cancelledAt?new Date(transaction.cancelledAt).toLocaleDateString("fr-FR"):"","Motif d’annulation":transaction.cancellationReason||""}});
    const book=XLSX.utils.book_new(); XLSX.utils.book_append_sheet(book,XLSX.utils.json_to_sheet(rows),"État des règlements");
    XLSX.writeFile(book,`Etat-reglements-${settlementHistoryCompany==="all"?"consolide":companyName(settlementHistoryCompany)}-${new Date().toISOString().slice(0,10)}.xlsx`);
    flash("État des règlements téléchargé");
  }

  const transientFinancialPanel=panel==="invoice"||panel==="split"||panel==="editFlow"||panel==="transfer"||panel==="settlement"||panel==="receiptReserve"||panel==="fundAccount";
  const resolvedActiveSection=panel==="forecast"?"forecast"
    :panel==="banks"||panel==="bankHistory"||panel==="fundAccount"?"banks"
    :panel==="import"?"import"
    :panel==="paymentApprovals"?"paymentApprovals"
    :panel==="settlements"||panel==="settlement"||panel==="receiptReserve"?"settlements"
    :panel==="executedTransfers"?"executedTransfers"
    :panel==="accountTransfer"?"accountTransfer"
    :panel==="blockedSuppliers"?"blockedSuppliers"
    :panel==="reserves"?"reserves"
    :panel==="reports"?"reports"
    :panel==="settings"?"settings"
    :view;
  const activeSection=transientFinancialPanel?stableFinancialSection.current:resolvedActiveSection;
  useEffect(()=>{if(!transientFinancialPanel)stableFinancialSection.current=resolvedActiveSection},[resolvedActiveSection,transientFinancialPanel]);
  useEffect(()=>{if(navigationReady&&embedded&&!initialPlanPage&&window.parent!==window)window.parent.postMessage({type:"moventra:financial-navigation",module:"treasury",section:activeSection},window.location.origin)},[activeSection,embedded,initialPlanPage,navigationReady]);
  const sectionCopy:Record<string,{title:string;description:string}>={
    overview:{title:"Vue d’ensemble",description:"Vue consolidée et pilotage séparé de chaque société."},
    planning:{title:"Plan de trésorerie",description:"Prévision glissante, arbitrage des échéances et solde cumulé."},
    forecast:{title:"Prévisions manuelles",description:"Créez et pilotez les encaissements et décaissements futurs."},
    banks:{title:"Banques",description:"Position bancaire, comptes et historique des mouvements."},
    import:{title:"Imports Excel",description:"Actualisez les états clients et fournisseurs depuis vos modèles."},
    paymentApprovals:{title:"Validations règlements",description:"Validez les bénéficiaires, les dates et les demandes de règlement."},
    settlements:{title:"État des règlements",description:"Consultez les encaissements et règlements traités."},
    executedTransfers:{title:"Virements exécutés",description:"Suivez les virements exécutés et leurs justificatifs."},
    accountTransfer:{title:"Virement entre comptes",description:"Pilotez les transferts entre les comptes bancaires de la société."},
    blockedSuppliers:{title:"Fournisseurs bloqués",description:"Gérez les fournisseurs temporairement exclus des règlements."},
    reserves:{title:"Transfert réserve",description:"Gérez les réserves et leur prise en compte dans la trésorerie."},
    reports:{title:"Rapports & exports",description:"Analysez, imprimez et exportez les états de trésorerie."},
    settings:{title:"Paramètres",description:"Configurez les modèles, banques et bénéficiaires."},
  };
  const activeSectionCopy=view==="overview"&&!panel&&initialPlanPage==="real"
    ?{title:"Plan réel de trésorerie",description:"Échéances enregistrées, opérations validées et suivi du circuit de règlement."}
    :view==="overview"&&!panel&&initialPlanPage==="intelligent"
      ?{title:"Plan intelligent de trésorerie",description:"Scénario d’arbitrage, priorités fournisseurs et objectifs de financement."}
      :sectionCopy[activeSection]||sectionCopy.overview;

  return <div className={`app-shell ${embedded?"is-embedded":""}`}>
    <aside className="sidebar">
      <div className="brand"><div className="brand-mark">AT</div><div><strong>Moventra ERP</strong><small>Module actif · Trésorerie & Finance</small></div></div>
      <div className="treasury-back"><button type="button" data-leave-application onClick={returnToMoventra}>← Retour Moventra ERP</button></div><div className="nav-label">Pilotage</div><nav className="nav" aria-label="Navigation principale">
        <button className={activeSection==="overview"?"active":""} onClick={()=>{setView("overview");setPanel(null)}}><span className="nav-icon">⌂</span><span>Vue d’ensemble</span></button><button className={activeSection==="planning"?"active":""} onClick={()=>{setView("planning");setPanel(null)}}><span className="nav-icon">↗</span><span>Plan de trésorerie</span></button><button className={activeSection==="forecast"?"active":""} onClick={()=>setPanel("forecast")}><span className="nav-icon">P</span><span>Prévisions manuelles</span></button><button className={activeSection==="banks"?"active":""} onClick={()=>setPanel("banks")}><span className="nav-icon">B</span><span>Banques</span></button><button className={activeSection==="import"?"active":""} onClick={()=>setPanel("import")}><span className="nav-icon">F</span><span>Imports Excel</span></button>
      </nav><div className="nav-label">Gestion financière</div><nav className="nav"><a className="treasury-nav-link" href="/treasury/collections"><span className="nav-icon">R</span><span>Recouvrement clients</span></a><button className={activeSection==="paymentApprovals"?"active":""} onClick={()=>setPanel("paymentApprovals")}><span className="nav-icon">✓</span><span>Validations règlements</span>{pendingPaymentApprovals.length>0&&<em className="nav-count">{pendingPaymentApprovals.length}</em>}</button><button className={activeSection==="settlements"?"active":""} onClick={openSettlements}><span className="nav-icon">É</span><span>État des règlements</span></button><button className={activeSection==="executedTransfers"?"active":""} onClick={()=>setPanel("executedTransfers")}><span className="nav-icon">V</span><span>Virements exécutés</span></button><button className={activeSection==="accountTransfer"?"active":""} onClick={openAccountTransfer}><span className="nav-icon">⇄</span><span>Virement entre comptes</span></button><button className={activeSection==="blockedSuppliers"?"active":""} onClick={openBlockedSuppliers}><span className="nav-icon">!</span><span>Fournisseurs bloqués</span></button><button className={activeSection==="reserves"?"active":""} onClick={openReserves}><span className="nav-icon">R</span><span>Transfert réserve</span></button><button className={activeSection==="reports"?"active":""} onClick={()=>setPanel("reports")}><span className="nav-icon">↧</span><span>Rapports & exports</span></button><button className={activeSection==="settings"?"active":""} onClick={()=>setPanel("settings")}><span className="nav-icon">⚙</span><span>Paramètres</span></button></nav>
      <div className="sidebar-bottom"><div className="sync-card"><b><i className="sync-dot"/>Modèles de données actifs</b>État client + état fournisseur<br/>{imports[0]?`Dernier import ${new Date(imports[0].importedAt).toLocaleDateString("fr-FR")}`:"Aucun import effectué"}</div><div className="profile"><div className="avatar">AT</div><div><b>Utilisateur connecté</b><span>Accès financier sécurisé</span></div></div></div>
    </aside>

    <main className="main">
      <header className="topbar"><div className="title-wrap"><h1>{activeSectionCopy.title}</h1><p>{activeSectionCopy.description}</p></div><div className="top-actions"><label className="company-filter"><span>Société</span><select value={companyFilter} onChange={e=>{setCompanyFilter(e.target.value);setPlanningSelection(null);setSelectedTransferIds([])}}><option value="all">Vue consolidée</option>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></label>{initialPlanPage?<button className="ghost-btn" type="button" onClick={()=>window.location.assign(`/treasury${embedded?"?embedded=1":""}`)}>← Vue d’ensemble</button>:<><button className="ghost-btn" onClick={()=>setPanel("import")}>⇧ Importer les états</button><button className="ghost-btn" onClick={()=>setPanel("forecast")}>Prévisions manuelles</button><button className="primary-btn" onClick={()=>setPanel("invoice")}>＋ Ajouter une facture</button></>}{panel&&<button type="button" className="settings-top-exit" onClick={()=>panel==="editFlow"?confirmCloseFlowEditor(document.querySelector<HTMLFormElement>(".edit-flow-form")):exitPanel()}>← Sortir</button>}</div></header>
      {preparedTransferAlerts.length>0&&<section className="transfer-threshold-alert" role="alert"><span className="threshold-icon">!</span><div><b>Alerte engagements de virement · seuil de 90 % dépassé</b>{preparedTransferAlerts.map(summary=><p key={summary.companyId}><strong>{companyName(summary.companyId)}</strong> : {money(summary.committed)} engagés sur {money(summary.available)} disponibles · {Number.isFinite(summary.ratio)?`${Math.round(summary.ratio*100)} %`:"besoin de trésorerie"}</p>)}</div><button type="button" onClick={()=>setPanel("paymentApprovals")}>Piloter les engagements</button></section>}
      {view==="overview"&&<>
      {!initialPlanPage&&<>
      <section className="source-strip"><div><span className="source-icon client">C</span><p><b>État client</b><small className="incoming-value">{scopedFlows.filter(f=>f.kind==="client").length} créances · {money(scopedFlows.filter(f=>f.kind==="client").reduce((s,f)=>s+f.amount,0))}</small></p><em>{companyFilter==="all"?"Consolidé":companyName(companyFilter)}</em></div><div><span className="source-icon supplier">F</span><p><b>État fournisseur</b><small className="outgoing-value">{scopedFlows.filter(f=>f.kind==="fournisseur").length} dettes · {money(scopedFlows.filter(f=>f.kind==="fournisseur").reduce((s,f)=>s+f.amount,0))}</small></p><em>{companyFilter==="all"?"Consolidé":companyName(companyFilter)}</em></div><button onClick={()=>setPanel("import")}>Mettre à jour ↗</button></section>

      <section className="metrics" aria-label="Indicateurs clés">
        <div className="metric"><div className="metric-head"><span>Trésorerie bancaire disponible</span><i className="metric-icon">B</i></div><div className="metric-value">{shortMoney(bankTotal)}</div><div className="metric-foot"><span>Brut {shortMoney(grossBankTotal)} · réserve hors calcul {shortMoney(excludedReserveTotal)}</span><button className="inline-action" onClick={openReserves}>Gérer</button></div></div>
        <div className="metric"><div className="metric-head"><span>Créances clients ouvertes</span><i className="metric-icon">↓</i></div><div className="metric-value incoming-value">{shortMoney(totals.incoming)}</div><div className="metric-foot">{scopedFlows.filter(f=>f.kind==="client").length} lignes à encaisser</div></div>
        <div className="metric"><div className="metric-head"><span>Dettes fournisseurs ouvertes</span><i className="metric-icon">↑</i></div><div className="metric-value outgoing-value">{shortMoney(totals.outgoing)}</div><div className="metric-foot">{scopedFlows.filter(f=>f.kind==="fournisseur").length} lignes à régler</div></div>
        <div className="metric"><div className="metric-head"><span>Solde prévisionnel global</span><i className="metric-icon">≈</i></div><div className={`metric-value ${totals.projected<0?"red":""}`}>{shortMoney(totals.projected)}</div><div className="metric-foot"><span className={totals.projected>=0?"up":"down"}>{totals.projected>=0?"Position positive":"Besoin de financement"}</span></div></div>
      </section>
      </>}

      <section className={`dashboard-grid ${initialPlanPage?"treasury-plan-page":""}`}>
        {!initialPlanPage&&<>
        <article className="card dynamic-chart">
          <div className="card-head"><div><h2>Évolution de la trésorerie</h2><p>Solde bancaire réel aujourd’hui, puis projection {chartPeriod==="day"?"journalière":chartPeriod==="week"?"hebdomadaire":"mensuelle"} des mouvements non exécutés</p></div><div className="chart-head-tools"><div className="chart-period" role="group" aria-label="Période du graphique"><button className={chartPeriod==="day"?"active":""} aria-pressed={chartPeriod==="day"} onClick={()=>setChartPeriod("day")}>Journalier</button><button className={chartPeriod==="week"?"active":""} aria-pressed={chartPeriod==="week"} onClick={()=>setChartPeriod("week")}>Semaine</button><button className={chartPeriod==="month"?"active":""} aria-pressed={chartPeriod==="month"} onClick={()=>setChartPeriod("month")}>Mois</button></div><div className="legend"><span><i style={{background:"var(--incoming)"}}/>Encaissements</span><span><i style={{background:"var(--outgoing)"}}/>Règlements</span><span><i style={{background:"#e7a43a"}}/>Solde disponible</span></div></div></div>
          <div className="chart-area"><div className="chart-grid"><span><em>{shortMoney(Math.max(1,...chartData.flatMap(item=>[item.incoming,item.outgoing])))}</em></span><span><em>{shortMoney(Math.max(1,...chartData.flatMap(item=>[item.incoming,item.outgoing]))/2)}</em></span><span><em>0</em></span></div><div className="bars" style={{gridTemplateColumns:`repeat(${chartData.length},minmax(46px,1fr))`}}>{chartData.map((item,index)=><div className={`bar-group ${index===0?"actual-balance":""}`} key={index} title={`${item.label} · Encaissements ${money(item.incoming)} · Règlements ${money(item.outgoing)} · Solde ${money(item.balance)}`}><div className="bar in" style={{height:`${item.inHeight}%`}}/><div className="bar out" style={{height:`${item.outHeight}%`}}/><span className="bar-label">{item.label}</span></div>)}</div><div className="dynamic-balance-line" aria-label="Solde réel puis évolution du solde prévisionnel">{chartData.map((item,index)=><span className={`balance-point ${index===0?"actual":""}`} key={index} style={{left:`${index/(chartData.length-1)*100}%`,top:`${item.balanceY}%`}} title={`${item.label} · Solde ${money(item.balance)}`}/>) }{chartData.slice(0,-1).map((item,index)=><i className="balance-segment" key={index} style={{left:`${index/(chartData.length-1)*100}%`,top:`${item.balanceY}%`,width:`${100/(chartData.length-1)}%`,transform:`rotate(${(chartData[index+1].balanceY-item.balanceY)*.8}deg)`}}/>)}</div></div>
        </article>

        <article className="card"><div className="card-head"><div><h2>Position des banques</h2><p>{companyFilter==="all"?"Toutes sociétés consolidées":companyName(companyFilter)} · soldes réels du jour</p></div><div className="bank-card-actions"><button className="filter-btn" onClick={openBankHistory}>Historique</button><button className="filter-btn" onClick={()=>setPanel("banks")}>Modifier</button></div></div><div className="banks">{scopedBanks.map((bank,i)=>{const bankReserve=scopedReserves.filter(reserve=>reserve.bankAccountId===bank.id&&!reserve.includedInTreasury).reduce((sum,reserve)=>sum+reserve.amount,0);const incomingTransit=accountTransfers.filter(transfer=>transfer.destinationBankId===bank.id&&transfer.status==="in_transit");const incomingTransitTotal=incomingTransit.reduce((sum,transfer)=>sum+Number(transfer.amount),0);const nextReception=incomingTransit.map(transfer=>transfer.receptionDate).sort()[0];return <div className={`bank ${incomingTransitTotal>0?"has-transit":""}`} key={bank.id}><div className={`bank-logo ${["a","b","c"][i%3]}`}>{bankInitials(bank.name)}</div><div className="bank-identity"><b>{bank.name}</b><span>{companyName(bank.companyId)} · {bank.accountLabel}</span>{bankReserve>0&&<small className="reserve-bank-note">Réserve hors trésorerie : {money(bankReserve)}</small>}{incomingTransitTotal>0&&<div className="bank-transit"><strong>Virement en cours</strong><small>+ {money(incomingTransitTotal)} attendu{incomingTransit.length>1?"s":""}{nextReception?` le ${new Date(`${nextReception}T00:00:00`).toLocaleDateString("fr-FR")}`:""}</small><small>Solde prévisionnel après réception : <b>{money(bank.balance+incomingTransitTotal)}</b></small></div>}</div><div className="bank-amount"><strong>{money(bank.balance)}</strong><small>Solde réel aujourd’hui</small><small>{grossBankTotal?Math.round(bank.balance/grossBankTotal*100):0} % du solde brut</small></div></div>})}</div><div className="liquidity"><span>Liquidité disponible après réserves</span><b>{money(bankTotal)}</b></div></article>
        </>}

        <nav className="treasury-plan-selector" aria-label="Choisir le plan de trésorerie"><div>{initialPlanPage&&<button type="button" className="plan-overview-return" onClick={()=>window.location.assign(`/treasury${embedded?"?embedded=1":""}`)}>← Vue d’ensemble</button>}<span>DEUX LECTURES COMPLÉMENTAIRES</span><b>Plans de trésorerie</b></div><button type="button" className={initialPlanPage==="real"?"active":""} aria-pressed={initialPlanPage==="real"} onClick={()=>window.location.assign(`/treasury/plan-reel${embedded?"?embedded=1":""}`)}><em>01</em><span><b>Plan réel</b><small>Ouvrir la page des opérations enregistrées</small></span></button><button type="button" className={initialPlanPage==="intelligent"?"active":""} aria-pressed={initialPlanPage==="intelligent"} onClick={()=>window.location.assign(`/treasury/plan-intelligent${embedded?"?embedded=1":""}`)}><em>02</em><span><b>Plan intelligent</b><small>Ouvrir la page d’arbitrage et de simulation</small></span></button></nav>
        {initialPlanPage==="intelligent"&&<article className="card smart-plan-card" id="intelligent-treasury-plan">
          <div className="smart-plan-head"><div><span className="ai-label">✦ PLAN 02 · MOTEUR PRÉDICTIF EXPLICABLE</span><h2>Plan intelligent de trésorerie</h2><p>Scénario calculé sur les factures réelles clients et fournisseurs, les réserves, les retards et la capacité mensuelle.</p></div><div className="smart-head-actions"><button className={`smart-params-btn ${smartScenarioDirty?"pending":""}`} type="button" aria-expanded={smartSettingsExpanded} onClick={()=>{setSmartPlanExpanded(true);setSmartSettingsExpanded(expanded=>!expanded)}}>{smartSettingsExpanded?"Masquer les paramètres":smartScenarioDirty?"Paramètres à valider":"Paramétrer le scénario"}</button><button type="button" onClick={exportIntelligentPlan}>↓ Excel</button><details className="smart-report-menu"><summary>▤ Rapport</summary><div><button type="button" onClick={printSmartPlanReport}>Imprimer / PDF A4</button><button type="button" onClick={exportSmartPlanWord}>Télécharger Word</button></div></details><button className="smart-collapse-btn" type="button" aria-expanded={smartPlanExpanded} onClick={()=>setSmartPlanExpanded(expanded=>!expanded)}>{smartPlanExpanded?"Réduire":"Afficher"}</button><div className="smart-confidence"><span>Indice de confiance</span><b>{intelligentPlan.confidence} %</b><small>selon la profondeur des données disponibles</small></div></div></div>
          {smartPlanExpanded&&<>
          {smartSettingsExpanded&&<div className="smart-month-settings"><div className="smart-settings-title"><div><b>Paramètres mensuels du scénario</b><span>Le mois en cours utilise exclusivement le planning réel. Le paramétrage commence au mois suivant.</span></div><div className="smart-settings-actions"><em>Réserve 15 % · Décaissements 65 % par défaut</em><button type="button" onClick={applySmartScenario}>✓ Prévisualiser le scénario</button></div></div><div className="smart-settings-grid"><div className="smart-settings-head"><span>Mois</span><span>Objectif (MAD)</span><span>% réserve</span><span>Réserve calculée</span><span>% encaissement à décaisser</span><span>Budget décaissements</span></div>{intelligentPlan.rows.filter(row=>!row.isCurrentMonth).map(row=>{const draft=smartMonthDrafts[row.monthKey]??{target:row.monthlyTarget,reservePercent:Math.round(row.reserveRate*100),disbursementPercent:Math.round(row.disbursementRate*100)};const draftReceipts=Math.max(row.plannedReceipts,draft.target);return <div className={`smart-settings-row ${smartScenarioDirty&&JSON.stringify(draft)!==JSON.stringify(smartMonthSettings[row.monthKey])?"draft-changed":""}`} key={row.monthKey}><b>{row.label}</b><label><input type="number" min="0" step="1000" value={draft.target} onChange={event=>updateSmartMonthSetting(row.monthKey,"target",Number(event.target.value))} aria-label={`Objectif ${row.label}`}/><span>MAD</span></label><label><input type="number" min="0" max="100" step="1" value={draft.reservePercent} onChange={event=>updateSmartMonthSetting(row.monthKey,"reservePercent",Number(event.target.value))} aria-label={`Pourcentage de réserve ${row.label}`}/><span>%</span></label><strong>{money(draft.target*draft.reservePercent/100)}</strong><label><input type="number" min="0" max="100" step="1" value={draft.disbursementPercent} onChange={event=>updateSmartMonthSetting(row.monthKey,"disbursementPercent",Number(event.target.value))} aria-label={`Pourcentage des encaissements à décaisser ${row.label}`}/><span>%</span></label><strong className="smart-disbursement-budget">{money(draftReceipts*draft.disbursementPercent/100)}</strong></div>})}</div>{smartScenarioDirty&&<p className="smart-draft-note">● Modifications en attente : prévisualisez le scénario, puis choisissez de l’enregistrer ou de l’annuler.</p>}</div>}
          {smartScenarioPendingDecision&&<div className="smart-decision-bar" role="status"><div><b>Prévisualisation du plan intelligent prête</b><span>Ce scénario n’est pas encore enregistré et le plan réel n’a pas été modifié.</span></div><div><button type="button" className="save" onClick={saveSmartScenario}>✓ Enregistrer le plan intelligent</button><button type="button" className="cancel" onClick={cancelSmartScenario}>Annuler et utiliser le plan réel</button><button type="button" onClick={()=>{setPrintMode("intelligent");window.setTimeout(()=>window.print(),100)}}>▧ Imprimer avant décision</button></div></div>}
          <div className="smart-assumptions"><span><b>Trésorerie de départ</b>{money(bankTotal)} <small>solde bancaire disponible après réserves</small></span><span><b>{currentMonthLabel} · planning réel</b>{money(intelligentPlan.rows[0]?.plannedReceipts??0)} <small>encaissements réellement planifiés</small></span><span><b>Décaissements réels · {currentMonthLabel}</b>{money(intelligentPlan.rows[0]?.scheduledSuppliers??0)} <small>aucun complément ni réserve automatique</small></span><span><b>Prévisions manuelles</b>{intelligentPlan.manualForecastCount} ligne(s) <small>incluses dans les deux plannings</small></span></div>
          <div className="smart-chart-head"><div className="smart-legend"><span><i className="sales-planned"/>Encaissements planifiés</span><span><i className="sales-forecast"/>Encaissements à prévoir</span><span><i className="suppliers-planned"/>Décaissements planifiés</span><span><i className="suppliers-forecast"/>Décaissements proposés selon le taux mensuel</span><span><i className="reserve"/>Réserve appliquée</span><span><i className="balance"/>Solde projeté</span></div><em>Taux de décaissement paramétrable par mois · Proposés : {money(intelligentPlan.totalComplements)}{intelligentPlan.totalDisbursementOverrun>0?` · Dépassement : ${money(intelligentPlan.totalDisbursementOverrun)}`:""}</em></div>
          <div className="smart-chart-area" key={JSON.stringify(smartMonthSettings)}><div className="smart-chart-grid"><span>{shortMoney(Math.max(1,...intelligentPlan.rows.flatMap(row=>[row.receipts,row.supplierPayments])))}</span><span>{shortMoney(Math.max(1,...intelligentPlan.rows.flatMap(row=>[row.receipts,row.supplierPayments]))/2)}</span><span>0</span></div><div className="smart-bars" style={{gridTemplateColumns:`repeat(${intelligentPlan.rows.length},minmax(90px,1fr))`}}>{intelligentPlan.rows.map(row=><div className={`smart-bar-group ${row.isCurrentMonth?"real-month":""}`} key={row.monthKey} title={row.isCurrentMonth?`${row.label} · Planning réel · Encaissements ${money(row.plannedReceipts)} · Décaissements ${money(row.scheduledSuppliers)} · Solde ${money(row.balance)}`:`${row.label} · Objectif ${money(row.monthlyTarget)} · Encaissements planifiés ${money(row.plannedReceipts)} · Encaissements à prévoir ${money(row.receiptForecast)} · Taux décaissement ${Math.round(row.disbursementRate*100)} % · Décaissements planifiés ${money(row.scheduledSuppliers)} · Décaissements proposés ${money(row.complement)} · Réserve ${money(row.reserve)} · Solde ${money(row.balance)}`}><div className="smart-receipt-stack" style={{height:`${row.receiptsHeight}%`}}><i className="planned" style={{height:`${row.plannedReceiptsShare}%`}}/><i className="forecast" style={{height:`${row.receiptForecastShare}%`}}/></div><div className="smart-supplier-stack" style={{height:`${row.supplierHeight}%`}}><i className="planned" style={{height:`${row.plannedSupplierShare}%`}}/><i className="forecast" style={{height:`${row.supplierForecastShare}%`}}/></div><div className="smart-bar reserve" style={{height:`${row.reserveHeight}%`}}/><strong className="smart-month-objective">{row.isCurrentMonth?"Planning réel":`Objectif ${shortMoney(row.monthlyTarget)} · Décaissement ${Math.round(row.disbursementRate*100)} % · D proposé ${shortMoney(row.complement)}`}</strong><span>{row.label}</span></div>)}</div><svg className="smart-balance-svg" viewBox="0 0 1000 100" preserveAspectRatio="none" aria-label="Évolution du solde projeté"><polyline points={intelligentPlan.rows.map((row,index)=>`${index/(intelligentPlan.rows.length-1)*1000},${row.balanceY}`).join(" ")} /></svg><div className="smart-balance-line">{intelligentPlan.rows.map((row,index)=><span className={`smart-balance-point ${row.balance<0?"negative":""}`} key={row.monthKey} style={{left:`${index/(intelligentPlan.rows.length-1)*100}%`,top:`${row.balanceY}%`}} title={`${row.label} · Solde ${money(row.balance)}`}/>)}</div></div>
          <div className="smart-month-lines"><div className="smart-month-head"><span>Mois</span><span>Encaissements / objectif</span><span>Réserve appliquée</span><span>Décaissements existants</span><span>Décaissement proposé</span><span>Total décaissements</span><span>Solde projeté</span></div>{intelligentPlan.rows.map(row=><div className={row.isCurrentMonth?"real-month-row":row.disbursementOverrun>0?"budget-overrun-row":""} key={row.label}><b>{row.label}{row.isCurrentMonth&&<small>Planning réel</small>}</b><span className="smart-applied-setting">{money(row.plannedReceipts)}<small>{row.isCurrentMonth?"encaissements réellement planifiés":`objectif ${money(row.monthlyTarget)} · à prévoir ${money(row.receiptForecast)}`}</small></span><span className="smart-applied-reserve">{row.isCurrentMonth?"—":`${Math.round(row.reserveRate*100)} %`}<small>{row.isCurrentMonth?"aucune réserve automatique":money(row.reserve)}</small></span><span className="smart-existing-outgoing">{money(row.scheduledSuppliers)}{row.manualSuppliers>0&&<small>dont prévisions manuelles {money(row.manualSuppliers)}</small>}</span>{row.isCurrentMonth?<span className="real-planning-cell"><b>Planning réel</b><small>aucun décaissement proposé</small></span>:<span className={row.disbursementOverrun>0?"budget-overrun":row.complement>0?"generated-line":"no-line"}>{row.disbursementOverrun>0?<><b>Budget dépassé</b><small>de {money(row.disbursementOverrun)} · plafond {Math.round(row.disbursementRate*100)} % : {money(row.disbursementBudget)}</small></>:row.complement>0?<><b>＋ {money(row.complement)}</b><small>plafond {Math.round(row.disbursementRate*100)} % : {money(row.disbursementBudget)}</small></>:<><b>Aucun complément</b><small>plafond {Math.round(row.disbursementRate*100)} % : {money(row.disbursementBudget)}</small></>}</span>}<strong className="outgoing-value">{money(row.supplierPayments)}</strong><strong className={row.balance<0?"outgoing-value":"incoming-value"}>{money(row.balance)}</strong></div>)}</div>
          <section className="smart-supplier-plan" aria-label="Plan intelligent des règlements fournisseurs"><header><div><span>ALLOCATION SUR FACTURES RÉELLES</span><h3>Plan proposé des règlements fournisseurs</h3><p>Les contraintes enregistrées sont prioritaires. Les fournisseurs sans politique restent simulés librement selon l’ancienneté, l’équité et la liquidité disponible.</p></div><div className="smart-supplier-header-actions"><button type="button" className="policy-toggle" onClick={()=>setSmartSupplierPoliciesExpanded(value=>!value)}>⚙ {smartSupplierPoliciesExpanded?"Masquer les paramètres":"Paramètres fournisseurs"}</button><button type="button" disabled={busy||!smartSupplierPlan.allocations.length} onClick={()=>void applySmartSupplierPlan()}>{busy?"Application…":"✓ Appliquer au plan réel"}</button></div></header>{smartSupplierPoliciesExpanded&&<section className="supplier-policy-panel"><header><div><b>Politiques de règlement par fournisseur</b><span>Le rang 1 est traité avant le rang 2. Sans politique enregistrée, le moteur conserve toute sa liberté de simulation.</span></div><div className="supplier-policy-panel-actions"><button type="button" onClick={()=>window.location.assign(`/treasury/supplier-policies${embedded?"?embedded=1":""}`)}>⛶ Agrandir</button><strong>{supplierPolicies.length} politique(s) active(s)</strong></div></header><div className="supplier-policy-table"><div className="head"><span>Fournisseur</span><span>Priorité</span><span>Fractionnable</span><span>Durée maximale</span><span>Date limite</span><span>Action</span></div>{supplierPolicyCandidates.map((candidate,index)=>{const policy=supplierPolicyFor(candidate.companyId,candidate.supplierName);return <form key={`${candidate.companyId}-${candidate.supplierName}`} onSubmit={event=>void saveSupplierPaymentPolicy(event,candidate.companyId,candidate.supplierName)} className={policy?"configured":"free"}><span><b>{candidate.supplierName}</b><small>{companyName(candidate.companyId)} · {policy?"politique active":"simulation libre"}</small></span><label><span>Rang</span><input name="priorityRank" type="number" min="1" max="999" defaultValue={policy?.priorityRank||index+1} required/></label><label><span>Mode</span><select name="splittable" defaultValue={String(policy?.splittable??true)}><option value="true">Oui</option><option value="false">Non · intégral</option></select></label><label><span>Mois</span><input name="maxInstallmentMonths" type="number" min="1" max="24" defaultValue={policy?.maxInstallmentMonths||3} required/></label><label><span>Au plus tard</span><input name="paymentDeadline" type="date" defaultValue={policy?.paymentDeadline||""}/></label><span className="policy-row-actions"><button type="submit" disabled={busy}>{policy?"Mettre à jour":"Enregistrer"}</button>{policy&&<button type="button" className="free-policy" disabled={busy} onClick={()=>void removeSupplierPaymentPolicy(policy)}>Simulation libre</button>}</span></form>})}</div></section>}<div className="smart-supplier-kpis"><span><small>Factures à arbitrer</small><b>{money(smartSupplierPlan.invoiceTotal)}</b></span><span><small>Règlements proposés</small><b>{money(smartSupplierPlan.allocatedTotal)}</b></span><span><small>Fournisseurs servis</small><b>{smartSupplierPlan.servedSupplierCount} / {smartSupplierPlan.supplierCount}</b></span><span><small>Factures fractionnées</small><b>{smartSupplierPlan.fractionCount}</b></span><span className={smartSupplierPlan.unallocatedTotal>0?"attention":""}><small>Solde reporté</small><b>{money(smartSupplierPlan.unallocatedTotal)}</b></span></div>{smartSupplierPlan.blockedTotal>0&&<p className="smart-blocked-note">! {money(smartSupplierPlan.blockedTotal)} de factures de fournisseurs bloqués sont exclues de la proposition.</p>}<div className="smart-supplier-table"><div className="head"><span>Priorité</span><span>Fournisseur / facture</span><span>Échéance</span><span>Montant facture</span><span>Échéancier proposé</span><span>Total planifié</span><span>Reste reporté</span><span>Décision</span></div>{[...smartSupplierPlan.byInvoice.entries()].map(([flowId,items])=>{const first=items[0],planned=items.reduce((sum,item)=>sum+item.proposedAmount,0),remaining=Math.max(0,first.invoiceAmount-planned),policy=supplierPolicyFor(first.companyId,flows.find(flow=>flow.id===flowId)?.party||first.supplier);return <div className="row" key={flowId}><span><em className={`priority-${first.priority.toLowerCase()}`}>{first.priority}</em><small>{policy?`rang ${policy.priorityRank}`:`score ${first.score}`}</small></span><span><b>{first.supplier}</b><small>{first.reference} · {companyName(first.companyId)}</small></span><span><b>{new Date(`${first.dueDate}T00:00:00`).toLocaleDateString("fr-FR")}</b><small>{first.overdueDays?`${first.overdueDays} j de retard`:"non échue"}</small></span><strong>{money(first.invoiceAmount)}</strong><span className="installment-chips">{items.map((item,index)=><i key={`${item.monthKey}-${index}`}><b>{new Date(`${item.paymentDate}T00:00:00`).toLocaleDateString("fr-FR",{day:"2-digit",month:"short"})}</b>{money(item.proposedAmount)}</i>)}</span><strong className="incoming-value">{money(planned)}</strong><strong className={remaining>0?"outgoing-value":"incoming-value"}>{money(remaining)}</strong><span><b>{remaining>0&&policy?"Contrainte à arbitrer":items.length>1||remaining>0?"Fractionner":"Replanifier"}</b><small>{first.reason}</small></span></div>})}{!smartSupplierPlan.allocations.length&&<p className="empty-state">Aucune capacité de règlement disponible avec les paramètres actuels. Augmentez l’objectif d’encaissement ou le taux de décaissement.</p>}</div><footer><span>Une facture contrainte incomplètement financée n’est jamais appliquée automatiquement. Elle reste à arbitrer afin de respecter la durée et la date limite.</span><b>Solde intelligent projeté : {money(smartSupplierPlan.projectedCash)}</b></footer></section>
          <div className="smart-fiscal-live-summary"><strong>Totaux des factures fournisseurs par exercice</strong><div>{smartSupplierFiscalSummary.map(row=><span key={row.exercise}><b>Exercice {row.exercise}</b><em>{money(row.invoiceTotal)}</em><small>{row.invoiceCount} facture(s) · {money(row.unpaidTotal)} non réglés</small></span>)}</div><p><b>Total général : {money(smartSupplierInvoiceTotal)}</b><b>Total non réglé : {money(smartSupplierUnpaidTotal)}</b><b>Couverture du plan : {smartSupplierSettlementRate.toLocaleString("fr-FR",{maximumFractionDigits:1})} %</b></p></div>
          <div className={`smart-recommendation ${intelligentPlan.lowestBalance<0?"attention":"covered"}`}><b>{intelligentPlan.lowestBalance<0?"Besoin de financement détecté":"Scénario finançable"}</b><span>{intelligentPlan.lowestBalance<0?`Le point bas atteint ${money(intelligentPlan.lowestBalance)}. Les lignes complémentaires restent des propositions et doivent être arbitrées avant validation.`:"Le solde reste positif après l’application des objectifs et des taux de réserve définis mois par mois."}</span></div>
          {printMode==="intelligent"&&<article className="print-intelligent"><header><div><strong>Moventra ERP</strong><span>Plan intelligent de trésorerie</span></div><time>{new Date().toLocaleDateString("fr-FR")}</time></header><h1>Scénario mensuel · {companyFilter==="all"?"Vue consolidée":companyName(companyFilter)}</h1><p>Trésorerie initiale : <b>{money(bankTotal)}</b> · Objectifs et réserves paramétrés mois par mois · Prévisions manuelles incluses.</p><table><thead><tr><th>Mois</th><th>Objectif</th><th>Encaissements planifiés</th><th>Encaissements à prévoir</th><th>% décaissement</th><th>Budget décaissements</th><th>% réserve</th><th>Réserve</th><th>Règlements existants</th><th>Complément intelligent</th><th>Dépassement</th><th>Total règlements</th><th>Solde final</th></tr></thead><tbody>{intelligentPlan.rows.map(row=><tr key={row.label}><td>{row.label}</td><td>{money(row.monthlyTarget)}</td><td>{money(row.plannedReceipts)}</td><td>{money(row.receiptForecast)}</td><td>{Math.round(row.disbursementRate*100)} %</td><td>{money(row.disbursementBudget)}</td><td>{Math.round(row.reserveRate*100)} %</td><td>{money(row.reserve)}</td><td>{money(row.scheduledSuppliers)}</td><td>{money(row.complement)}</td><td>{money(row.disbursementOverrun)}</td><td>{money(row.supplierPayments)}</td><td>{money(row.balance)}</td></tr>)}</tbody></table><footer>Décaissements proposés = max(0, taux mensuel × encaissements − décaissements réels existants). Tout dépassement réel est signalé.</footer></article>}
          {printMode==="smartReport"&&<article className="print-smart-report"><header><div><strong>Moventra ERP</strong><span>Rapport du plan intelligent de trésorerie</span></div><div><b>Payeur(s) : {smartPayerNames}</b><time>Imprimé le {new Date().toLocaleString("fr-FR")} · Utilisateur : {initialUserName}</time></div></header><section className="smart-report-kpis"><div><span>Total des factures ouvertes · tous exercices</span><b>{money(smartSupplierInvoiceTotal)}</b><small>Dont {money(smartSupplierPlan.allocatedTotal)} planifiés · {smartSupplierReportRows.length} facture(s)</small></div><div><span>Total des factures non réglées</span><b>{money(smartSupplierUnpaidTotal)}</b><small>Solde restant après ce plan</small></div><div><span>Pourcentage de règlement par ce plan</span><b>{smartSupplierSettlementRate.toLocaleString("fr-FR",{maximumFractionDigits:1})} %</b><small>Montant planifié / factures ouvertes</small></div></section><section className="smart-report-fiscal"><h2>Contrôle exhaustif par exercice</h2><table><thead><tr><th>Exercice</th><th>Factures incluses</th><th>Total factures</th><th>Total planifié</th><th>Total non réglé</th><th>Taux de règlement</th></tr></thead><tbody>{smartSupplierFiscalSummary.map(row=><tr key={row.exercise}><td>{row.exercise}</td><td>{row.invoiceCount}</td><td>{money(row.invoiceTotal)}</td><td>{money(row.plannedTotal)}</td><td>{money(row.unpaidTotal)}</td><td>{row.settlementRate.toLocaleString("fr-FR",{maximumFractionDigits:1})} %</td></tr>)}{!smartSupplierFiscalSummary.length&&<tr><td colSpan={6}>Aucune facture fournisseur ouverte.</td></tr>}</tbody></table><p><b>Contrôle de complétude :</b> {smartSupplierReportRows.length} facture(s) fournisseur(s) sur {smartSupplierReportRows.length} sont présentes dans l’annexe détaillée, y compris les factures non financées, bloquées, hors calcul ou hors horizon.</p></section><section className="smart-ai-analysis"><h2>Analyse explicable et recommandation de décision</h2><p><b>Diagnostic.</b> {decisionSupportAnalysis.diagnosis}</p><p><b>Ordre de règlement.</b> {decisionSupportAnalysis.supplierStrategy}</p><p><b>Coordination commercial / recouvrement.</b> {decisionSupportAnalysis.commercialStrategy}</p><p><b>Réserve et gain.</b> {decisionSupportAnalysis.reserveStrategy}</p><b>Conditions de réussite</b><ol>{decisionSupportAnalysis.actions.map((action,index)=><li key={index}>{action}</li>)}</ol></section><h2>1. Objectifs commerciaux et de recouvrement</h2><p className="report-intro">Les montants « à réaliser » constituent l’objectif de vente encaissée ou de recouvrement complémentaire nécessaire au financement du plan fournisseur.</p><table className="smart-report-monthly"><thead><tr><th>Mois</th><th>Objectif total</th><th>Déjà planifié</th><th>À réaliser par vente / recouvrement</th><th>Réserve</th><th>Règlements fournisseurs</th><th>Solde projeté</th></tr></thead><tbody>{intelligentPlan.rows.map(row=><tr key={row.monthKey}><td>{row.label}</td><td>{money(row.monthlyTarget)}</td><td>{money(row.plannedReceipts)}</td><td className={row.receiptForecast>0?"report-target":""}>{money(row.receiptForecast)}</td><td>{money(row.reserve)}</td><td>{money(row.supplierPayments)}</td><td>{money(row.balance)}</td></tr>)}</tbody></table>{smartSupplierFundingGap>0&&<section className="smart-report-deficit"><b>Déficit de financement fournisseurs : {money(smartSupplierFundingGap)}</b><span>Objectif commercial brut additionnel recommandé : <strong>{money(additionalCommercialObjective)}</strong> en ventes encaissées et/ou recouvrement pour poursuivre le plan.</span></section>}<h2>2. Annexe exhaustive des factures fournisseurs</h2><table className="smart-report-suppliers"><thead><tr><th>Priorité</th><th>Fournisseur / facture</th><th>Échéance</th><th>Montant</th><th>Politique</th><th>Échéancier proposé</th><th>Planifié</th><th>Reste</th><th>État / objectif</th></tr></thead><tbody>{smartSupplierReportRows.map(row=><tr key={row.flow.id}><td><b>{row.priority}</b><small>Exercice {String(row.flow.invoiceDate||row.flow.dueDate).slice(0,4)} · {row.policy?`Rang ${row.policy.priorityRank}`:`Score ${row.score}`}</small></td><td><b>{row.supplier}</b><small>{row.flow.reference} · Payeur {companyName(row.flow.companyId)}</small></td><td>{new Date(`${row.flow.dueDate}T00:00:00`).toLocaleDateString("fr-FR")}<small>{row.overdueDays?`${row.overdueDays} j de retard`:"Non échue"}</small></td><td>{money(row.invoiceAmount)}</td><td>{row.policy?`Rang ${row.policy.priorityRank}`:"Libre"}<small>{row.policy?(row.policy.splittable?`${row.policy.maxInstallmentMonths} mois max.`:"Paiement intégral"):"Simulation automatique"}</small></td><td>{row.items.length?row.items.map((item,index)=><span className="report-installment" key={`${item.paymentDate}-${index}`}>{new Date(`${item.paymentDate}T00:00:00`).toLocaleDateString("fr-FR")} · {money(item.proposedAmount)}</span>):<span>Non financé / hors horizon</span>}</td><td>{money(row.planned)}</td><td>{money(row.remaining)}</td><td><b>{row.status}</b>{row.fundingObjective>0&&<small>Objectif additionnel : {money(row.fundingObjective)}</small>}</td></tr>)}{!smartSupplierReportRows.length&&<tr><td colSpan={9}>Aucune facture fournisseur réelle dans cette sélection.</td></tr>}</tbody></table><aside><b>Consigne de pilotage</b><span>L’annexe couvre la totalité des factures fournisseurs ouvertes de la sélection, y compris les lignes non financées, bloquées, hors calcul ou situées au-delà de l’horizon du scénario. Ces lignes restent visibles avec leur montant non réglé.</span></aside><footer><span>Indice de confiance : {intelligentPlan.confidence} %</span><span>Direction financière</span><span>Commercial / Recouvrement</span><span>Direction générale</span></footer></article>}
          </>}
        </article>}

        {initialPlanPage==="real"&&<article className="card schedule-card" id="real-settlement-plan">
          <div className="card-head"><div><span className="real-plan-label">PLAN 01 · DONNÉES COMPTABLES RÉELLES</span><h2>Plan réel des règlements</h2><p>Factures clients et fournisseurs enregistrées, dates planifiées et état réel du circuit de validation.</p></div><div className="schedule-actions"><div className="schedule-export"><button className="group-transfer-btn" disabled={selectedTransferFlows.length<2} onClick={openGroupedTransfer}>Virement groupé ({selectedTransferFlows.length})</button><button onClick={exportSettlementPlan}>↓ Excel</button><button onClick={()=>{setPrintMode("schedule");window.setTimeout(()=>window.print(),100)}}>▧ Imprimer le plan réel</button></div><div className="schedule-tools"><button onClick={()=>setFilter("tous")} className={`filter-btn ${filter==="tous"?"active":""}`}>Tous</button><button onClick={()=>setFilter("client")} className={`filter-btn ${filter==="client"?"active":""}`}>Clients</button><button onClick={()=>setFilter("fournisseur")} className={`filter-btn ${filter==="fournisseur"?"active":""}`}>Fournisseurs</button></div></div></div>
          <div className="party-filter-bar"><div className="schedule-filter-stack"><div className="schedule-company-filter" aria-label="Filtrer le plan par société"><button className={companyFilter==="all"?"active":""} onClick={()=>setCompanyFilter("all")}>Toutes sociétés</button>{companies.map(company=><button className={companyFilter===company.id?"active":""} onClick={()=>setCompanyFilter(company.id)} key={company.id}>{company.shortName}</button>)}</div><div className="party-filter-input"><span aria-hidden="true">⌕</span><input type="search" list="treasury-parties" value={partySearch} onChange={event=>setPartySearch(event.target.value)} placeholder="Client, fournisseur, référence ou montant…" aria-label="Rechercher par client, fournisseur, référence ou montant"/><datalist id="treasury-parties">{partyOptions.map(party=><option value={party} key={party}/>)}</datalist>{partySearch&&<button type="button" onClick={()=>setPartySearch("")} aria-label="Effacer la recherche">×</button>}</div><div className="schedule-date-filter"><span>Date planifiée</span><label>Du <input type="date" value={scheduleDateFrom} max={scheduleDateTo||undefined} onChange={event=>setScheduleDateFrom(event.target.value)} aria-label="Date planifiée de début"/></label><label>Au <input type="date" value={scheduleDateTo} min={scheduleDateFrom||undefined} onChange={event=>setScheduleDateTo(event.target.value)} aria-label="Date planifiée de fin"/></label>{(scheduleDateFrom||scheduleDateTo)&&<button type="button" onClick={()=>{setScheduleDateFrom("");setScheduleDateTo("")}} aria-label="Effacer la plage de dates">×</button>}</div><div className="schedule-advanced-filters"><div className="schedule-amount-range"><span>Montant</span><label>Min <input type="number" min="0" step="100" value={scheduleAmountMin} onChange={event=>setScheduleAmountMin(event.target.value)} placeholder="0" aria-label="Montant minimum"/></label><label>Max <input type="number" min="0" step="100" value={scheduleAmountMax} onChange={event=>setScheduleAmountMax(event.target.value)} placeholder="Sans limite" aria-label="Montant maximum"/></label></div><label>Type<select value={filter} onChange={event=>setFilter(event.target.value as "tous"|"client"|"fournisseur")}><option value="tous">Tous les types</option><option value="client">Encaissements</option><option value="fournisseur">Règlements</option></select></label><label>État<select value={scheduleStatusFilter} onChange={event=>setScheduleStatusFilter(event.target.value)}><option value="all">Tous les états</option><option value="planned">Planifié</option><option value="late">En retard</option><option value="pending">À valider</option><option value="approved">Approuvé</option><option value="printed">Ordre imprimé</option><option value="rejected">Refusé</option><option value="ignored">Ignoré</option></select></label><label>Paiement<select value={schedulePaymentFilter} onChange={event=>setSchedulePaymentFilter(event.target.value)}><option value="all">Toutes les étapes</option><option value="receipt">Encaissement à valider</option><option value="prepare">Règlement à préparer</option><option value="validation">En validation</option><option value="to_print">Ordre à imprimer</option><option value="printed">Ordre imprimé</option><option value="forecast">Prévision manuelle</option></select></label><label>Prise en compte<select value={scheduleIncludedFilter} onChange={event=>setScheduleIncludedFilter(event.target.value)}><option value="all">Tous</option><option value="included">Pris en compte</option><option value="excluded">Non pris en compte</option></select></label><label>Sens du tri<select value={scheduleDateSort} onChange={event=>setScheduleDateSort(event.target.value as "oldest"|"newest")}><option value="oldest">Croissant / ancien</option><option value="newest">Décroissant / récent</option></select></label>{(filter!=="tous"||scheduleAmountMin||scheduleAmountMax||scheduleStatusFilter!=="all"||schedulePaymentFilter!=="all"||scheduleIncludedFilter!=="all"||scheduleDateSort!=="oldest"||scheduleSortField!=="plannedDate")&&<button className="schedule-clear-filters" type="button" onClick={()=>{setFilter("tous");setScheduleAmountMin("");setScheduleAmountMax("");setScheduleStatusFilter("all");setSchedulePaymentFilter("all");setScheduleIncludedFilter("all");setScheduleDateSort("oldest");setScheduleSortField("plannedDate")}}>Effacer</button>}</div></div><span><b>{visible.length}</b> résultat{visible.length>1?"s":""}</span></div>
          <div className="schedule-filter-presets"><div><b>Modèles de filtres</b><span>Réutilisez rapidement une combinaison de critères.</span></div><select value={selectedScheduleFilterId} onChange={event=>setSelectedScheduleFilterId(event.target.value)} aria-label="Choisir un modèle de filtre"><option value="">Choisir un modèle enregistré</option>{savedScheduleFilters.map(preset=><option value={preset.id} key={preset.id}>{preset.name}</option>)}</select><button type="button" className="apply-filter-preset-btn" disabled={!selectedScheduleFilterId} onClick={()=>applyScheduleFilterPreset(selectedScheduleFilterId)}>✓ Appliquer le modèle</button><button type="button" className="save-filter-preset-btn" onClick={saveScheduleFilterPreset}>＋ Enregistrer ce filtre</button><button type="button" className="delete-filter-preset-btn" disabled={!selectedScheduleFilterId} onClick={deleteScheduleFilterPreset}>Supprimer le modèle</button></div>
          <div className="table-wrap"><table data-datagrid="off"><colgroup><col style={{width:"90px"}}/><col style={{width:"265px"}}/><col style={{width:"95px"}}/><col style={{width:"120px"}}/><col style={{width:"105px"}}/><col style={{width:"105px"}}/><col style={{width:"120px"}}/><col style={{width:"120px"}}/><col style={{width:"90px"}}/><col style={{width:"95px"}}/><col style={{width:"280px"}}/></colgroup><thead><tr><th>Société</th><th>Expéditeur → Récepteur</th><th>Type</th><th aria-sort={scheduleSortField==="reference"?(scheduleDateSort==="oldest"?"ascending":"descending"):"none"}><button type="button" className={scheduleSortField==="reference"?"active":""} onClick={()=>toggleScheduleSort("reference")}>Facture <i>{scheduleSortIcon("reference")}</i></button></th><th aria-sort={scheduleSortField==="invoiceDate"?(scheduleDateSort==="oldest"?"ascending":"descending"):"none"}><button type="button" className={scheduleSortField==="invoiceDate"?"active":""} onClick={()=>toggleScheduleSort("invoiceDate")}>Date <i>{scheduleSortIcon("invoiceDate")}</i></button></th><th aria-sort={scheduleSortField==="dueDate"?(scheduleDateSort==="oldest"?"ascending":"descending"):"none"}><button type="button" className={scheduleSortField==="dueDate"?"active":""} onClick={()=>toggleScheduleSort("dueDate")}>Échéance <i>{scheduleSortIcon("dueDate")}</i></button></th><th aria-sort={scheduleSortField==="plannedDate"?(scheduleDateSort==="oldest"?"ascending":"descending"):"none"}><button type="button" className={scheduleSortField==="plannedDate"?"active":""} onClick={()=>toggleScheduleSort("plannedDate")}>Planifié <i>{scheduleSortIcon("plannedDate")}</i></button></th><th aria-sort={scheduleSortField==="amount"?(scheduleDateSort==="oldest"?"ascending":"descending"):"none"}><button type="button" className={scheduleSortField==="amount"?"active":""} onClick={()=>toggleScheduleSort("amount")}>Reste à payer <i>{scheduleSortIcon("amount")}</i></button></th><th title="Prise en compte dans le calcul">Calcul</th><th>État</th><th>Paiement</th></tr></thead><tbody>{pagedVisible.map(flow=><tr key={flow.id} className={`${flow.excluded?"excluded-row":""} ${selectedTransferIds.includes(flow.id)?"transfer-selected":""} ${canEditFlow(flow)?"editable-flow-row":"locked-flow-row"}`} role="button" tabIndex={0} title={canEditFlow(flow)?"Cliquer pour consulter et modifier":"Modification verrouillée après validation hiérarchique"} onClick={event=>{if((event.target as HTMLElement).closest("button,input,select,a,label"))return;openFlowEditor(flow)}} onKeyDown={event=>{if(event.key==="Enter")openFlowEditor(flow)}}>
            <td><span className={`company-badge company-${flow.companyId}`}>{companyName(flow.companyId)}</span></td>
            <td>{(()=>{const savedOrder=transferOrders.filter(order=>order.cashFlowId===flow.id&&(order.status==="brouillon"||order.status==="imprimé")).sort((a,b)=>b.id-a.id)[0];const senderBank=savedOrder?banks.find(bank=>bank.id===savedOrder.companyBankId):banks.find(bank=>bank.companyId===flow.companyId&&bank.isPrimary)||banks.find(bank=>bank.companyId===flow.companyId);const receiverBank=savedOrder?supplierBanks.find(bank=>bank.id===savedOrder.supplierBankId):undefined;const receiverName=flow.beneficiaryName||receiverBank?.beneficiaryName||flow.party;return <div className="schedule-transfer-parties"><div className="schedule-transfer-party sender"><em>Expéditeur</em><b>{companyName(flow.companyId)}</b><small>{senderBank?`${senderBank.name} · ${senderBank.accountLabel}`:"Compte source à sélectionner"}</small></div><span aria-hidden="true">→</span><div className="schedule-transfer-party receiver"><em>Récepteur</em><b>{receiverName}{flow.blockedSupplier&&<i className="blocked-supplier-badge">Fournisseur bloqué</i>}</b><small>{receiverBank?`${receiverBank.bankName} · RIB ${receiverBank.rib}`:flow.party}</small></div></div>})()}</td>
            <td><span className={`type-badge ${flow.kind==="client"?"in":"out"}`}>{flow.kind==="client"?"Encaissement":"Règlement"}</span></td>
            <td className="invoice-reference-cell"><b>{flow.reference}</b><small>{flow.parentFlowId?"Fraction de facture":"Facture source"}</small></td>
            <td>{flow.invoiceDate?new Date(`${flow.invoiceDate}T00:00:00`).toLocaleDateString("fr-FR",{day:"2-digit",month:"short",year:"numeric"}):<span className="not-applicable">—</span>}</td>
            <td>{new Date(`${flow.dueDate}T00:00:00`).toLocaleDateString("fr-FR",{day:"2-digit",month:"short",year:"numeric"})}</td>
            <td><input className="date-edit" type="date" aria-label={`Date planifiée ${flow.party}`} value={flow.plannedDate} onChange={e=>changeDate(flow.id,e.target.value)}/>{flow.manualOverride&&<span className="manual-mark">● Ajustée manuellement</span>}</td>
            <td><span className={`amount ${flow.kind==="client"?"in":"out"}`}>{flow.kind==="client"?"+ ":"− "}{money(flow.amount)}</span></td>
            <td><button className={`calc-toggle ${flow.excluded?"off":"on"}`} onClick={()=>toggleExcluded(flow)} aria-label={flow.excluded?"Réintégrer au calcul":"Retirer du calcul"}><i/><span>{flow.excluded?"Non":"Oui"}</span></button></td>
            <td>{(()=>{const approval=flow.kind==="fournisseur"?approvalForFlow(flow.id):undefined;const printedOrder=flow.kind==="fournisseur"?printedOrderForFlow(flow.id):undefined;const fallbackStatus=flow.kind==="fournisseur"&&flow.isForecast?"planifiée":flow.status;const label=flow.excluded?"ignorée":printedOrder?"ordre imprimé":approval?.status==="pending"?"à valider":approval?.status==="approved"?"approuvé":approval?.status==="rejected"?"refusé":fallbackStatus;const tone=flow.excluded?"neutral":printedOrder?"ok":approval?.status==="pending"?"soon":approval?.status==="approved"?"ok":approval?.status==="rejected"?"late":fallbackStatus==="planifié"||fallbackStatus==="planifiée"?"ok":fallbackStatus==="en retard"?"late":"soon";return <span className={`status ${tone}`} title={printedOrder?.internalNumber||undefined}>{label}</span>})()}</td>
            <td>{flow.excluded?<button className="excluded-payment-action" onClick={()=>toggleExcluded(flow)}><b>Hors calcul</b><small>Réintégrer la ligne</small></button>:flow.kind==="fournisseur"?(()=>{const approval=approvalForFlow(flow.id);const printedOrder=printedOrderForFlow(flow.id);return <div className="transfer-row-actions">{approval?.status==="approved"?printedOrder?<><span className="schedule-printed-order"><b>✓ Ordre imprimé</b><small>{printedOrder.internalNumber}<br/>{new Date(printedOrder.createdAt).toLocaleDateString("fr-FR")}</small></span><button className="transfer-btn reprint" onClick={()=>reprintTransferOrder(printedOrder)}>Réimprimer</button><button className="settle-btn outgoing" disabled={busy} onClick={()=>executePrintedOrder(flow,printedOrder)}>Exécuter</button></>:<><label className="transfer-check" title="Sélectionner pour un virement groupé"><input type="checkbox" checked={selectedTransferIds.includes(flow.id)} onChange={()=>toggleTransferSelection(flow)} aria-label={`Sélectionner la facture ${flow.reference}`}/><span>✓</span></label><button className="transfer-btn" onClick={()=>openTransfer(flow)}>{flow.isForecast?"Choisir le mode de règlement":"Imprimer ordre de virement"}</button></>:approval?.status==="pending"?<><button className="approval-waiting" onClick={()=>setPanel("paymentApprovals")}>En validation</button><button className="edit-flow-btn" disabled title="Verrouillé pendant la validation hiérarchique">Modifier</button><button className="delete-flow-btn" disabled title="Verrouillé pendant la validation hiérarchique">Supprimer</button></>:<><button className="prepare-payment-btn" disabled={busy} onClick={()=>preparePayment(flow)}>{approval?.status==="rejected"?"Repréparer":"Préparer règlement"}</button><button className="edit-flow-btn" onClick={()=>openFlowEditor(flow)}>Modifier</button>{canDeleteFlow(flow)&&<button className="delete-flow-btn" disabled={busy} onClick={()=>deleteFlow(flow)}>Supprimer</button>}{!flow.isForecast&&(flow.parentFlowId?<button className="split-btn undo" onClick={()=>cancelSplit(flow)}>Annuler fractions</button>:<button className="split-btn" onClick={()=>openSplit(flow)}>Fractionner</button>)}</>}</div>})():flow.kind==="client"?<div className="transfer-row-actions"><button className="settle-btn incoming" disabled={busy} onClick={()=>openSettlement(flow)}>Valider encaissement</button><button className="edit-flow-btn" onClick={()=>openFlowEditor(flow)}>Modifier</button>{canDeleteFlow(flow)&&<button className="delete-flow-btn" disabled={busy} onClick={()=>deleteFlow(flow)}>Supprimer</button>}</div>:<span className="not-applicable">—</span>}</td>
          </tr>)}{pagedVisible.length===0&&<tr><td colSpan={11} className="empty-filter">Aucun client ou fournisseur ne correspond à cette recherche.</td></tr>}</tbody></table></div>
          <div className="table-foot"><span><b>{visible.filter(f=>!f.excluded).length}</b> lignes prises en compte · <b>{visible.filter(f=>f.excluded).length}</b> exclues</span><div className="pagination"><button disabled={schedulePage===0} onClick={()=>setSchedulePage(page=>Math.max(0,page-1))}>← Précédente</button><span>Page <b>{schedulePage+1}</b> sur <b>{schedulePageCount}</b></span><button disabled={schedulePage>=schedulePageCount-1} onClick={()=>setSchedulePage(page=>Math.min(schedulePageCount-1,page+1))}>Suivante →</button></div><span>{companyFilter==="all"?"Vue consolidée":companyName(companyFilter)}</span></div>
          {printMode==="schedule"&&<article className="print-schedule"><header><div><strong>Moventra ERP</strong><span>Plan des règlements · {companyFilter==="all"?"Vue consolidée":companyName(companyFilter)}</span></div><time>{new Date().toLocaleDateString("fr-FR")}</time></header><h1>État des échéances {filter==="client"?"clients":filter==="fournisseur"?"fournisseurs":"clients et fournisseurs"}</h1><p>{visible.length} ligne(s) · {visible.filter(flow=>!flow.excluded).length} prise(s) en compte · {visible.filter(flow=>flow.excluded).length} exclue(s){(scheduleDateFrom||scheduleDateTo)?` · Période ${scheduleDateFrom?new Date(`${scheduleDateFrom}T00:00:00`).toLocaleDateString("fr-FR"):"début"} au ${scheduleDateTo?new Date(`${scheduleDateTo}T00:00:00`).toLocaleDateString("fr-FR"):"sans limite"}`:""}</p><table><thead><tr><th>Expéditeur</th><th>Récepteur</th><th>Type</th><th>Référence</th><th>Date facture</th><th>Échéance</th><th>Date planifiée</th><th>Montant</th><th>État</th></tr></thead><tbody>{visible.map(flow=>{const order=flow.kind==="fournisseur"?printedOrderForFlow(flow.id):undefined;const approval=flow.kind==="fournisseur"?approvalForFlow(flow.id):undefined;return <tr key={flow.id}><td>{companyName(flow.companyId)}</td><td>{(()=>{const savedOrder=transferOrders.filter(item=>item.cashFlowId===flow.id&&(item.status==="brouillon"||item.status==="imprimé")).sort((a,b)=>b.id-a.id)[0];const beneficiary=savedOrder?supplierBanks.find(bank=>bank.id===savedOrder.supplierBankId):undefined;return beneficiary?.beneficiaryName||flow.beneficiaryName||flow.party})()}{flow.blockedSupplier?" · FOURNISSEUR BLOQUÉ":""}</td><td className={flow.kind==="client"?"incoming-value":"outgoing-value"}>{flow.kind==="client"?"Encaissement":"Règlement"}</td><td>{flow.reference}</td><td>{flow.invoiceDate||"—"}</td><td>{flow.dueDate}</td><td>{flow.plannedDate}</td><td className={flow.kind==="client"?"incoming-value":"outgoing-value"}>{money(flow.amount)}</td><td>{flow.excluded?"Ignorée":order?`Ordre imprimé · ${order.internalNumber}`:approval?.status==="pending"?"En validation":approval?.status==="approved"?"Approuvé":approval?.status==="rejected"?"Refusé":flow.status}</td></tr>})}</tbody></table></article>}
        </article>}
      </section>
      </>}
      {view==="planning"&&<section className="planning-workspace">
        <div className="planning-toolbar"><div><b>Prévision glissante</b><span>{companyFilter==="all"?"Vue consolidée":companyName(companyFilter)} · soldes bancaires mis à jour manuellement</span></div><div className="planning-scale" role="group" aria-label="Échelle du planning"><button className={planningScale==="day"?"active":""} onClick={()=>{setPlanningScale("day");setPlanningSelection(null)}}>14 jours</button><button className={planningScale==="week"?"active":""} onClick={()=>{setPlanningScale("week");setPlanningSelection(null)}}>13 semaines</button><button className={planningScale==="month"?"active":""} onClick={()=>{setPlanningScale("month");setPlanningSelection(null)}}>12 mois</button></div></div>
        <div className="planning-alerts"><div><span>Solde de départ</span><b>{money(bankTotal)}</b></div><div><span>Point bas prévisionnel</span><b className={Math.min(...planningData.map(period=>period.balance))<minimumCash?"cash-danger":""}>{money(Math.min(...planningData.map(period=>period.balance)))}</b></div><div><span>Seuil de sécurité</span><b>{money(minimumCash)}</b></div><div><span>Périodes sous le seuil</span><b className={planningData.some(period=>period.balance<minimumCash)?"cash-danger":""}>{planningData.filter(period=>period.balance<minimumCash).length}</b></div></div>
        <article className="planning-card"><div className="planning-table-wrap"><table className="planning-table"><thead><tr><th>Rubrique</th>{planningData.map((period,index)=><th key={index}>{period.label}</th>)}</tr></thead><tbody>
          <tr className="opening-row"><th>Solde d’ouverture</th>{planningData.map((period,index)=><td key={index}>{shortMoney(period.opening)}</td>)}</tr>
          <tr><th><i className="planning-dot incoming"/>Factures clients</th>{planningData.map((period,index)=><td key={index}><button className="planning-amount incoming-value" onClick={()=>setPlanningSelection({index,kind:"client",forecast:false})}>{shortMoney(period.clientInvoices)}</button></td>)}</tr>
          <tr><th><i className="planning-dot incoming"/>Encaissements manuels</th>{planningData.map((period,index)=><td key={index}><button className="planning-amount incoming-value" onClick={()=>setPlanningSelection({index,kind:"client",forecast:true})}>{shortMoney(period.clientForecasts)}</button></td>)}</tr>
          <tr className="total-row incoming-total"><th>Total encaissements</th>{planningData.map((period,index)=><td key={index}><button className="planning-amount incoming-value" onClick={()=>setPlanningSelection({index,kind:"client"})}>{shortMoney(period.incoming)}</button></td>)}</tr>
          <tr><th><i className="planning-dot outgoing"/>Factures fournisseurs</th>{planningData.map((period,index)=><td key={index}><button className="planning-amount outgoing-value" onClick={()=>setPlanningSelection({index,kind:"fournisseur",forecast:false})}>{shortMoney(period.supplierInvoices)}</button></td>)}</tr>
          <tr><th><i className="planning-dot outgoing"/>Décaissements manuels</th>{planningData.map((period,index)=><td key={index}><button className="planning-amount outgoing-value" onClick={()=>setPlanningSelection({index,kind:"fournisseur",forecast:true})}>{shortMoney(period.supplierForecasts)}</button></td>)}</tr>
          <tr className="total-row outgoing-total"><th>Total décaissements</th>{planningData.map((period,index)=><td key={index}><button className="planning-amount outgoing-value" onClick={()=>setPlanningSelection({index,kind:"fournisseur"})}>{shortMoney(period.outgoing)}</button></td>)}</tr>
          <tr className="net-row"><th>Flux net</th>{planningData.map((period,index)=><td className={period.net<0?"cash-danger":"cash-positive"} key={index}>{shortMoney(period.net)}</td>)}</tr>
          <tr className="balance-row"><th>Solde de clôture</th>{planningData.map((period,index)=><td className={period.balance<minimumCash?"cash-danger":""} key={index}><button className="planning-amount" onClick={()=>setPlanningSelection({index,kind:"all"})}>{shortMoney(period.balance)}</button></td>)}</tr>
          <tr className="safety-row"><th>Seuil de sécurité</th>{planningData.map((_,index)=><td key={index}>{shortMoney(minimumCash)}</td>)}</tr>
          <tr className="available-row"><th>Trésorerie disponible</th>{planningData.map((period,index)=><td className={period.balance-minimumCash<0?"cash-danger":"cash-positive"} key={index}>{shortMoney(period.balance-minimumCash)}</td>)}</tr>
        </tbody></table></div></article>
        {selectedPlanningPeriod&&<article className="planning-detail"><div className="planning-detail-head"><div><b>Détail · {selectedPlanningPeriod.label}</b><span>{selectedPlanningItems.length} ligne(s) composent le montant sélectionné</span></div><button onClick={()=>setPlanningSelection(null)} aria-label="Fermer le détail">×</button></div>{selectedPlanningItems.length===0?<p className="empty-state">Aucune échéance dans cette cellule.</p>:<div className="planning-detail-list"><div className="planning-detail-columns"><span>Société</span><span>Client / Fournisseur</span><span>Montant</span><span>Date planifiée</span><span>Pris en compte</span></div>{selectedPlanningItems.map(flow=><div className={flow.excluded?"excluded":""} key={flow.id}><span className={`company-badge company-${flow.companyId}`}>{companyName(flow.companyId)}</span><p><b>{flow.party}</b><small>{flow.reference} · échéance {new Date(flow.dueDate).toLocaleDateString("fr-FR")}</small></p><span className={flow.kind==="client"?"incoming-value":"outgoing-value"}>{flow.kind==="client"?"+ ":"− "}{money(flow.amount)}</span><input type="date" value={flow.plannedDate} aria-label={`Date planifiée ${flow.party}`} onChange={event=>changeDate(flow.id,event.target.value)}/><button className={`calc-toggle ${flow.excluded?"off":"on"}`} onClick={()=>toggleExcluded(flow)}><i/><span>{flow.excluded?"Ignorée":"Incluse"}</span></button></div>)}</div>}</article>}
      </section>}
    </main>

    {panel&&<div className="drawer-backdrop"><aside className={`drawer ${(panel==="import"||panel==="forecast"||panel==="settings"||panel==="transfer"||panel==="split"||panel==="reports"||panel==="bankHistory"||panel==="settlements"||panel==="executedTransfers"||panel==="paymentApprovals"||panel==="blockedSuppliers"||panel==="reserves"||panel==="accountTransfer")?"drawer-wide":""} ${(panel==="settlements"||panel==="executedTransfers"||panel==="paymentApprovals")?"drawer-full":""}`} aria-label="Panneau d’action"><div className="drawer-head"><div><h2>{panel==="import"?"Mettre à jour les états":panel==="banks"?"Position bancaire":panel==="bankHistory"?"Historique bancaire":panel==="settlements"?"État des règlements":panel==="executedTransfers"?"État des virements exécutés":panel==="paymentApprovals"?"Validation hiérarchique des règlements":panel==="blockedSuppliers"?"Fournisseurs bloqués":panel==="reserves"?"Transfert réserve & épargne":panel==="fundAccount"?"Alimenter un compte bancaire":panel==="accountTransfer"?"Virement entre comptes":panel==="settlement"?"Valider le mouvement":panel==="receiptReserve"?"Affecter une part à l’épargne":panel==="forecast"?"Prévisions manuelles":panel==="settings"?"Paramètres de paiement":panel==="transfer"?(transferFlow?.isForecast?"Choisir le mode de règlement":"Ordre de virement"):panel==="split"?"Fractionner le paiement":panel==="editFlow"?"Modifier la ligne":panel==="reports"?"Rapports & exports":"Nouvelle facture"}</h2><p>{panel==="import"?"Utilisez les mêmes modèles Excel à chaque actualisation.":panel==="banks"?"Saisissez les soldes disponibles aujourd’hui.":panel==="bankHistory"?"Consultez les encaissements, décaissements et ajustements du compte.":panel==="settlements"?"Consultez les règlements validés et annulez-les si nécessaire.":panel==="executedTransfers"?"Contrôlez et imprimez les virements réellement exécutés avec leurs comptes et bénéficiaires.":panel==="paymentApprovals"?"Le responsable valide les demandes avant l’ordre de virement et le paiement.":panel==="blockedSuppliers"?"Ces fournisseurs restent dans le planning avec une prise en compte à Non par défaut.":panel==="reserves"?"Identifiez la banque qui détient la réserve et décidez si elle reste disponible dans la trésorerie.":panel==="fundAccount"?"Enregistrez un apport réel de trésorerie avec sa date, son origine et sa traçabilité.":panel==="accountTransfer"?"Déplacez un montant entre deux banques de la même société sans modifier sa trésorerie totale.":panel==="settlement"?"Affectez la banque puis confirmez la date réelle du mouvement.":panel==="receiptReserve"?"Définissez le pourcentage de l’encaissement à réserver automatiquement.":panel==="forecast"?"Utilisez un modèle ou saisissez une prévision libre.":panel==="settings"?"Modèles, banques de vos sociétés et comptes fournisseurs.":panel==="transfer"?(transferFlow?.isForecast?"Choisissez une application bancaire, un virement classique ou un autre mode de règlement.":"Préparez puis imprimez le document à transmettre à la banque."):panel==="split"?"Répartissez le reste à payer sur plusieurs montants et plusieurs dates.":panel==="editFlow"?"Modification autorisée uniquement avant validation hiérarchique.":panel==="reports"?"Analysez, imprimez ou exportez la trésorerie courante.":"Ajoutez-la directement au plan prévisionnel."}</p></div><div className="drawer-head-actions">{panel==="settings"&&supplierBankCreationFromTransfer&&<button type="button" className="drawer-back" onClick={returnToTransferOrder}>← Retour vers ordre de virement</button>}<button type="button" className="close drawer-exit" onClick={()=>panel==="editFlow"?confirmCloseFlowEditor(document.querySelector<HTMLFormElement>(".edit-flow-form")):exitPanel()}>{panel==="editFlow"?"× Fermer":"← Sortir vers la vue globale"}</button></div></div>
      {panel==="import"&&<>
        <div className="import-note"><b>Import différentiel sécurisé · multi-sociétés</b><span>Choisissez la société propriétaire des états. Moventra ERP conserve intégralement le planning actuel et ajoute uniquement les factures clients ou fournisseurs encore inconnues.</span></div>
        <div className="field company-import-field"><label>Société concernée</label><select value={importCompany} onChange={e=>{setImportCompany(e.target.value);setPendingImports({});setImportErrors({});setImportDecisionOpen(false);setImportPreview(null);}}>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></div>
        <div className="upload-grid">{(["client","fournisseur"] as const).map(kind=>{const batch=pendingImports[kind];return <label className={`upload-card ${batch?"ready":""}`} key={kind}><input type="file" accept=".xlsx,.xls" onChange={e=>selectModel(kind,e.target.files?.[0])}/><span className="upload-symbol">{batch?"✓":"⇧"}</span><b>État {kind} · {companyName(importCompany)}</b><small>{batch?batch.fileName:"Sélectionner le fichier Excel"}</small>{batch&&<strong className={kind==="client"?"incoming-value":"outgoing-value"}>{batch.items.length} lignes ouvertes · {money(batch.items.reduce((s,i)=>s+i.amount,0))}</strong>}{importErrors[kind]&&<em>{importErrors[kind]}</em>}</label>})}</div>
        <div className="import-summary"><span>Modèles attendus : <b>ETAT CLIENT.xlsx</b> et <b>ETAT FOURNISSEUR.xlsx</b></span><span>{Object.values(pendingImports).filter(Boolean).length}/2 fichier(s) prêt(s)</span></div>
        <div className="drawer-actions"><button type="button" className="ghost-btn" onClick={event=>event.currentTarget.closest("form")?.reset()}>Réinitialiser</button><button className="primary-btn" disabled={busy||!Object.values(pendingImports).some(Boolean)} onClick={previewImport}>{busy?"Vérification…":`Vérifier les nouvelles lignes · ${companyName(importCompany)}`}</button></div>
        {importDecisionOpen&&<div className="import-decision-backdrop"><section className="import-decision" role="alertdialog" aria-modal="true" aria-labelledby="import-decision-title"><div className="decision-icon safe">✓</div><h3 id="import-decision-title">Vérification différentielle terminée</h3><p>Résultat du contrôle des états de <b>{companyName(importCompany)}</b>. Aucune ligne du planning actuel ne sera modifiée.</p><div className="import-preview-kpis"><div><span>Lignes analysées</span><b>{importPreview?.received||0}</b></div><div className="new"><span>Nouvelles lignes</span><b>{importPreview?.imported||0}</b></div><div><span>Déjà connues</span><b>{importPreview?.skipped||0}</b></div></div><div className="import-safety-checks"><span>✓ Références déjà importées, réglées ou fractionnées ignorées</span><span>✓ Dates planifiées, exclusions et validations actuelles conservées</span><span>✓ Doublons présents dans le même fichier automatiquement écartés</span></div><div className="decision-options safe-only"><button className="decision-option append" disabled={busy||!importPreview?.imported} onClick={confirmImport}><span>＋</span><div><b>{importPreview?.imported?`Ajouter ${importPreview.imported} nouvelle(s) ligne(s)`:"Aucune nouvelle ligne à ajouter"}</b><small>{importPreview?.imported?"Les factures déjà connues resteront strictement inchangées.":"Ces fichiers correspondent entièrement à des factures déjà enregistrées."}</small></div></button></div><button className="decision-cancel" disabled={busy} onClick={()=>setImportDecisionOpen(false)}>{busy?"Import en cours…":"Revenir aux fichiers"}</button></section></div>}
      </>}
      {panel==="banks"&&<form onSubmit={updateBanks} className="bank-form">{scopedBanks.map((bank,i)=><div className="bank-input" key={bank.id}><div className={`bank-logo ${["a","b","c"][i%3]}`}>{bankInitials(bank.name)}</div><div><b>{bank.name}</b><span>{companyName(bank.companyId)} · {bank.accountLabel}</span></div><label><input name={`bank-${bank.id}`} type="number" step="0.01" defaultValue={bank.balance} required/><small>MAD</small></label></div>)}<div className="bank-total"><span>Total après saisie</span><b>{money(bankTotal)}</b></div><div className="drawer-actions"><button type="button" className="ghost-btn" onClick={event=>event.currentTarget.closest("form")?.reset()}>Réinitialiser</button><button className="primary-btn" type="submit">Enregistrer les soldes</button></div></form>}
      {panel==="blockedSuppliers"&&<div className="blocked-suppliers-panel"><form className="blocked-supplier-form" onSubmit={addBlockedSupplier}><div className="field"><label>Société</label><select value={blockedCompanyId} onChange={event=>setBlockedCompanyId(event.target.value)}>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></div><div className="field"><label>Fournisseur à bloquer</label><input name="supplierName" list="blocked-supplier-candidates" placeholder="Nom du fournisseur" required/><datalist id="blocked-supplier-candidates">{blockedSupplierCandidates.map(name=><option value={name} key={name}/>)}</datalist></div><button className="primary-btn" type="submit">Ajouter aux fournisseurs bloqués</button></form><div className="blocked-info"><b>Comportement dans le planning</b><span>Les factures restent visibles avec la mention « Fournisseur bloqué ». La prise en compte passe automatiquement à « Non », mais vous pouvez réintégrer une ligne manuellement.</span></div><div className="blocked-list-head"><b>Liste des fournisseurs bloqués</b><span>{blockedSuppliers.length} fournisseur(s)</span></div><div className="blocked-supplier-list">{blockedSuppliers.length===0?<p className="empty-state">Aucun fournisseur bloqué.</p>:blockedSuppliers.map(item=><div key={item.id}><span className={`company-badge company-${item.companyId}`}>{companyName(item.companyId)}</span><div><b>{item.supplierName}</b><small>{flows.filter(flow=>flow.companyId===item.companyId&&flow.kind==="fournisseur"&&normalized(flow.party)===item.supplierKey).length} ligne(s) dans le planning</small></div><span className="blocked-status">Bloqué</span><button type="button" onClick={()=>deleteBlockedSupplier(item)}>Débloquer</button></div>)}</div></div>}
      {panel==="fundAccount"&&<form className="account-funding-form" data-confirm="none" onSubmit={submitAccountFunding}>
        <div className="funding-company-summary"><span className={`company-dot company-${fundingCompanyId}`}/><div><b>{companyName(fundingCompanyId)}</b><small>Demande d’alimentation soumise au circuit de validation</small></div></div>
        <div className="funding-guidance"><b>Écriture contrôlée</b><span>Aucun solde ne change à cette étape. Après validation, l’ordre devient imprimable ; le compte bénéficiaire sera crédité seulement lors de l’exécution réelle.</span></div>
        <div className="form-grid">
          <div className="field full"><label>Société qui finance</label><select value={fundingSourceCompanyId} onChange={event=>setFundingSourceCompanyId(event.target.value)} required><option value="">Choisir la société source</option>{fundingSourceCompanies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select><small className="field-help">La demande apparaîtra dans les validations de cette société avant impression de l’ordre.</small></div>
          <div className="field full"><label>Compte bancaire à alimenter</label><select value={fundingBankId} onChange={event=>setFundingBankId(Number(event.target.value))} required><option value="">Choisir le compte</option>{fundingBanks.map(bank=><option value={bank.id} key={bank.id}>{bank.name} · {bank.accountLabel} · solde {money(bank.balance)}</option>)}</select></div>
          <div className="field"><label>Montant à créditer (MAD)</label><input type="number" min="0.01" step="0.01" value={fundingAmount||""} onChange={event=>setFundingAmount(Number(event.target.value))} placeholder="0,00" required/></div>
          <div className="field"><label>Date de l’apport</label><input type="date" value={fundingDate} onChange={event=>setFundingDate(event.target.value)} required/></div>
          <div className="field full"><label>Origine des fonds / motif</label><input value={fundingReason} onChange={event=>setFundingReason(event.target.value)} placeholder="Ex. Apport associé, crédit bancaire, régularisation d’ouverture" maxLength={180} required/></div>
        </div>
        {fundingBank?<div className="funding-impact"><div><span>Solde actuel</span><b>{money(fundingBank.balance)}</b></div><i>＋</i><div><span>Crédit</span><b className="incoming-value">{money(fundingAmount)}</b></div><i>＝</i><div><span>Nouveau solde</span><b>{money(fundingBank.balance+fundingAmount)}</b></div></div>:<div className="settlement-warning">Aucun compte bancaire n’est disponible pour {companyName(fundingCompanyId)}.<button type="button" onClick={()=>setPanel("settings")}>Paramétrer une banque</button></div>}
        <div className="drawer-actions"><button type="button" className="ghost-btn" onClick={event=>event.currentTarget.closest("form")?.reset()}>Réinitialiser</button><button className="primary-btn" disabled={busy||!fundingSourceCompanyId||!fundingBankId||fundingAmount<=0||!fundingReason.trim()}>{busy?"Transmission…":`Créer la demande de ${money(fundingAmount)}`}</button></div>
      </form>}
      {panel==="accountTransfer"&&<div className="account-transfer-panel">
        <div className="account-transfer-rule"><span>⇄</span><div><b>Virement bancaire avec délai de réception</b><small>Le compte source est débité à la date d’émission. Le compte bénéficiaire n’est crédité qu’à la date de réception ; entre les deux, le montant reste identifié « en transit ».</small></div></div>
        <form className="account-transfer-form" data-confirm="none" onSubmit={executeAccountTransfer}>
          <div className="field full"><label>Société</label><select value={accountTransferCompanyId} onChange={event=>{setAccountTransferAmount(0);selectAccountTransferCompany(event.target.value)}}>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></div>
          {accountTransferBanks.length<2?<div className="settlement-warning field full">Deux comptes bancaires au minimum sont nécessaires pour {companyName(accountTransferCompanyId)}.<button type="button" onClick={()=>setPanel("settings")}>Ajouter une banque</button></div>:<>
            <div className="field"><label>Compte à débiter</label><select value={accountTransferSourceId} onChange={event=>{const id=Number(event.target.value);setAccountTransferSourceId(id);if(id===accountTransferDestinationId)setAccountTransferDestinationId(accountTransferBanks.find(bank=>bank.id!==id)?.id??0)}} required>{accountTransferBanks.map(bank=><option value={bank.id} key={bank.id}>{bank.name} · {bank.accountLabel}</option>)}</select><small className="field-help supplier-bank-found">Disponible après réserves : {money(Math.max(0,(accountTransferSource?.balance||0)-accountTransferProtected))}</small></div>
            <div className="field"><label>Compte à créditer</label><select value={accountTransferDestinationId} onChange={event=>setAccountTransferDestinationId(Number(event.target.value))} required>{accountTransferBanks.filter(bank=>bank.id!==accountTransferSourceId).map(bank=><option value={bank.id} key={bank.id}>{bank.name} · {bank.accountLabel}</option>)}</select></div>
            <div className="field"><label>Montant à transférer</label><input name="amount" type="number" min="0.01" max={Math.max(0,(accountTransferSource?.balance||0)-accountTransferProtected)} step="0.01" value={accountTransferAmount||""} onChange={event=>setAccountTransferAmount(Number(event.target.value))} placeholder="0,00" required/></div>
            <div className="field"><label>Date de débit / émission</label><input name="transferDate" type="date" value={accountTransferDebitDate} max={accountTransferReceptionDate||undefined} onChange={event=>{setAccountTransferDebitDate(event.target.value);if(accountTransferReceptionDate<event.target.value)setAccountTransferReceptionDate(event.target.value)}} required/><small className="field-help">Le compte source est débité à cette date.</small></div>
            <div className="field"><label>Date de réception bénéficiaire</label><input name="receptionDate" type="date" value={accountTransferReceptionDate} min={accountTransferDebitDate} onChange={event=>setAccountTransferReceptionDate(event.target.value)} required/><small className="field-help">Le compte bénéficiaire sera crédité uniquement à cette date.</small></div>
            <div className="field full"><label>Motif</label><input name="purpose" placeholder="Ex. Rééquilibrage de trésorerie entre comptes" required/></div>
            {accountTransferSource&&accountTransferDestination&&<div className="account-transfer-impact field full"><div><span>Compte source</span><b>{money(accountTransferSource.balance)}</b><small>Après débit : <strong className="outgoing-value">{money(accountTransferSource.balance-accountTransferAmount)}</strong></small></div><i>→</i><div><span>Compte bénéficiaire</span><b>{money(accountTransferDestination.balance)}</b><small>{accountTransferReceptionDate<=new Date().toISOString().slice(0,10)?<>Après réception : <strong className="incoming-value">{money(accountTransferDestination.balance+accountTransferAmount)}</strong></>:<>Crédit prévu le <strong>{new Date(`${accountTransferReceptionDate}T00:00:00`).toLocaleDateString("fr-FR")}</strong></>}</small></div><div className="account-transfer-total"><span>Montant en transit</span><b>{accountTransferReceptionDate>new Date().toISOString().slice(0,10)?money(accountTransferAmount):money(0)}</b><small>Trésorerie économique inchangée ; disponible bancaire crédité à réception</small></div></div>}
            <div className="drawer-actions field full"><button type="button" className="ghost-btn" onClick={event=>event.currentTarget.closest("form")?.reset()}>Réinitialiser</button><button className="primary-btn" disabled={busy||accountTransferAmount<=0||accountTransferAmount>(accountTransferSource?.balance||0)-accountTransferProtected}>{busy?"Exécution…":"Exécuter le virement"}</button></div>
          </>}
        </form>
        <section className="account-transfer-history">
          <div className="history-section-head"><div><b>Historique des virements entre comptes</b><span>{accountTransferHistory.length} opération(s) dans la période</span></div><button type="button" className="history-print-btn" disabled={accountTransferHistory.length===0} onClick={()=>{setPrintMode("accountTransfers");window.setTimeout(()=>window.print(),100)}}>↓ Imprimer l’état</button></div>
          <div className="history-date-range"><label><span>Du</span><input type="date" value={accountTransferHistoryDateFrom} max={accountTransferHistoryDateTo||undefined} onChange={event=>setAccountTransferHistoryDateFrom(event.target.value)}/></label><label><span>Au</span><input type="date" value={accountTransferHistoryDateTo} min={accountTransferHistoryDateFrom||undefined} onChange={event=>setAccountTransferHistoryDateTo(event.target.value)}/></label>{(accountTransferHistoryDateFrom||accountTransferHistoryDateTo)&&<button type="button" onClick={()=>{setAccountTransferHistoryDateFrom("");setAccountTransferHistoryDateTo("")}}>Toute la période</button>}</div>
          <div className="account-transfer-history-table"><div className="account-transfer-history-head"><span>Débit / réception</span><span>Compte débité</span><span>Compte bénéficiaire</span><span>Référence / statut</span><span>Montant</span></div>{accountTransferHistory.length===0?<p className="empty-state">Aucun virement entre comptes dans cette plage de dates.</p>:accountTransferHistory.map(({transfer,sourceBank,destinationBank})=><article key={transfer.id}><time><b>{new Date(`${transfer.debitDate}T00:00:00`).toLocaleDateString("fr-FR")}</b><small>Réception : {new Date(`${transfer.receptionDate}T00:00:00`).toLocaleDateString("fr-FR")}</small></time><span><b>{sourceBank?.name||"Compte inconnu"}</b><small>{sourceBank?.accountLabel||"—"}{sourceBank?.rib?` · RIB ${sourceBank.rib}`:""}</small></span><span><b>{destinationBank?.name||"Compte inconnu"}</b><small>{destinationBank?.accountLabel||"—"}{destinationBank?.rib?` · RIB ${destinationBank.rib}`:""}</small></span><span><b>{transfer.reference}</b><small>{transfer.purpose}</small><em className={`account-transfer-status ${transfer.status}`}>{transfer.status==="received"?"Reçu":"En transit"}</em></span><strong>{moneyExact(transfer.amount)}</strong></article>)}</div>
        </section>
        {printMode==="accountTransfers"&&<article className="print-history-state"><header><div><strong>Moventra ERP</strong><span>Historique des virements entre comptes</span></div><time>{new Date().toLocaleDateString("fr-FR")}</time></header><h1>{companyName(accountTransferCompanyId)}</h1><p>Période : {accountTransferHistoryDateFrom?new Date(`${accountTransferHistoryDateFrom}T00:00:00`).toLocaleDateString("fr-FR"):"origine"} au {accountTransferHistoryDateTo?new Date(`${accountTransferHistoryDateTo}T00:00:00`).toLocaleDateString("fr-FR"):"jour courant"} · {accountTransferHistory.length} opération(s)</p><table><thead><tr><th>Date débit</th><th>Date réception</th><th>Compte débité</th><th>Compte bénéficiaire</th><th>Référence / motif</th><th>Statut</th><th>Montant</th></tr></thead><tbody>{accountTransferHistory.map(({transfer,sourceBank,destinationBank})=><tr key={transfer.id}><td>{new Date(`${transfer.debitDate}T00:00:00`).toLocaleDateString("fr-FR")}</td><td>{new Date(`${transfer.receptionDate}T00:00:00`).toLocaleDateString("fr-FR")}</td><td><b>{sourceBank?.name||"—"}</b><small>{sourceBank?.accountLabel||"—"}{sourceBank?.rib?` · RIB ${sourceBank.rib}`:""}</small></td><td><b>{destinationBank?.name||"—"}</b><small>{destinationBank?.accountLabel||"—"}{destinationBank?.rib?` · RIB ${destinationBank.rib}`:""}</small></td><td><b>{transfer.reference}</b><small>{transfer.purpose}</small></td><td>{transfer.status==="received"?"Reçu":"En transit"}</td><td>{moneyExact(transfer.amount)}</td></tr>)}</tbody></table><footer>État édité depuis Moventra ERP · Le crédit bénéficiaire est comptabilisé à la date de réception</footer></article>}
      </div>}
      {panel==="reserves"&&<div className="reserves-panel"><div className="reserve-kpis"><div><span>Total affecté</span><b>{money(reserves.reduce((sum,reserve)=>sum+reserve.amount,0))}</b></div><div><span>Hors trésorerie</span><b className="outgoing-value">{money(reserves.filter(reserve=>!reserve.includedInTreasury).reduce((sum,reserve)=>sum+reserve.amount,0))}</b></div><div><span>Pris en compte</span><b className="incoming-value">{money(reserves.filter(reserve=>reserve.includedInTreasury).reduce((sum,reserve)=>sum+reserve.amount,0))}</b></div></div><form className="reserve-form" onSubmit={addReserveAllocation}><div className="field"><label>Société</label><select value={reserveCompanyId} onChange={event=>setReserveCompanyId(event.target.value)}>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></div><div className="field"><label>Banque où se trouve le montant</label><select name="bankAccountId" required><option value="">Choisir la banque</option>{banks.filter(bank=>bank.companyId===reserveCompanyId).map(bank=><option value={bank.id} key={bank.id}>{bank.name} · {bank.accountLabel} · {money(bank.balance)}</option>)}</select></div><div className="field"><label>Nature</label><select name="category"><option value="reserve">Réserve</option><option value="epargne">Épargne</option></select></div><div className="field"><label>Libellé</label><input name="label" placeholder="Ex. Réserve de sécurité" required/></div><div className="field"><label>Montant transféré</label><input name="amount" type="number" min="0.01" step="0.01" required/></div><div className="field"><label>Date du transfert</label><input name="transferDate" type="date" defaultValue={new Date().toISOString().slice(0,10)} required/></div><div className="field"><label>Pris en compte dans la trésorerie</label><select name="includedInTreasury" defaultValue="false"><option value="false">Non · déduire du disponible</option><option value="true">Oui · conserver dans le disponible</option></select></div><div className="field"><label>Note</label><input name="note" placeholder="Motif ou destination"/></div><div className="drawer-actions field full"><button className="primary-btn" disabled={!banks.some(bank=>bank.companyId===reserveCompanyId)}>Enregistrer le transfert</button></div></form><div className="reserve-list-head"><b>Réserves et épargnes enregistrées</b><span>{reserves.length} affectation(s)</span></div><div className="reserve-list">{reserves.length===0?<p className="empty-state">Aucune réserve enregistrée.</p>:reserves.map(reserve=>{const bank=banks.find(item=>item.id===reserve.bankAccountId);return <div key={reserve.id}><span className={`reserve-kind ${reserve.category}`}>{reserve.category==="reserve"?"R":"É"}</span><div><b>{reserve.label} <span className={`company-badge company-${reserve.companyId}`}>{companyName(reserve.companyId)}</span></b><small>{bank?`${bank.name} · ${bank.accountLabel}`:"Banque inconnue"} · {new Date(`${reserve.transferDate}T00:00:00`).toLocaleDateString("fr-FR")}</small></div><strong>{money(reserve.amount)}</strong><button className={`reserve-toggle ${reserve.includedInTreasury?"on":"off"}`} onClick={()=>toggleReserveInTreasury(reserve)}><i/><span>{reserve.includedInTreasury?"Oui":"Non"}</span></button><button className="reserve-delete" onClick={()=>deleteReserveAllocation(reserve)}>Supprimer</button></div>})}</div></div>}
      {panel==="settlement"&&settlementFlow&&<form className="settlement-form" data-confirm="none" onSubmit={confirmSettlement}>
        <div className="settlement-source"><span className={`settlement-icon ${settlementFlow.kind}`}>{settlementFlow.kind==="client"?"＋":"−"}</span><div><span className={`company-badge company-${settlementFlow.companyId}`}>{companyName(settlementFlow.companyId)}</span><b>{settlementFlow.party}</b><small>{settlementFlow.reference} · échéance {new Date(settlementFlow.dueDate).toLocaleDateString("fr-FR")}</small></div><strong className={settlementFlow.kind==="client"?"incoming-value":"outgoing-value"}>{settlementFlow.kind==="client"?"+ ":"− "}{money(Math.abs(settlementFlow.amount))}</strong></div>
        <div className="form-grid"><div className="field full"><label>Banque à {settlementFlow.kind==="client"?"créditer":"débiter"}</label><select value={settlementBankId} onChange={event=>setSettlementBankId(Number(event.target.value))} required><option value="">Choisir une banque</option>{banks.filter(bank=>bank.companyId===settlementFlow.companyId).map(bank=><option value={bank.id} key={bank.id}>{bank.name} · {bank.accountLabel} · solde {money(bank.balance)}</option>)}</select></div><div className="field full"><label>Date réelle du {settlementFlow.kind==="client"?"règlement client":"paiement fournisseur"}</label><input type="date" value={settlementDate} onChange={event=>setSettlementDate(event.target.value)} required/></div></div>
        {settlementBank?<div className="settlement-impact"><div><span>Solde avant</span><b>{money(settlementBank.balance)}</b></div><span className={settlementFlow.kind==="client"?"incoming-value":"outgoing-value"}>{settlementFlow.kind==="client"?"＋":"−"} {money(Math.abs(settlementFlow.amount))}</span><div><span>Solde après validation</span><b className={settlementBank.balance+(settlementFlow.kind==="client"?1:-1)*Math.abs(settlementFlow.amount)<0?"outgoing-value":""}>{money(settlementBank.balance+(settlementFlow.kind==="client"?1:-1)*Math.abs(settlementFlow.amount))}</b></div></div>:<div className="settlement-warning">Aucune banque n’est paramétrée pour {companyName(settlementFlow.companyId)}.<button type="button" onClick={()=>setPanel("settings")}>Paramétrer les banques</button></div>}
        <div className="settlement-note">Après validation, cette échéance quittera le planning ouvert et le mouvement sera ajouté à l’historique de la banque.</div>
        <div className="drawer-actions"><button type="button" className="ghost-btn" onClick={event=>event.currentTarget.closest("form")?.reset()}>Réinitialiser</button><button type="button" className="close-edit-flow-btn" disabled={busy} onClick={exitPanel}>Fermer</button><button className={`primary-btn ${settlementFlow.kind==="fournisseur"?"danger-primary":""}`} disabled={busy||!settlementBankId}>{busy?"Validation…":settlementFlow.kind==="client"?"Valider l’encaissement":"Valider le règlement"}</button></div>
      </form>}
      {panel==="receiptReserve"&&receiptReserve&&receiptReserveBank&&<form className="receipt-reserve-form" onSubmit={saveReceiptReserve}>
        <div className="receipt-confirmed"><span>✓</span><div><b>Encaissement client validé</b><small>{receiptReserve.flow.party} · {receiptReserve.flow.reference}</small></div><strong className="incoming-value">+ {money(Math.abs(receiptReserve.flow.amount))}</strong></div>
        <div className="receipt-reserve-question"><b>Quel pourcentage souhaitez-vous transférer à l’épargne ?</b><p>Ce montant alimentera automatiquement la réserve et sera retiré de la trésorerie bancaire disponible.</p><label><input type="number" min="0.01" max="100" step="0.01" value={receiptReservePercent} onChange={event=>setReceiptReservePercent(Number(event.target.value))} required/><span>%</span></label><div className="percentage-presets">{[5,10,15,20].map(percent=><button type="button" className={receiptReservePercent===percent?"active":""} onClick={()=>setReceiptReservePercent(percent)} key={percent}>{percent} %</button>)}</div></div>
        <div className="receipt-reserve-calculation"><div><span>Banque créditée</span><b>{receiptReserveBank.name}</b><small>{receiptReserveBank.accountLabel}</small></div><div><span>Montant vers l’épargne</span><b className="outgoing-value">− {money(receiptReserveAmount)}</b><small>{receiptReservePercent}% de l’encaissement</small></div><div><span>Disponible après épargne</span><b className={receiptAvailableBefore-receiptReserveAmount<0?"outgoing-value":""}>{money(receiptAvailableBefore-receiptReserveAmount)}</b><small>Solde bancaire moins réserves</small></div></div>
        <div className="receipt-reserve-lock">Cette étape finalise l’encaissement. L’épargne sera enregistrée avec « Prise en compte : Non » afin d’être déduite du disponible.</div>
        <div className="drawer-actions"><button type="button" className="ghost-btn" disabled={busy} onClick={()=>{setReceiptReserve(null);setPanel("settlements");flash("Encaissement conservé intégralement dans la trésorerie disponible")}}>Conserver 100 % en trésorerie</button><button className="primary-btn" disabled={busy||receiptReserveAmount<=0||receiptReserveAmount>receiptAvailableBefore}>{busy?"Affectation…":`Affecter ${money(receiptReserveAmount)} à l’épargne`}</button></div>
      </form>}
      {panel==="paymentApprovals"&&<div className="payment-approvals-panel">
        <div className="approval-kpis"><div><span>En attente de décision</span><b>{pendingPaymentApprovals.length}</b></div><div><span>Approuvés</span><b className="incoming-value">{paymentApprovals.filter(item=>item.status==="approved").length}</b></div><div><span>Refusés</span><b className="outgoing-value">{paymentApprovals.filter(item=>item.status==="rejected").length}</b></div></div>
        <section className="treasury-pilot" aria-labelledby="treasury-pilot-title"><div className="treasury-pilot-head"><div><h3 id="treasury-pilot-title">Capacité bancaire avant exécution des virements</h3><p>La batterie montre la trésorerie qui restera après les ordres imprimés et les règlements approuvés à imprimer. Un transfert déjà exécuté n’est plus compté comme engagement.</p></div><div className="pilot-legend"><span><i className="safe"/>Marge confortable</span><span><i className="warning"/>Marge faible</span><span><i className="critical"/>Marge critique</span></div></div><div className="pilot-company-grid">{preparedTransferSummaries.map(summary=>{const committedPercent=Number.isFinite(summary.ratio)?Math.round(summary.ratio*100):100;const remainingPercent=Math.round(summary.remainingRatio*100);const level=summary.ratio>.9?"critical":summary.ratio>=.7?"warning":"safe";const statusLabel=summary.deficit>0?"BESOIN DE TRÉSORERIE":level==="critical"?"MARGE CRITIQUE":level==="warning"?"VIGILANCE":"CAPACITÉ MAÎTRISÉE";const donor=preparedTransferSummaries.filter(item=>item.companyId!==summary.companyId&&item.remaining>0).sort((a,b)=>b.remaining-a.remaining)[0];const canFund=Boolean(donor&&summary.need>0&&donor.remaining>=summary.need);return <article className={`company-pilot-card ${level}`} key={summary.companyId}><header><div><span className={`company-dot company-${summary.companyId}`}/><b>{companyName(summary.companyId)}</b></div><em>{statusLabel}</em></header><div className="battery-row"><div className="battery-shell" role="meter" aria-label={`Capacité bancaire restante ${companyName(summary.companyId)}`} aria-valuemin={0} aria-valuemax={100} aria-valuenow={remainingPercent}><div className={`battery-fill ${level}`} style={{width:`${remainingPercent}%`}}/><span>{remainingPercent} % disponible</span></div><strong>{money(summary.committed)}</strong><small>{committedPercent} % engagés</small></div><div className="pilot-values"><div><span>Trésorerie disponible</span><b>{money(summary.available)}</b></div><div><span>Reste après engagements</span><b className={summary.remaining<=0&&summary.committed>0?"outgoing-value":""}>{money(summary.remaining)}</b></div><div><span>Ordres déjà imprimés</span><b>{money(summary.prepared)}</b></div><div><span>Approuvés à imprimer</span><b>{money(summary.approvedAwaitingOrder)}</b></div><div><span>Demandes à décider</span><b>{money(summary.pendingDecision)}</b></div><div><span>Financement interne en attente</span><b className="incoming-value">{money(summary.incomingTransferPending)}</b></div></div>{level==="critical"&&<div className="pilot-critical-message"><div><b>{summary.deficit>0?`! Besoin à financer : ${money(summary.deficit)}`:`! Marge restante : ${money(Math.max(0,summary.remaining))}`}</b><span>{summary.deficit>0?(summary.incomingTransferPending>0?`${money(summary.incomingTransferPending)} sont déjà demandés par transfert interne.`:"Les engagements dépassent la trésorerie réellement disponible."):"Les engagements absorbent plus de 90 % du disponible. Aucun nouveau virement ne devrait être ajouté sans arbitrage."}</span></div>{canFund&&donor&&<button type="button" disabled={busy} onClick={()=>requestInternalTransfer(donor.companyId,summary.companyId,summary.need)}>Transférer {money(summary.need)} depuis {companyName(donor.companyId)}</button>}{summary.need>0&&donor&&!canFund&&<small>Capacité insuffisante de {companyName(donor.companyId)} pour financer la totalité du besoin.</small>}{summary.need>0&&<button type="button" className="fund-account-btn" disabled={busy||!banks.some(bank=>bank.companyId===summary.companyId)} title={banks.some(bank=>bank.companyId===summary.companyId)?`Créditer un compte bancaire de ${companyName(summary.companyId)}`:"Paramétrez d’abord un compte bancaire"} onClick={()=>openAccountFunding(summary.companyId,summary.need)}>＋ Alimenter le compte</button>}</div>}</article>})}</div></section>
        {preparedTransferAlerts.length>0&&<div className="approval-threshold-alert"><b>! Seuil de sécurité dépassé</b><span>Les ordres imprimés et les règlements approuvés à imprimer dépassent 90 % du disponible.</span>{preparedTransferAlerts.map(summary=><small key={summary.companyId}>{companyName(summary.companyId)} · {money(summary.committed)} engagés / {money(summary.available)} disponibles</small>)}</div>}
        <div className="approval-workflow"><span className="active">1. Préparer le règlement</span><i>→</i><span>2. Validation hiérarchique</span><i>→</i><span>3. Ordre de virement</span><i>→</i><span>4. Règlement bancaire</span></div>
        <div className="approval-list-head"><b>Demandes transmises au responsable</b><div><span>{pendingPaymentApprovals.length} décision(s) requise(s)</span><button type="button" onClick={()=>{setPrintMode("approvals");window.setTimeout(()=>window.print(),100)}}>↓ Imprimer l’état global</button></div></div>
        <div className="approval-filters"><label><span>Société</span><select value={approvalCompanyFilter} onChange={event=>setApprovalCompanyFilter(event.target.value)}><option value="all">Toutes les sociétés</option>{companies.map(company=><option value={company.id} key={company.id}>{company.shortName}</option>)}</select></label><label className="approval-filter-search"><span>Fournisseur ou facture</span><input type="search" value={approvalSupplierSearch} onChange={event=>setApprovalSupplierSearch(event.target.value)} placeholder="Rechercher…"/></label><div className="approval-filter-amount"><span>Montant (MAD)</span><input type="number" min="0" step="100" value={approvalAmountMin} onChange={event=>setApprovalAmountMin(event.target.value)} placeholder="Minimum"/><input type="number" min="0" step="100" value={approvalAmountMax} onChange={event=>setApprovalAmountMax(event.target.value)} placeholder="Maximum"/></div><label><span>État</span><select value={approvalStatusFilter} onChange={event=>setApprovalStatusFilter(event.target.value)}><option value="all">Tous les états</option><option value="pending">À valider</option><option value="approved">Approuvé</option><option value="rejected">Refusé</option><option value="executed">Exécuté</option><option value="cancelled">Annulé</option></select></label><label><span>Décision / étape</span><select value={approvalDecisionFilter} onChange={event=>setApprovalDecisionFilter(event.target.value)}><option value="all">Toutes les décisions</option><option value="required">Décision requise</option><option value="to_print">Ordre à imprimer</option><option value="printed">Ordre imprimé</option><option value="executed">Règlement exécuté</option><option value="rejected">Décision refusée</option><option value="cancelled">Transfert annulé</option></select></label><button type="button" className="approval-filter-clear" disabled={approvalCompanyFilter==="all"&&!approvalSupplierSearch&&!approvalAmountMin&&!approvalAmountMax&&approvalStatusFilter==="all"&&approvalDecisionFilter==="all"} onClick={()=>{setApprovalCompanyFilter("all");setApprovalSupplierSearch("");setApprovalAmountMin("");setApprovalAmountMax("");setApprovalStatusFilter("all");setApprovalDecisionFilter("all")}}>Effacer</button><strong>{filteredPaymentApprovals.length+filteredAccountFundings.length} résultat{filteredPaymentApprovals.length+filteredAccountFundings.length>1?"s":""}</strong></div>
        <div className="approval-group-toolbar"><div><b>Ordre de virement groupé</b><span>{selectedTransferFlows.length?`${selectedTransferFlows.length} ligne(s) · ${selectedTransferFlows[0]?.party} · ${money(selectedTransferTotal)}`:"Cochez au moins deux factures approuvées du même fournisseur, de la même société et avec la même date planifiée."}</span></div><button type="button" className="ghost-btn" disabled={!selectedTransferFlows.length} onClick={()=>setSelectedTransferIds([])}>Effacer</button><button type="button" className="primary-btn" disabled={selectedTransferFlows.length<2} onClick={openGroupedTransfer}>Préparer un ordre consolidé ({selectedTransferFlows.length})</button></div>
        {consolidationGroups.length>0&&<section className="consolidation-proposals"><header><div><b>Propositions d’ordres consolidés</b><span>Même société, même fournisseur et même date planifiée.</span></div><em>{consolidationGroups.length} proposition{consolidationGroups.length>1?"s":""}</em></header><div>{consolidationGroups.map(group=>{const total=group.reduce((sum,flow)=>sum+Math.abs(Number(flow.amount)),0);const first=group[0];return <article key={`${first.companyId}-${normalized(first.party)}-${first.plannedDate}`}><span className={`company-badge company-${first.companyId}`}>{companyName(first.companyId)}</span><div><b>{first.party}</b><small>{group.length} factures · règlement le {new Date(`${first.plannedDate}T00:00:00`).toLocaleDateString("fr-FR")}</small><small>{group.map(flow=>flow.reference).join(" · ")}</small></div><strong>{money(total)}</strong><button type="button" onClick={()=>openConsolidationProposal(group)}>Préparer l’ordre consolidé</button></article>})}</div></section>}<div className="approval-table"><div className="approval-head"><span>Demande</span><span>Expéditeur → Récepteur</span><span>Facture / date planifiée</span><span>Montant</span><span>État</span><span>Décision</span></div>{filteredPaymentApprovals.length===0?<p className="empty-state">Aucune demande ne correspond aux filtres sélectionnés.</p>:filteredPaymentApprovals.map(approval=>{const flow=findFlow(approval.cashFlowId);const transaction=bankTransactions.find(item=>item.cashFlowId===approval.cashFlowId&&!item.cancelledAt);const settledBank=transaction?banks.find(item=>item.id===transaction.bankAccountId):undefined;const printedOrder=printedOrderForFlow(approval.cashFlowId);const internalTransfer=internalTransferForFlow(approval.cashFlowId);const savedOrder=transferOrders.filter(item=>item.cashFlowId===approval.cashFlowId&&(item.status==="brouillon"||item.status==="imprimé")).sort((a,b)=>b.id-a.id)[0];const senderBank=savedOrder?banks.find(item=>item.id===savedOrder.companyBankId):banks.find(item=>item.companyId===approval.companyId&&item.isPrimary)||banks.find(item=>item.companyId===approval.companyId);const receiverBank=savedOrder?supplierBanks.find(item=>item.id===savedOrder.supplierBankId):flow?supplierBanks.find(item=>item.companyId===flow.companyId&&normalized(item.supplierName)===normalized(flow.party)):undefined;const receiverName=internalTransfer?companyName(internalTransfer.toCompanyId):flow?.beneficiaryName||receiverBank?.beneficiaryName||flow?.party||transaction?.party||"Bénéficiaire à préciser";const selectable=Boolean(flow&&approval.status==="approved"&&!printedOrder&&!transaction&&internalTransfer?.status!=="completed"&&!flow.excluded);const beneficiaryEditable=Boolean(flow&&(approval.status==="pending"||approval.status==="approved")&&!transaction&&!printedOrder&&!internalTransfer);return <div className={`approval-row ${flow&&selectedTransferIds.includes(flow.id)?"group-selected":""}`} key={approval.id}><div className="approval-request-cell"><label className="approval-select-check" title={selectable?"Ajouter cette facture à l’ordre groupé":"Sélection indisponible pour cette ligne"}><input type="checkbox" checked={Boolean(flow&&selectedTransferIds.includes(flow.id))} disabled={!selectable} onChange={()=>flow&&toggleTransferSelection(flow)} aria-label={flow?`Sélectionner la facture ${flow.reference}`:"Ligne non sélectionnable"}/><i>✓</i></label><span><b>{new Date(approval.requestedAt).toLocaleDateString("fr-FR")}</b><small>Par {approval.requestedBy}</small></span></div><div className="approval-transfer-parties"><div className="transfer-party sender"><em>Expéditeur</em><b><i className={`company-dot company-${approval.companyId}`}/>{companyName(approval.companyId)}</b><small>{senderBank?`${senderBank.name} · ${senderBank.accountLabel}`:"Compte source à sélectionner"}</small></div><span className="transfer-direction" aria-hidden="true">→</span><div className="transfer-party receiver"><em>Récepteur</em><b>{receiverName}</b>{beneficiaryEditable&&flow&&<label className="approval-beneficiary-editor"><span>{approval.status==="pending"?"Bénéficiaire à valider":"Bénéficiaire modifiable avant émission de l’ordre"}</span><input type="text" value={approvalBeneficiaryNames[approval.id]??flow.beneficiaryName??flow.party} onChange={event=>setApprovalBeneficiaryNames(current=>({...current,[approval.id]:event.target.value}))} aria-label={`Bénéficiaire du règlement ${flow.reference}`} required/>{approval.status==="approved"&&<button type="button" disabled={busy} onClick={()=>saveApprovalBeneficiary(approval)}>Enregistrer le bénéficiaire</button>}</label>}<small>{receiverBank?`${receiverBank.bankName} · RIB ${receiverBank.rib}`:internalTransfer?"Compte de la société destinataire":"Compte bénéficiaire à sélectionner"}</small>{approval.beneficiaryChanged&&approval.beneficiaryNameBefore&&approval.beneficiaryNameAfter&&<span className="approval-beneficiary-audit"><strong>Bénéficiaire modifié</strong><i>Avant : {approval.beneficiaryNameBefore}</i><i>Après : {approval.beneficiaryNameAfter}</i></span>}</div></div><div className="approval-invoice-cell"><b>{flow?.reference||transaction?.reference||internalTransfer?.reference||`Flux n° ${approval.cashFlowId}`}</b>{flow?.operationLabel&&<small className="approval-operation-label">{flow.operationLabel}</small>}{flow&&<label className={`approval-date-editor ${approval.dateChanged?"changed":""}`}><em>Date planifiée</em><input type="date" value={approvalPlannedDates[approval.id]??flow.plannedDate} disabled={approval.status!=="pending"||Boolean(transaction)||Boolean(printedOrder)} onChange={event=>setApprovalPlannedDates(current=>({...current,[approval.id]:event.target.value}))} aria-label={`Date planifiée de ${flow.reference}`}/></label>}{approval.dateChanged&&approval.plannedDateBefore&&approval.plannedDateAfter&&<small className="approval-date-audit"><b>Date modifiée</b><span>Avant : {new Date(`${approval.plannedDateBefore}T00:00:00`).toLocaleDateString("fr-FR")}</span><span>Après : {new Date(`${approval.plannedDateAfter}T00:00:00`).toLocaleDateString("fr-FR")}</span><span>Par : {approval.decidedBy||"Responsable hiérarchique"}</span></small>}</div><strong className="outgoing-value">{money(flow?.amount||transaction?.amount||internalTransfer?.amount||0)}</strong><span><em className={`approval-state ${internalTransfer?.status==="cancelled"?"rejected":transaction?"approved":approval.status}`}>{internalTransfer?.status==="cancelled"?"Annulé":transaction?"Exécuté":approval.status==="pending"?"À valider":approval.status==="approved"?"Approuvé":"Refusé"}</em>{approval.decidedAt&&<small>{new Date(approval.decidedAt).toLocaleDateString("fr-FR")}</small>}</span><span className={`approval-actions ${printedOrder&&!transaction&&approval.status==="approved"?"order-ready":""}`}><span className="approval-main-action">{internalTransfer?.status==="cancelled"?<span className="internal-transfer-completed internal-transfer-cancelled"><em>× Transfert annulé</em><small>{internalTransfer.reference}</small></span>:internalTransfer?.status==="completed"?<span className="internal-transfer-completed"><em>✓ Transfert exécuté</em><small>{internalTransfer.reference}</small><button type="button" onClick={()=>reprintInternalTransferOrder(internalTransfer)}>Réimprimer l’ordre</button></span>:transaction?printedOrder?<span className="printed-order-action executed-order-action"><em>✓ Ordre imprimé</em><small>{printedOrder.internalNumber}<br/>{new Date(printedOrder.createdAt).toLocaleDateString("fr-FR")} · Règlement exécuté le {new Date(`${transaction.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</small><div className="executed-payment-documents"><button type="button" onClick={()=>reprintTransferOrder(printedOrder)}>Réimprimer la lettre de virement</button><button type="button" onClick={()=>printSettlementPaymentAdvice(transaction)}>Réimprimer le justificatif</button></div></span>:<span className="internal-transfer-completed"><em>✓ Règlement exécuté</em><small>{paymentMethodLabel(transaction.paymentMethod)}{transaction.paymentReference?` · N° ${transaction.paymentReference}`:""}<br/>{settledBank?.name||"Banque"} · {new Date(`${transaction.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</small><div className="executed-payment-documents"><button type="button" onClick={()=>printSettlementPaymentOrder(transaction)}>Réimprimer l’ordre de règlement</button><button type="button" onClick={()=>printSettlementPaymentAdvice(transaction)}>Réimprimer le justificatif</button></div></span>:approval.status==="pending"?<><button className="approve" disabled={busy} onClick={()=>decidePayment(approval,"approved")}>Valider</button><button className="reject" disabled={busy} onClick={()=>decidePayment(approval,"rejected")}>Refuser</button></>:approval.status==="approved"&&flow?printedOrder?<><span className="printed-order-action"><em>✓ Ordre imprimé</em><small>{printedOrder.internalNumber}<br/>{new Date(printedOrder.createdAt).toLocaleDateString("fr-FR")}</small><button type="button" onClick={()=>reprintTransferOrder(printedOrder)}>Réimprimer</button></span><button className="execute-payment-btn" disabled={busy} onClick={()=>executePrintedOrder(flow,printedOrder)}>Exécuter</button></>:<button className="transfer-btn print-transfer-btn" onClick={()=>openTransfer(flow)}>Imprimer l’ordre de virement</button>:<span>—</span>}</span><button className={`cancel-approval-btn ${transaction?"rollback-execution":""}`} disabled={busy||internalTransfer?.status==="cancelled"} onClick={()=>internalTransfer?.status==="completed"?cancelInternalTransfer(internalTransfer):transaction?cancelSettlement(transaction):cancelPaymentPreparation(approval)}>{internalTransfer?.status==="completed"?"Annuler le transfert":internalTransfer?.status==="cancelled"?"Annulé":transaction?"Revenir en arrière":"Annuler"}</button></span></div>})}</div>
        {filteredAccountFundings.length>0?<section className="legacy-funding-section"><header><div><b>Alimentations bancaires historiques</b><span>Opérations exécutées avant l’activation du nouveau circuit de validation.</span></div><em>{filteredAccountFundings.length} opération(s)</em></header><div className="legacy-funding-source-picker"><label><span>Société source du transfert</span><select value={legacyFundingSourceCompanyId} onChange={event=>{const companyId=event.target.value;setLegacyFundingSourceCompanyId(companyId);setLegacyFundingSourceBankId(banks.find(bank=>bank.companyId===companyId&&bank.isPrimary)?.id||banks.find(bank=>bank.companyId===companyId)?.id||0)}}><option value="">Choisir la société source</option>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></label><label><span>Compte bancaire source</span><select value={legacyFundingSourceBank?.id||0} onChange={event=>setLegacyFundingSourceBankId(Number(event.target.value))} disabled={!legacyFundingSourceCompanyId}><option value="">Choisir le compte source</option>{legacyFundingSourceBanks.map(bank=><option value={bank.id} key={bank.id}>{bank.name} · {bank.accountLabel} · solde {money(bank.balance)} · RIB {bank.rib||"non renseigné"}</option>)}</select></label><small>Ces informations seront imprimées sur le justificatif historique sans modifier les écritures bancaires.</small></div><div className="approval-table"><div className="approval-head"><span>Date</span><span>Société / Compte</span><span>Référence</span><span>Montant</span><span>État</span><span>Décision</span></div>{filteredAccountFundings.map(transaction=>{const bank=banks.find(item=>item.id===transaction.bankAccountId);const sourceExecution=bankTransactions.find(item=>item.direction==="debit"&&!item.cancelledAt&&item.reference===`SOURCE-${transaction.reference}`);return <div className="approval-row legacy-funding-row" key={`funding-${transaction.id}`}><span><b>{new Date(`${transaction.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</b><small>Écriture historique</small></span><span><b><i className={`company-dot company-${transaction.companyId}`}/>{companyName(transaction.companyId)}</b><small>{bank?.name||"Banque inconnue"} · {bank?.accountLabel||"—"}</small></span><span><b>{transaction.reference}</b><small>{transaction.party}</small></span><strong className="incoming-value">+ {money(transaction.amount)}</strong><span><em className={`approval-state ${sourceExecution?"approved":"pending"}`}>{sourceExecution?"Exécuté":"À exécuter"}</em>{sourceExecution&&<small>{companyName(sourceExecution.companyId)} · {new Date(`${sourceExecution.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</small>}</span><span className="approval-actions legacy-funding-actions"><button type="button" className="transfer-btn reprint" disabled={!legacyFundingSourceBank&& !sourceExecution} onClick={()=>reprintAccountFunding(transaction)}>Réimprimer le justificatif</button>{sourceExecution?<><button type="button" className="execute-payment-btn" disabled>Déjà exécuté</button><button type="button" className="cancel-legacy-funding-btn" disabled={busy} onClick={()=>cancelLegacyAccountFunding(transaction,sourceExecution)}>{busy?"Annulation…":"Annuler"}</button></>:<button type="button" className="execute-payment-btn" disabled={busy||!legacyFundingSourceBank||legacyFundingSourceCompanyId===transaction.companyId} onClick={()=>executeLegacyAccountFunding(transaction)}>{busy?"Exécution…":"Exécuter"}</button>}</span></div>})}</div></section>:accountFundingTransactions.length>0&&<div className="legacy-funding-hidden"><span>{accountFundingTransactions.length} alimentation(s) historique(s) sont masquées par les filtres actuels.</span><button type="button" onClick={()=>{setApprovalCompanyFilter("all");setApprovalSupplierSearch("");setApprovalAmountMin("");setApprovalAmountMax("");setApprovalStatusFilter("all");setApprovalDecisionFilter("all")}}>Afficher les alimentations</button></div>}
        {printMode==="approvals"&&<article className="print-approvals"><header><div><strong>Moventra ERP</strong><span>État global des validations et ordres de virement</span></div><time>{new Date().toLocaleDateString("fr-FR")}</time></header><h1>Pilotage des règlements fournisseurs</h1><div className="print-approval-kpis"><div>En attente <b>{pendingPaymentApprovals.length}</b></div><div>Approuvés <b>{paymentApprovals.filter(item=>item.status==="approved").length}</b></div><div>Refusés <b>{paymentApprovals.filter(item=>item.status==="rejected").length}</b></div><div>Ordres imprimés <b>{preparedTransferOrders.length}</b></div></div><h2>Capacité bancaire par société</h2><table><thead><tr><th>Société</th><th>Disponible</th><th>Engagements</th><th>Consommation</th><th>Reste après engagements</th><th>Situation</th></tr></thead><tbody>{preparedTransferSummaries.map(summary=><tr key={summary.companyId}><td>{companyName(summary.companyId)}</td><td>{money(summary.available)}</td><td className="outgoing-value">{money(summary.committed)}</td><td>{Number.isFinite(summary.ratio)?`${Math.round(summary.ratio*100)} %`:"Besoin"}</td><td>{money(summary.remaining)}</td><td>{summary.alert?"ALERTE > 90 %":summary.ratio>=.7?"Vigilance":"Maîtrisée"}</td></tr>)}</tbody></table><h2>Demandes et décisions</h2><table><thead><tr><th>Date</th><th>Société</th><th>Fournisseur</th><th>Facture</th><th>Date planifiée / modification</th><th>Montant</th><th>Décision</th><th>Ordre de virement</th></tr></thead><tbody>{paymentApprovals.map(approval=>{const flow=findFlow(approval.cashFlowId);const transaction=bankTransactions.find(item=>item.cashFlowId===approval.cashFlowId&&!item.cancelledAt);const settledBank=transaction?banks.find(item=>item.id===transaction.bankAccountId):undefined;const order=printedOrderForFlow(approval.cashFlowId);const internalTransfer=internalTransferForFlow(approval.cashFlowId);return <tr key={approval.id}><td>{new Date(approval.requestedAt).toLocaleDateString("fr-FR")}</td><td>{companyName(approval.companyId)}</td><td>{flow?.party||transaction?.party||"—"}</td><td>{flow?.reference||transaction?.reference||internalTransfer?.reference||`Flux n° ${approval.cashFlowId}`}</td><td>{approval.dateChanged&&approval.plannedDateBefore&&approval.plannedDateAfter?<><b>Date modifiée</b><small>Avant : {new Date(`${approval.plannedDateBefore}T00:00:00`).toLocaleDateString("fr-FR")} · Après : {new Date(`${approval.plannedDateAfter}T00:00:00`).toLocaleDateString("fr-FR")}</small></>:flow?.plannedDate?new Date(`${flow.plannedDate}T00:00:00`).toLocaleDateString("fr-FR"):"—"}</td><td className="outgoing-value">{money(flow?.amount||transaction?.amount||internalTransfer?.amount||0)}</td><td>{transaction?"Exécuté":approval.status==="pending"?"À valider":approval.status==="approved"?"Approuvé":"Refusé"}</td><td>{internalTransfer?.status==="completed"?`Exécuté · ${internalTransfer.reference}`:transaction?`Exécuté · ${settledBank?.name||"Banque"}`:order?`Imprimé · ${order.internalNumber}`:"Non imprimé"}</td></tr>})}</tbody></table><footer>État édité depuis Moventra ERP · Document de pilotage interne</footer></article>}
        <div className="settlements-info"><b>Contrôle interne</b><span>Ni l’ordre de virement ni le règlement bancaire ne peuvent être exécutés avant la validation du responsable hiérarchique.</span></div>
      </div>}
      {panel==="executedTransfers"&&<div className="executed-transfers-panel">
        <div className="executed-transfers-tools"><label><span>Société émettrice</span><select value={executedTransferCompany} onChange={event=>setExecutedTransferCompany(event.target.value)}><option value="all">Toutes les sociétés</option>{companies.map(company=><option key={company.id} value={company.id}>{company.name}</option>)}</select></label><label className="executed-transfer-search"><span>Référence, bénéficiaire ou banque</span><input type="search" value={executedTransferSearch} onChange={event=>setExecutedTransferSearch(event.target.value)} placeholder="Rechercher un virement exécuté…"/></label><div className="history-date-range"><label><span>Du</span><input type="date" value={executedTransferDateFrom} max={executedTransferDateTo||undefined} onChange={event=>setExecutedTransferDateFrom(event.target.value)}/></label><label><span>Au</span><input type="date" value={executedTransferDateTo} min={executedTransferDateFrom||undefined} onChange={event=>setExecutedTransferDateTo(event.target.value)}/></label>{(executedTransferDateFrom||executedTransferDateTo)&&<button type="button" onClick={()=>{setExecutedTransferDateFrom("");setExecutedTransferDateTo("")}}>Toute la période</button>}</div><button className="history-print-btn" disabled={!executedTransferRows.length} onClick={()=>{setPrintMode("executedTransfers");window.setTimeout(()=>window.print(),100)}}>↓ Imprimer l’état</button></div>
        <div className="executed-transfer-kpis"><div><span>Virements exécutés</span><b>{executedTransferRows.length}</b></div><div><span>Montant total</span><b className="outgoing-value">{moneyExact(executedTransferRows.reduce((sum,row)=>sum+row.transaction.amount,0))}</b></div><div><span>Sociétés émettrices</span><b>{new Set(executedTransferRows.map(row=>row.transaction.companyId)).size}</b></div></div>
        <div className="executed-transfers-table"><div className="executed-transfers-head"><span>Date / référence</span><span>Expéditeur</span><span>Compte débité</span><span>Bénéficiaire</span><span>Compte crédité</span><span>Facture / objet</span><span>Montant</span><span>Validation</span></div>{executedTransferRows.length===0?<p className="empty-state">Aucun virement exécuté ne correspond aux filtres sélectionnés.</p>:executedTransferRows.map(row=><article key={row.transaction.id}><span><b>{new Date(`${row.transaction.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</b><small>{row.transaction.reference}</small><small>Exécuté le {new Date(row.transaction.createdAt).toLocaleString("fr-FR")}</small></span><span><b>{companyName(row.transaction.companyId)}</b><small>{row.sourceBank?.accountHolder||companies.find(company=>company.id===row.transaction.companyId)?.name||"—"}</small></span><span><b>{row.sourceBank?.name||"Banque inconnue"}</b><small>{row.sourceBank?.accountLabel||"—"}</small><small>{row.sourceBank?.rib?`RIB ${row.sourceBank.rib}`:"RIB non renseigné"}</small></span><span><b>{row.beneficiaryName}</b><small>{row.internalTransfer?`Transfert vers ${companyName(row.internalTransfer.toCompanyId)}`:row.flow?.party||row.transaction.party}</small></span><span><b>{row.beneficiaryBank}</b><small>{row.beneficiaryAccount}</small></span><span><b>{row.flow?.reference||row.transaction.reference}</b><small>{row.order?.purpose||row.internalTransfer?.reference||row.transaction.party}</small><small>{row.order?.internalNumber?`Ordre ${row.order.internalNumber}`:"Exécution bancaire directe"}</small></span><strong className="outgoing-value">− {moneyExact(row.transaction.amount)}</strong><span><b>{row.approval?.decidedBy||"Validation enregistrée"}</b><small>{row.approval?.decidedAt?new Date(row.approval.decidedAt).toLocaleString("fr-FR"):"Date de décision non disponible"}</small><small>{row.approval?.decisionNote||"Virement exécuté"}</small>{!row.internalTransfer&&row.flow&&<button type="button" className="supplier-advice-btn" title="Imprimer ou enregistrer cet avis en PDF" onClick={()=>printSupplierPaymentAdvice(row)}>Avis fournisseur</button>}</span></article>)}</div>
        <div className="settlements-info"><b>État financier non modifiable</b><span>Seuls les débits bancaires réellement exécutés et non annulés sont affichés. Chaque ligne restitue les sociétés, comptes, RIB, bénéficiaire, facture, validation et référence bancaire.</span></div>
        {printMode==="executedTransfers"&&<article className="print-history-state executed-transfers-print"><header><div><strong>Moventra ERP</strong><span>État détaillé des virements exécutés</span></div><time>{new Date().toLocaleDateString("fr-FR")}</time></header><h1>{executedTransferCompany==="all"?"Toutes les sociétés":companyName(executedTransferCompany)}</h1><p>Période : {executedTransferDateFrom?new Date(`${executedTransferDateFrom}T00:00:00`).toLocaleDateString("fr-FR"):"origine"} au {executedTransferDateTo?new Date(`${executedTransferDateTo}T00:00:00`).toLocaleDateString("fr-FR"):"jour courant"} · {executedTransferRows.length} virement(s) · Total {moneyExact(executedTransferRows.reduce((sum,row)=>sum+row.transaction.amount,0))}</p><table><thead><tr><th>Date / référence</th><th>Expéditeur</th><th>Compte débité</th><th>Bénéficiaire</th><th>Compte crédité</th><th>Facture / objet</th><th>Montant</th><th>Validation</th></tr></thead><tbody>{executedTransferRows.map(row=><tr key={row.transaction.id}><td><b>{new Date(`${row.transaction.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</b><small>{row.transaction.reference}</small></td><td><b>{companyName(row.transaction.companyId)}</b><small>{row.sourceBank?.accountHolder||"—"}</small></td><td><b>{row.sourceBank?.name||"—"}</b><small>{row.sourceBank?.accountLabel||"—"} · {row.sourceBank?.rib?`RIB ${row.sourceBank.rib}`:"RIB non renseigné"}</small></td><td><b>{row.beneficiaryName}</b><small>{row.flow?.party||row.transaction.party}</small></td><td><b>{row.beneficiaryBank}</b><small>{row.beneficiaryAccount}</small></td><td><b>{row.flow?.reference||row.transaction.reference}</b><small>{row.order?.purpose||row.internalTransfer?.reference||row.transaction.party}{row.order?.internalNumber?` · Ordre ${row.order.internalNumber}`:""}</small></td><td className="outgoing-value">− {moneyExact(row.transaction.amount)}</td><td><b>{row.approval?.decidedBy||"Validation enregistrée"}</b><small>{row.approval?.decidedAt?new Date(row.approval.decidedAt).toLocaleString("fr-FR"):"—"}</small></td></tr>)}</tbody></table><footer>État édité depuis Moventra ERP · Virements bancaires exécutés et non annulés · Document de contrôle financier</footer></article>}
      </div>}      {panel==="settlements"&&<div className="settlements-panel">
        <div className="settlements-tools"><label><span>Société</span><select value={settlementHistoryCompany} onChange={event=>setSettlementHistoryCompany(event.target.value)}><option value="all">Toutes les sociétés</option>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></label><label><span>Nature</span><select value={settlementHistoryKind} onChange={event=>setSettlementHistoryKind(event.target.value as "all"|"client"|"fournisseur")}><option value="all">Tous les mouvements</option><option value="client">Encaissements clients</option><option value="fournisseur">Règlements fournisseurs</option></select></label><label className="settlements-search"><span>Client, fournisseur ou référence</span><input type="search" value={settlementHistorySearch} onChange={event=>setSettlementHistorySearch(event.target.value)} placeholder="Rechercher…"/></label><div className="history-date-range"><label><span>Du</span><input type="date" value={settlementHistoryDateFrom} max={settlementHistoryDateTo||undefined} onChange={event=>setSettlementHistoryDateFrom(event.target.value)}/></label><label><span>Au</span><input type="date" value={settlementHistoryDateTo} min={settlementHistoryDateFrom||undefined} onChange={event=>setSettlementHistoryDateTo(event.target.value)}/></label>{(settlementHistoryDateFrom||settlementHistoryDateTo)&&<button type="button" onClick={()=>{setSettlementHistoryDateFrom("");setSettlementHistoryDateTo("")}}>Toute la période</button>}</div><button className="ghost-btn" onClick={exportSettlementHistory}>↓ Télécharger Excel</button><button className="history-print-btn" disabled={settlementTransactions.length===0} onClick={()=>{setPrintMode("settlements");window.setTimeout(()=>window.print(),100)}}>↓ Imprimer l’état</button></div>
        <div className="settlements-kpis"><div><span>Règlements actifs</span><b>{activeSettlementTransactions.length}</b></div><div><span>Encaissements validés</span><b className="incoming-value">{money(activeSettlementTransactions.filter(item=>item.kind==="client").reduce((sum,item)=>sum+item.amount,0))}</b></div><div><span>Paiements validés</span><b className="outgoing-value">{money(activeSettlementTransactions.filter(item=>item.kind==="fournisseur").reduce((sum,item)=>sum+item.amount,0))}</b></div></div>
        <div className="settlements-table"><div className="settlements-head"><span>Date</span><span>Société / Tiers</span><span>Banque</span><span>Type</span><span>Montant</span><span>État</span><span>Action</span></div>{settlementTransactions.length===0?<p className="empty-state">Aucun règlement validé ne correspond à ces filtres.</p>:settlementTransactions.map(transaction=>{const bank=banks.find(item=>item.id===transaction.bankAccountId);const flow=findFlow(transaction.cashFlowId);const displayedParty=flow?.beneficiaryName||transaction.party;return <div className={`settlements-row ${transaction.cancelledAt?"cancelled":""}`} key={transaction.id}><span>{new Date(`${transaction.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</span><span><b><i className={`company-dot company-${transaction.companyId}`}/>{displayedParty}</b><small>{companyName(transaction.companyId)} · {transaction.reference}</small></span><span><b>{bank?.name||"Banque inconnue"}</b><small>{bank?.accountLabel||"—"}</small>{transaction.kind==="fournisseur"&&<small>{paymentMethodLabel(transaction.paymentMethod)}{transaction.paymentReference?` · N° ${transaction.paymentReference}`:""}</small>}</span><span><em className={`settlement-kind ${transaction.kind}`}>{transaction.kind==="client"?"Encaissement":"Règlement"}</em></span><strong className={transaction.kind==="client"?"incoming-value":"outgoing-value"}>{transaction.kind==="client"?"+ ":"− "}{money(transaction.amount)}</strong><span>{transaction.cancelledAt?<em className="settlement-state cancelled">Annulé</em>:<em className="settlement-state valid">Validé</em>}{transaction.cancelledAt&&<small>{new Date(transaction.cancelledAt).toLocaleDateString("fr-FR")}</small>}</span><div className="settlement-row-actions">{transaction.kind==="fournisseur"&&!transaction.cancelledAt&&<button type="button" className="print-settlement-proof-btn" onClick={()=>printSettlementPaymentAdvice(transaction)}>Imprimer justificatif</button>}<button className="cancel-settlement-btn" disabled={Boolean(transaction.cancelledAt)||busy} onClick={()=>cancelSettlement(transaction)}>{transaction.cancelledAt?"Déjà annulé":"Annuler"}</button></div></div>})}</div>
        {printMode==="settlements"&&<article className="print-history-state"><header><div><strong>Moventra ERP</strong><span>État des règlements bancaires</span></div><time>{new Date().toLocaleDateString("fr-FR")}</time></header><h1>{settlementHistoryCompany==="all"?"Toutes les sociétés":companyName(settlementHistoryCompany)}</h1><p>Période : {settlementHistoryDateFrom?new Date(`${settlementHistoryDateFrom}T00:00:00`).toLocaleDateString("fr-FR"):"origine"} au {settlementHistoryDateTo?new Date(`${settlementHistoryDateTo}T00:00:00`).toLocaleDateString("fr-FR"):"jour courant"} · {settlementTransactions.length} mouvement(s)</p><table><thead><tr><th>Date</th><th>Société</th><th>Tiers</th><th>Banque</th><th>Référence</th><th>Nature</th><th>Montant</th><th>État</th></tr></thead><tbody>{settlementTransactions.map(transaction=>{const bank=banks.find(item=>item.id===transaction.bankAccountId);return <tr key={transaction.id}><td>{new Date(`${transaction.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</td><td>{companyName(transaction.companyId)}</td><td>{transaction.party}</td><td>{bank?.name||"—"}<small>{bank?.accountLabel||"—"}</small></td><td>{transaction.reference}</td><td>{transaction.kind==="client"?"Encaissement":"Règlement"}</td><td className={transaction.kind==="client"?"incoming-value":"outgoing-value"}>{transaction.kind==="client"?"+ ":"− "}{moneyExact(transaction.amount)}</td><td>{transaction.cancelledAt?"Annulé":"Validé"}</td></tr>})}</tbody></table><footer>État édité depuis Moventra ERP · Historique des règlements filtré par période</footer></article>}
        <div className="settlements-info"><b>Annulation avec traçabilité</b><span>L’annulation contre-passe le mouvement bancaire, conserve l’écriture dans l’historique et remet automatiquement la facture dans le plan de trésorerie.</span></div>
      </div>}
      {panel==="bankHistory"&&<div className="bank-history-panel">
        <div className="history-toolbar"><div className="history-bank-picker"><label>Compte bancaire</label><select value={historyBankId} onChange={event=>setHistoryBankId(Number(event.target.value))}>{(scopedBanks.length?scopedBanks:banks).map(bank=><option value={bank.id} key={bank.id}>{companyName(bank.companyId)} · {bank.name} · {bank.accountLabel}</option>)}</select></div><div className="history-date-range"><label><span>Du</span><input type="date" value={historyDateFrom} max={historyDateTo||undefined} onChange={event=>setHistoryDateFrom(event.target.value)}/></label><label><span>Au</span><input type="date" value={historyDateTo} min={historyDateFrom||undefined} onChange={event=>setHistoryDateTo(event.target.value)}/></label>{(historyDateFrom||historyDateTo)&&<button type="button" onClick={()=>{setHistoryDateFrom("");setHistoryDateTo("")}}>Toute la période</button>}</div><button type="button" className="history-print-btn" disabled={!historyBank||historyTransactions.length===0} onClick={()=>{setPrintMode("bankHistory");window.setTimeout(()=>window.print(),100)}}>↓ Imprimer l’état</button></div>
        {historyBank&&<><div className="history-kpis"><div><span>Solde actuel</span><b>{money(historyBank.balance)}</b></div><div><span>Crédits enregistrés</span><b className="incoming-value">{money(historyTransactions.filter(item=>!item.cancelledAt&&item.direction==="credit").reduce((sum,item)=>sum+item.amount,0))}</b></div><div><span>Débits enregistrés</span><b className="outgoing-value">{money(historyTransactions.filter(item=>!item.cancelledAt&&item.direction==="debit").reduce((sum,item)=>sum+item.amount,0))}</b></div></div><div className="history-table"><div className="history-head"><span>Date</span><span>Expéditeur</span><span>Bénéficiaire</span><span>Mouvement</span><span>Montant</span><span>Solde après</span></div>{historyTransactions.length===0?<p className="empty-state">Aucun mouvement enregistré pour cette banque.</p>:historyTransactions.map(transaction=>{const parties=historyPartyDetails(transaction);return <div className={`history-row ${transaction.cancelledAt?"cancelled":""}`} key={transaction.id}><span>{new Date(`${transaction.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</span><span className="history-party sender"><em>Expéditeur</em><b>{parties.sender.name}</b><small>{parties.sender.detail}</small></span><span className="history-party receiver"><em>Bénéficiaire</em><b>{parties.receiver.name}</b><small>{parties.receiver.detail}</small></span><span className="history-movement"><b>{transaction.party}</b><small>{transaction.reference} · {transaction.cancelledAt?"Mouvement annulé":transaction.reference.startsWith("OUVERTURE-")?"Solde d’ouverture":transaction.reference.startsWith("VC-")?"Virement entre comptes":transaction.kind==="ajustement"&&transaction.party.startsWith("Annulation")?"Contre-passation":transaction.kind==="ajustement"?"Ajustement manuel":transaction.kind==="client"?"Encaissement client":"Règlement fournisseur"}</small></span><strong className={transaction.direction==="credit"?"incoming-value":"outgoing-value"}>{transaction.direction==="credit"?"+ ":"− "}{money(transaction.amount)}</strong><b>{money(transaction.balanceAfter)}</b></div>})}</div></>}
        {printMode==="bankHistory"&&historyBank&&<article className="print-bank-history"><header><div><strong>Moventra ERP</strong><span>État détaillé du compte bancaire</span></div><time>{new Date().toLocaleDateString("fr-FR")}</time></header><h1>{companyName(historyBank.companyId)} · {historyBank.name}</h1><p>{historyBank.accountLabel}{historyBank.rib?` · RIB ${historyBank.rib}`:""} · Période : {historyDateFrom?new Date(`${historyDateFrom}T00:00:00`).toLocaleDateString("fr-FR"):"origine"} au {historyDateTo?new Date(`${historyDateTo}T00:00:00`).toLocaleDateString("fr-FR"):"jour courant"} · {historyTransactions.length} mouvement(s)</p><div className="print-bank-kpis"><div>Solde actuel<b>{moneyExact(historyBank.balance)}</b></div><div>Crédits enregistrés<b className="incoming-value">{moneyExact(historyTransactions.filter(item=>!item.cancelledAt&&item.direction==="credit").reduce((sum,item)=>sum+item.amount,0))}</b></div><div>Débits enregistrés<b className="outgoing-value">{moneyExact(historyTransactions.filter(item=>!item.cancelledAt&&item.direction==="debit").reduce((sum,item)=>sum+item.amount,0))}</b></div></div><table><thead><tr><th>Date</th><th>Expéditeur</th><th>Bénéficiaire</th><th>Référence / mouvement</th><th>Montant</th><th>Solde après</th></tr></thead><tbody>{historyTransactions.map(transaction=>{const parties=historyPartyDetails(transaction);return <tr className={transaction.cancelledAt?"cancelled":""} key={transaction.id}><td>{new Date(`${transaction.transactionDate}T00:00:00`).toLocaleDateString("fr-FR")}</td><td><b>{parties.sender.name}</b><small>{parties.sender.detail}</small></td><td><b>{parties.receiver.name}</b><small>{parties.receiver.detail}</small></td><td><b>{transaction.reference}</b><small>{transaction.party}{transaction.cancelledAt?" · Annulé":""}</small></td><td className={transaction.direction==="credit"?"incoming-value":"outgoing-value"}>{transaction.direction==="credit"?"+ ":"− "}{moneyExact(transaction.amount)}</td><td>{moneyExact(transaction.balanceAfter)}</td></tr>})}</tbody></table><footer>État édité depuis Moventra ERP · Historique bancaire détaillé · Document de contrôle interne</footer></article>}
      </div>}
      {panel==="forecast"&&<>
        <div className="forecast-kpis"><div><span>Encaissements prévus</span><b className="incoming-value">{money(forecasts.filter(f=>f.kind==="client"&&!f.excluded).reduce((s,f)=>s+f.amount,0))}</b></div><div><span>Décaissements prévus</span><b className="outgoing-value">{money(forecasts.filter(f=>f.kind==="fournisseur"&&!f.excluded).reduce((s,f)=>s+f.amount,0))}</b></div><div><span>Lignes actives</span><b>{forecasts.filter(f=>!f.excluded).length}</b></div></div>
        <div className="template-picker"><label>Partir d’un modèle enregistré</label><select value={selectedTemplateId} onChange={e=>setSelectedTemplateId(Number(e.target.value))}><option value={0}>Prévision libre</option>{templates.map(template=><option value={template.id} key={template.id}>{companyName(template.companyId)} · {template.label}</option>)}</select><button type="button" onClick={()=>setPanel("settings")}>Gérer les modèles</button></div>
        <form key={selectedTemplateId} onSubmit={addForecast} className="forecast-form">
          <div className="field"><label>Société</label><select name="companyId" defaultValue={selectedTemplate?.companyId||selectedCompanyId} required>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></div>
          <div className="field"><label>Nature</label><select name="kind" defaultValue={selectedTemplate?.kind||"fournisseur"} required><option value="fournisseur">Décaissement futur</option><option value="client">Encaissement futur</option></select></div>
          <div className="field"><label>Date prévue</label><input name="plannedDate" type="date" required/></div>
          <div className="field"><label>Montant (MAD)</label><input name="amount" type="number" min="0.01" step="0.01" defaultValue={selectedTemplate?.defaultAmount||undefined} required/></div>
          <div className="field full"><label>Libellé de la prévision</label><input name="label" defaultValue={selectedTemplate?.label||""} placeholder="Ex. Paie septembre, TVA, achat véhicule…" required/></div><div className="field full"><label>Bénéficiaire prévu <span className="optional-label">Facultatif</span></label><input name="beneficiaryName" placeholder="Nom de la personne ou société qui recevra le règlement"/><small className="field-help">Il pourra encore être corrigé par le responsable avant validation.</small></div>
          <div className="field full"><label>Note facultative</label><input name="note" defaultValue={selectedTemplate?.note||""} placeholder="Hypothèse ou détail"/></div>
          <div className="drawer-actions field full"><button className="primary-btn" type="submit">＋ Ajouter la prévision</button></div>
        </form>
        <div className="forecast-list"><div className="forecast-list-head"><b>Prévisions enregistrées</b><span>{forecasts.length} ligne(s)</span></div>{forecasts.length===0?<p className="empty-state">Aucune prévision manuelle pour le moment.</p>:forecasts.map(flow=>{const editable=canEditFlow(flow);return <div className={`forecast-row ${flow.excluded?"excluded":""} ${editable?"editable":"locked"}`} key={flow.id}><span className={`forecast-kind ${flow.kind}`}>{flow.kind==="client"?"＋":"−"}</span><div><b>{flow.party} <small className="inline-company">{companyName(flow.companyId)}</small></b><small>{new Date(flow.plannedDate).toLocaleDateString("fr-FR")} · {flow.note||"Sans note"}</small></div><strong className={flow.kind==="client"?"incoming-value":"outgoing-value"}>{money(flow.amount)}</strong><button className={`calc-toggle ${flow.excluded?"off":"on"}`} disabled={!editable} title={editable?"Inclure ou exclure cette prévision":"Prévision verrouillée après règlement"} onClick={()=>toggleExcluded(flow)}><i/><span>{flow.excluded?"Ignorée":"Incluse"}</span></button><div className="forecast-row-actions">{editable?<><button type="button" className="edit-flow-btn" onClick={()=>openFlowEditor(flow)}>Modifier</button><button type="button" className="delete-flow-btn" disabled={busy} onClick={()=>deleteFlow(flow)}>Supprimer</button></>:<span className="forecast-locked-state">{flowLockReason(flow)||"Verrouillée"}</span>}</div></div>})}</div>
      </>}
      {panel==="reports"&&<div className="reports-panel">
        <div className="report-actions"><div><b>{companyFilter==="all"?"Rapport consolidé":companyName(companyFilter)}</b><span>{scopedFlows.length} ligne(s) · situation au {new Date().toLocaleDateString("fr-FR")}</span></div><button className="ghost-btn" onClick={()=>{setPrintMode("report");window.setTimeout(()=>window.print(),100)}}>Imprimer</button><button className="primary-btn" onClick={exportReport}>Exporter Excel</button></div>
        <div className="report-kpis"><div><span>Banques disponibles</span><b>{money(bankTotal)}</b></div><div><span>Encaissements</span><b className="incoming-value">{money(totals.incoming)}</b></div><div><span>Règlements</span><b className="outgoing-value">{money(totals.outgoing)}</b></div><div><span>Solde projeté</span><b>{money(totals.projected)}</b></div></div>
        <div className="report-company-grid">{companies.filter(company=>companyFilter==="all"||company.id===companyFilter).map(company=>{const companyFlows=flows.filter(flow=>flow.companyId===company.id&&!flow.excluded);const incoming=companyFlows.filter(flow=>flow.kind==="client").reduce((sum,flow)=>sum+flow.amount,0);const outgoing=companyFlows.filter(flow=>flow.kind==="fournisseur").reduce((sum,flow)=>sum+flow.amount,0);const cashGross=banks.filter(bank=>bank.companyId===company.id).reduce((sum,bank)=>sum+bank.balance,0);const reserveExcluded=reserves.filter(reserve=>reserve.companyId===company.id&&!reserve.includedInTreasury).reduce((sum,reserve)=>sum+reserve.amount,0);const cash=cashGross-reserveExcluded;return <div key={company.id}><span className={`company-badge company-${company.id}`}>{company.shortName}</span><dl><div><dt>Banques disponibles</dt><dd>{money(cash)}</dd></div><div><dt>À encaisser</dt><dd className="incoming-value">{money(incoming)}</dd></div><div><dt>À régler</dt><dd className="outgoing-value">{money(outgoing)}</dd></div><div><dt>Projection</dt><dd className={cash+incoming-outgoing<0?"red":""}>{money(cash+incoming-outgoing)}</dd></div></dl></div>})}</div>
        <div className="report-table"><div className="report-table-head"><span>Société</span><span>Type</span><span>Tiers</span><span>Date</span><span>Montant</span></div>{scopedFlows.slice().sort((a,b)=>a.plannedDate.localeCompare(b.plannedDate)).slice(0,25).map(flow=><div key={flow.id}><span>{companyName(flow.companyId)}</span><span className={flow.kind==="client"?"incoming-value":"outgoing-value"}>{flow.kind==="client"?"Encaissement":"Règlement"}</span><span>{flow.party}<small>{flow.reference}</small></span><span>{new Date(flow.plannedDate).toLocaleDateString("fr-FR")}</span><span className={flow.kind==="client"?"incoming-value":"outgoing-value"}>{money(flow.amount)}</span></div>)}</div>
        <article className="print-report"><header><div><strong>Moventra ERP</strong><span>{companyFilter==="all"?"Rapport consolidé":companyName(companyFilter)}</span></div><time>{new Date().toLocaleDateString("fr-FR")}</time></header><h1>Situation de trésorerie</h1><div className="print-report-totals"><div>Banques disponibles <b>{money(bankTotal)}</b></div><div>Encaissements <b className="incoming-value">{money(totals.incoming)}</b></div><div>Règlements <b className="outgoing-value">{money(totals.outgoing)}</b></div><div>Solde projeté <b>{money(totals.projected)}</b></div></div><table><thead><tr><th>Société</th><th>Type</th><th>Tiers</th><th>Date</th><th>Montant</th></tr></thead><tbody>{scopedFlows.map(flow=><tr key={flow.id}><td>{companyName(flow.companyId)}</td><td className={flow.kind==="client"?"incoming-value":"outgoing-value"}>{flow.kind==="client"?"Encaissement":"Règlement"}</td><td>{flow.party} · {flow.reference}</td><td>{flow.plannedDate}</td><td className={flow.kind==="client"?"incoming-value":"outgoing-value"}>{money(flow.amount)}</td></tr>)}</tbody></table></article>
      </div>}
      {panel==="settings"&&<div className="settings-stack">
        <section className="settings-section"><div className="settings-title"><div><h3>Modèles de prévisions</h3><p>Créez des écritures réutilisables : salaires, leasing, impôts ou autres.</p></div><span>{templates.length} modèle(s)</span></div>
          <form className="compact-form" onSubmit={addTemplate}><div className="field"><label>Société</label><select name="companyId" defaultValue={selectedCompanyId}>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></div><div className="field"><label>Nature</label><select name="kind"><option value="fournisseur">Décaissement</option><option value="client">Encaissement</option></select></div><div className="field"><label>Nom du modèle</label><input name="label" placeholder="Ex. Salaires mensuels" required/></div><div className="field"><label>Montant par défaut</label><input name="defaultAmount" type="number" min="0" step="0.01" defaultValue="0"/></div><div className="field full"><label>Note</label><input name="note" placeholder="Libellé ou hypothèse par défaut"/></div><div className="field full settings-submit"><button className="primary-btn">Enregistrer le modèle</button></div></form>
          <div className="settings-list">{templates.map(template=><div key={template.id}><span className={`forecast-kind ${template.kind}`}>{template.kind==="client"?"＋":"−"}</span><p><b>{template.label}</b><small>{companyName(template.companyId)} · <span className={template.kind==="client"?"incoming-value":"outgoing-value"}>{template.defaultAmount?money(template.defaultAmount):"Montant à saisir"}</span></small></p></div>)}</div>
        </section>
        <section className="settings-section"><div className="settings-title"><div><h3>Banques de nos sociétés</h3><p>Cliquez sur une ligne pour consulter ou modifier ses coordonnées.</p></div><button className="primary-btn settings-add" onClick={()=>setBankEditor("new")}>＋ Ajouter une banque</button></div>
          <div className="bank-directory"><div className="bank-directory-head"><span>Société</span><span>Banque et compte</span><span>RIB / IBAN</span><span>Solde</span><span>Actions</span></div>{banks.map(bank=><div className={`bank-directory-row ${bankEditor===bank.id?"selected":""}`} key={bank.id} onClick={()=>setBankEditor(bank.id)} role="button" tabIndex={0} onKeyDown={event=>{if(event.key==="Enter")setBankEditor(bank.id)}}><span><i className={`company-dot company-${bank.companyId}`}/>{companyName(bank.companyId)}</span><span><b>{bank.name}{bank.isPrimary&&<em className="primary-bank-badge">Principale</em>}</b><small>{bank.accountLabel}</small></span><span><b>{bank.rib||"RIB à renseigner"}</b><small>{bank.iban||bank.swift||"—"}</small></span><span><b>{money(bank.balance)}</b></span><span className="row-actions">{!bank.isPrimary&&<button type="button" className="primary-action" onClick={event=>{event.stopPropagation();makePrimaryBank(bank)}}>Définir principale</button>}<button type="button" onClick={event=>{event.stopPropagation();setBankEditor(bank.id)}}>Modifier</button><button type="button" className="danger" onClick={event=>{event.stopPropagation();deleteCompanyBank(bank)}}>Supprimer</button></span></div>)}</div>
          {bankEditor==="new"&&<form className="bank-editor" onSubmit={addCompanyBank}><div className="bank-editor-head"><div><b>Nouvelle banque</b><small>Ajoutez un compte bancaire à la société sélectionnée.</small></div><button type="button" className="close-mini" onClick={()=>setBankEditor(null)}>×</button></div><div className="compact-form"><div className="field"><label>Société</label><select name="companyId" defaultValue={selectedCompanyId}>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></div><div className="field"><label>Banque</label><input name="name" required/></div><div className="field"><label>Libellé du compte</label><input name="accountLabel" required/></div><div className="field"><label>Solde initial</label><input name="balance" type="number" step="0.01" defaultValue="0"/></div><div className="field"><label>Titulaire</label><input name="accountHolder"/></div><div className="field"><label>RIB</label><input name="rib" inputMode="numeric" pattern="[0-9 ]*" minLength={24} maxLength={29} placeholder="24 chiffres" required/></div><div className="field"><label>IBAN</label><input name="iban"/></div><div className="field"><label>SWIFT / BIC</label><input name="swift"/></div><div className="field full"><label>Agence / adresse</label><input name="branchAddress"/></div><div className="drawer-actions field full"><button type="button" className="ghost-btn" onClick={()=>setBankEditor(null)}>Annuler</button><button className="primary-btn">Ajouter la banque</button></div></div></form>}
          {typeof bankEditor==="number"&&banks.filter(bank=>bank.id===bankEditor).map(bank=><form className="bank-editor" key={bank.id} onSubmit={event=>updateCompanyBank(event,bank.id)}><div className="bank-editor-head"><div><b>Modifier {bank.name}</b><small>{companyName(bank.companyId)} · {bank.accountLabel}</small></div><button type="button" className="close-mini" onClick={()=>setBankEditor(null)}>×</button></div><div className="compact-form"><div className="field"><label>Banque</label><input name="name" defaultValue={bank.name} required/></div><div className="field"><label>Libellé du compte</label><input name="accountLabel" defaultValue={bank.accountLabel} required/></div><div className="field"><label>Titulaire</label><input name="accountHolder" defaultValue={bank.accountHolder||""}/></div><div className="field"><label>RIB</label><input name="rib" defaultValue={bank.rib||""} inputMode="numeric" pattern="[0-9 ]*" minLength={24} maxLength={29} placeholder="24 chiffres" required/></div><div className="field"><label>IBAN</label><input name="iban" defaultValue={bank.iban||""}/></div><div className="field"><label>SWIFT / BIC</label><input name="swift" defaultValue={bank.swift||""}/></div><div className="field full"><label>Agence / adresse</label><input name="branchAddress" defaultValue={bank.branchAddress||""}/></div><div className="drawer-actions field full"><button type="button" className="danger-btn" onClick={()=>deleteCompanyBank(bank)}>Supprimer</button><button type="button" className="ghost-btn" onClick={()=>setBankEditor(null)}>Annuler</button><button className="primary-btn">Enregistrer</button></div></div></form>)}
        </section>
        <section id="supplier-bank-settings" className="settings-section"><div className="settings-title"><div><h3>Banques fournisseurs</h3><p>Enregistrez le bénéficiaire et son RIB pour préparer les virements.</p></div><span>{supplierBanks.length} compte(s)</span></div>
          <form className="compact-form supplier-bank-form" onSubmit={addSupplierBank}><div className="field"><label>Société payeuse</label><select name="companyId" value={supplierBankCompanyId} onChange={event=>{setSupplierBankCompanyId(event.target.value);setSupplierBankSupplierName("");setSupplierBankBeneficiaryName("")}}>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></div><div className="field supplier-search"><label>Rechercher le fournisseur</label><div><span aria-hidden="true">⌕</span><input name="supplierName" list="supplier-bank-candidates" value={supplierBankSupplierName} onChange={event=>{setSupplierBankSupplierName(event.target.value);setSupplierBankBeneficiaryName(event.target.value)}} placeholder="Saisir le nom du fournisseur…" autoComplete="off" required/></div><datalist id="supplier-bank-candidates">{supplierBankCandidates.map(name=><option value={name} key={name}/>)}</datalist><small>{supplierBankCandidates.length} fournisseur(s) proposé(s) depuis les états de {companyName(supplierBankCompanyId)}</small></div><div className="field"><label>Bénéficiaire</label><input name="beneficiaryName" value={supplierBankBeneficiaryName} onChange={event=>setSupplierBankBeneficiaryName(event.target.value)} placeholder="Repris automatiquement du fournisseur" required/></div><div className="field"><label>Banque</label><div className="bank-name-picker"><select value={supplierBankNameMode==="new"?"__new__":supplierBankName} onChange={event=>{if(event.target.value==="__new__"){setSupplierBankNameMode("new");setSupplierBankName("")}else{setSupplierBankNameMode("existing");setSupplierBankName(event.target.value)}}} required><option value="">Choisir une banque enregistrée</option>{knownBankNames.map(name=><option value={name} key={normalized(name)}>{name}</option>)}<option value="__new__">＋ Ajouter une nouvelle banque</option></select>{supplierBankNameMode==="new"&&<input name="bankName" value={supplierBankName} onChange={event=>setSupplierBankName(event.target.value)} placeholder="Nom de la nouvelle banque" autoFocus required/>}{supplierBankNameMode==="existing"&&<input type="hidden" name="bankName" value={supplierBankName}/>}<small>{supplierBankNameMode==="new"?"Ce nouveau nom sera proposé lors des prochaines saisies.":`${knownBankNames.length} banque(s) déjà enregistrée(s).`}</small></div></div><div className="field"><label>RIB</label><input name="rib" inputMode="numeric" pattern="[0-9 ]*" minLength={24} maxLength={29} placeholder="24 chiffres" required/></div><div className="field"><label>IBAN</label><input name="iban"/></div><div className="field"><label>SWIFT / BIC</label><input name="swift"/></div><div className="field settings-submit"><button className="primary-btn">Ajouter le compte</button></div></form>
          <div className="supplier-bank-list">{supplierBanks.length===0?<p className="empty-state">Aucune banque fournisseur enregistrée.</p>:supplierBanks.map(bank=><div className={supplierBankEditor===bank.id?"selected":""} key={bank.id}><span className="supplier-bank-icon">B</span><p><b>{bank.supplierName}</b><small>{companyName(bank.companyId)} · Bénéficiaire {bank.beneficiaryName}</small><small>{bank.bankName} · RIB {bank.rib}</small></p><span className="supplier-bank-row-actions"><button type="button" onClick={()=>setSupplierBankEditor(bank.id)}>Modifier</button><button type="button" className="danger" onClick={()=>deleteSupplierBank(bank)}>Supprimer</button></span></div>)}</div>
          {supplierBanks.filter(bank=>bank.id===supplierBankEditor).map(bank=><form className="supplier-bank-editor" onSubmit={event=>updateSupplierBank(event,bank.id)} key={bank.id}><div className="bank-editor-head"><div><b>Modifier la banque de {bank.supplierName}</b><small>{companyName(bank.companyId)} · les changements seront utilisés pour les prochains virements.</small></div><button type="button" className="close-mini" onClick={()=>setSupplierBankEditor(null)}>×</button></div><div className="compact-form"><div className="field"><label>Fournisseur</label><input name="supplierName" list="supplier-bank-candidates" defaultValue={bank.supplierName} required/></div><div className="field"><label>Bénéficiaire</label><input name="beneficiaryName" defaultValue={bank.beneficiaryName} required/></div><div className="field"><label>Banque</label><input name="bankName" defaultValue={bank.bankName} required/></div><div className="field"><label>RIB</label><input name="rib" defaultValue={bank.rib} inputMode="numeric" pattern="[0-9 ]*" minLength={24} maxLength={29} placeholder="24 chiffres" required/></div><div className="field"><label>IBAN</label><input name="iban" defaultValue={bank.iban||""}/></div><div className="field"><label>SWIFT / BIC</label><input name="swift" defaultValue={bank.swift||""}/></div><div className="drawer-actions field full"><button type="button" className="danger-btn" onClick={()=>deleteSupplierBank(bank)}>Supprimer</button><button type="button" className="ghost-btn" onClick={()=>setSupplierBankEditor(null)}>Annuler</button><button className="primary-btn">Enregistrer</button></div></div></form>)}
        </section>
      </div>}
      {panel==="split"&&splitFlow&&<form className="split-payment-form" onSubmit={saveSplit}>
        <div className="split-source"><div><span className={`company-badge company-${splitFlow.companyId}`}>{companyName(splitFlow.companyId)}</span><b>{splitFlow.party}</b><small>Facture {splitFlow.reference} · échéance {new Date(splitFlow.dueDate).toLocaleDateString("fr-FR")}</small></div><strong className="outgoing-value">{money(Math.abs(splitFlow.amount))}</strong></div>
        <div className="split-lines-head"><span>Fraction</span><span>Date de paiement</span><span>Montant</span><span></span></div>
        <div className="split-lines">{splitLines.map((line,index)=><div key={index}><b>{index+1}/{splitLines.length}</b><input type="date" value={line.plannedDate} onChange={event=>setSplitLines(items=>items.map((item,i)=>i===index?{...item,plannedDate:event.target.value}:item))} required/><input type="number" min="0.01" step="0.01" value={line.amount} onChange={event=>setSplitLines(items=>items.map((item,i)=>i===index?{...item,amount:Number(event.target.value)}:item))} required/><button type="button" disabled={splitLines.length<=2} onClick={()=>setSplitLines(items=>items.filter((_,i)=>i!==index))} aria-label={`Supprimer la fraction ${index+1}`}>×</button></div>)}</div>
        <button type="button" className="add-split-line" onClick={()=>{const last=splitLines[splitLines.length-1];const next=new Date(`${last?.plannedDate||splitFlow.dueDate}T00:00:00`);next.setDate(next.getDate()+30);setSplitLines(items=>[...items,{amount:0,plannedDate:next.toISOString().slice(0,10)}])}}>＋ Ajouter une échéance</button>
        <div className="split-totals"><div><span>Total facture</span><b>{money(Math.abs(splitFlow.amount))}</b></div><div><span>Total réparti</span><b>{money(splitLines.reduce((sum,line)=>sum+Number(line.amount||0),0))}</b></div><div><span>Écart restant</span><b className={Math.abs(Math.abs(splitFlow.amount)-splitLines.reduce((sum,line)=>sum+Number(line.amount||0),0))>.01?"outgoing-value":"incoming-value"}>{money(Math.abs(splitFlow.amount)-splitLines.reduce((sum,line)=>sum+Number(line.amount||0),0))}</b></div></div>
        <div className="drawer-actions"><button type="button" className="ghost-btn" onClick={event=>event.currentTarget.closest("form")?.reset()}>Réinitialiser</button><button className="primary-btn">Enregistrer les échéances</button></div>
      </form>}
      {panel==="transfer"&&transferFlow&&<div className="transfer-workflow-page">
        <div className="transfer-context"><span className={`company-badge company-${transferFlow.companyId}`}>{companyName(transferFlow.companyId)}</span><div><b>{transferDraftSupplierBank?.beneficiaryName||transferFlow.beneficiaryName||transferFlow.party}</b><small>{transferDraftSupplierBank&&normalized(transferDraftSupplierBank.beneficiaryName)!==normalized(transferFlow.party)?`Bénéficiaire bancaire · facture fournisseur ${transferFlow.party} · `:""}{selectedTransferFlows.length} facture(s) sélectionnée(s) · <span className="outgoing-value">{money(selectedTransferTotal)}</span></small></div></div>
        <div className="transfer-lines-summary">{selectedTransferFlows.map(flow=><div key={flow.id}><span>{flow.reference}</span><small>Date planifiée {new Date(`${flow.plannedDate}T00:00:00`).toLocaleDateString("fr-FR")}</small><b className="outgoing-value">{money(Math.abs(flow.amount))}</b></div>)}</div>
        <form key={`transfer-${transferFlow.id}-${transferDraftOrder?.id||"new"}`} className="form-grid transfer-form" data-confirm="none" onSubmit={createTransferOrder}>
          <div className="field full"><label>Compte de notre société à débiter</label><select name="companyBankId" defaultValue={transferDraftOrder?.companyBankId||banks.find(bank=>bank.companyId===transferFlow.companyId&&bank.isPrimary)?.id||banks.find(bank=>bank.companyId===transferFlow.companyId)?.id||""} required><option value="">Choisir une banque</option>{banks.filter(bank=>bank.companyId===transferFlow.companyId).map(bank=><option value={bank.id} key={bank.id}>{bank.name} · {bank.accountLabel}{bank.isPrimary?" · Banque principale":""}{bank.rib?` · RIB ${bank.rib}`:" · RIB à paramétrer"}</option>)}</select><small className="field-help supplier-bank-found">La banque principale de {companyName(transferFlow.companyId)} est proposée automatiquement. {transferFlow.isForecast&&transferPaymentMethod!=="bank"?"Ce compte sera débité lors de la validation du règlement.":"Son RIB figurera sur la lettre."}</small></div>
          {transferFlow.isForecast&&<div className="field full payment-method-choice"><label>Mode de règlement</label><select name="paymentMethod" value={transferPaymentMethod} onChange={event=>{setTransferPaymentMethod(event.target.value as "mobile"|"bank"|"other");setTransferReceptionBeneficiaryName("")}}><option value="mobile">Application mobile bancaire</option><option value="bank">Virement bancaire vers le fournisseur</option><option value="other">Autre mode de règlement</option></select><small className="field-help">Le formulaire change automatiquement selon le mode choisi.</small></div>}
          {transferFlow.isForecast&&transferPaymentMethod==="mobile"?<div className="mobile-payment-notice field full"><span>▣</span><div><b>Paiement par application mobile bancaire</b><small>Aucun compte bénéficiaire ni lettre de virement ne sont nécessaires.</small><input name="paymentReference" placeholder="Référence opération mobile (facultatif)"/></div></div>:transferFlow.isForecast&&transferPaymentMethod==="other"?<div className="mobile-payment-notice other-payment field full"><span>◇</span><div><b>Autre mode de règlement</b><small>Sélectionnez le moyen puis saisissez son numéro ou la référence du justificatif.</small><div className="other-payment-fields"><select name="otherPaymentMethod" required><option value="">Choisir le moyen</option><option value="cheque">Chèque</option><option value="traite">Traite</option><option value="especes">Espèces</option><option value="carte">Carte professionnelle</option><option value="other">Autre</option></select><input name="paymentReference" placeholder="N° du chèque, de la traite ou référence du justificatif" required/></div></div></div>:<div className="field full"><label>Compte bancaire du bénéficiaire</label><select key={showAllSupplierBanks?"all-supplier-banks":"matching-supplier-banks"} name="supplierBankId" onChange={event=>setTransferReceptionBeneficiaryName(event.currentTarget.value?event.currentTarget.selectedOptions[0]?.textContent?.split(" · ")[1]||transferFlow.party:"")} defaultValue={preferredTransferSupplierBank?`supplier:${preferredTransferSupplierBank.id}`:""} required disabled={!transferSupplierBankOptions.length&&!ownBeneficiaryBanks.length}><option value="">Choisir le bénéficiaire</option>{transferSupplierBankOptions.length>0&&<optgroup label="BÉNÉFICIAIRES FOURNISSEURS">{transferSupplierBankOptions.map(bank=><option value={`supplier:${bank.id}`} key={`supplier-${bank.id}`}>{bank.supplierName} · {bank.beneficiaryName||"Bénéficiaire non renseigné"} · {bank.bankName} · RIB {bank.rib}{bank.companyId!==transferFlow.companyId?` · enregistré sous ${companyName(bank.companyId)}`:""}</option>)}</optgroup>}{ownBeneficiaryBanks.length>0&&<optgroup label="NOS COMPTES BANCAIRES">{ownBeneficiaryBanks.map(bank=><option value={`company:${bank.id}`} key={`company-bank-${bank.id}`}>{companyName(bank.companyId)} · {bank.accountHolder||companies.find(company=>company.id===bank.companyId)?.name||"Notre société"} · {bank.name} · {bank.accountLabel} · RIB {bank.rib}</option>)}</optgroup>}</select>{preferredTransferSupplierBank&&preferredTransferSupplierBank.companyId!==transferFlow.companyId?<small className="field-help supplier-bank-found">Compte de {transferFlow.party} retrouvé sous {companyName(preferredTransferSupplierBank.companyId)}. Nos propres comptes figurent aussi dans la section « NOS COMPTES BANCAIRES ».</small>:preferredTransferSupplierBank?<small className="field-help supplier-bank-found">Le compte enregistré de {preferredTransferSupplierBank.supplierName} est proposé automatiquement. Nos comptes sont disponibles dans une section séparée de la liste.</small>:showAllSupplierBanks?<div className="supplier-bank-manual-warning"><div><b>Tous les bénéficiaires sont affichés</b><span>La liste distingue les bénéficiaires fournisseurs de nos comptes bancaires. Une confirmation de sécurité sera demandée pour tout compte ne correspondant pas au fournisseur.</span></div></div>:<div className="supplier-bank-missing"><div><b>Aucun compte fournisseur correspondant à {transferFlow.party}</b><span>Vous pouvez afficher tous les bénéficiaires, sélectionner l’un de nos comptes ou en ajouter un nouveau.</span></div></div>}{!preferredTransferSupplierBank&&!transferReceptionBeneficiaryName&&<div className="supplier-bank-quick-actions"><button type="button" className="ghost-btn" disabled={!supplierBanks.length} onClick={()=>setShowAllSupplierBanks(value=>!value)}>{showAllSupplierBanks?"Afficher seulement le fournisseur concerné":`Afficher tous les bénéficiaires fournisseurs (${supplierBanks.length})`}</button><button type="button" className="primary-btn" onClick={openSupplierBankCreationForTransfer}>＋ Ajouter un nouveau bénéficiaire</button></div>}</div>}
          <div className="transfer-order-meta field full">
            <div className="field"><label>{transferFlow.isForecast&&transferPaymentMethod==="mobile"?"Date du règlement par application bancaire":transferFlow.isForecast&&transferPaymentMethod==="other"?"Date du règlement":`Date de réception prévue chez ${transferReceptionBeneficiaryName||transferDraftSupplierBank?.beneficiaryName||preferredTransferSupplierBank?.beneficiaryName||transferFlow.beneficiaryName||transferFlow.party}`}</label><input name="orderDate" type="date" defaultValue={transferDraftOrder?.orderDate||transferFlow.plannedDate||new Date().toISOString().slice(0,10)} required/><small className="field-help">Cette date positionne le décaissement dans le calcul prévisionnel de trésorerie.</small></div>
            <div className="field transfer-total-field"><label>{transferFlow.isForecast&&transferPaymentMethod==="mobile"?"Montant du règlement mobile":transferFlow.isForecast&&transferPaymentMethod==="other"?"Montant du règlement":selectedTransferFlows.length>1?"Total des lignes sélectionnées":"Montant du virement"}</label><div className="transfer-total-input"><input name="amount" type="number" step="0.01" min="0.01" defaultValue={Number((transferDraftOrder?.amount||selectedTransferTotal||Math.abs(transferFlow.amount)).toFixed(2))} readOnly={selectedTransferFlows.length>1} required/><span>MAD</span></div>{selectedTransferFlows.length>1&&<small className="field-help">Total calculé automatiquement à partir des factures cochées.</small>}</div>
          </div>
          <div className="field full"><label>Motif</label><input name="purpose" defaultValue={transferDraftOrder?.purpose||transferFlow.operationLabel||(transferFlow.isForecast?transferFlow.party:selectedTransferFlows.length>1?`Règlement consolidé de ${selectedTransferFlows.length} factures · date planifiée ${new Date(`${selectedTransferFlows[0].plannedDate}T00:00:00`).toLocaleDateString("fr-FR")}`:`Règlement facture ${transferFlow.reference}`)} required/></div>
          <div className="field full"><label>Commentaire <span className="optional-label">Facultatif</span></label><textarea name="comment" rows={3} defaultValue={transferDraftOrder?.comment||""} placeholder="Instruction complémentaire à communiquer au directeur de l’agence, si nécessaire…"/></div>
          <div className="transfer-number-note"><b>N° interne</b><span>Attribué automatiquement dès l’enregistrement, même sans impression.</span></div>
          {transferFlow.isForecast&&transferPaymentMethod!=="bank"?<div className="drawer-actions transfer-drawer-actions field full"><button type="button" className="ghost-btn" onClick={printTransferWorkflow}>▧ Imprimer</button><button type="submit" name="intent" value={transferPaymentMethod} className="primary-btn" disabled={busy}>{busy?"Traitement…":transferPaymentMethod==="mobile"?"✓ Exécuter le règlement mobile":"✓ Exécuter le règlement"}</button></div>:<div className="drawer-actions transfer-drawer-actions field full"><button type="button" className="ghost-btn" onClick={preferredTransferSupplierBank?()=>{setSupplierBankCreationFromTransfer(true);setPanel("settings")}:openSupplierBankCreationForTransfer}>{preferredTransferSupplierBank?"Paramétrer les banques":"Créer banque pour bénéficiaire"}</button><button type="submit" name="intent" value="save" className="save-order-draft-btn" disabled={busy||(!transferSupplierBankOptions.length&&!ownBeneficiaryBanks.length)}>{busy?"Enregistrement…":"Enregistrer sans imprimer"}</button><button type="submit" name="intent" value="print" className="primary-btn" disabled={busy||(!transferSupplierBankOptions.length&&!ownBeneficiaryBanks.length)}>{(!transferSupplierBankOptions.length&&!ownBeneficiaryBanks.length)?"Compte bénéficiaire requis":busy?"Préparation…":"Imprimer la lettre de virement"}</button></div>}
        </form>
      </div>}
      {panel==="editFlow"&&editingFlow&&<form className="form-grid edit-flow-form" onSubmit={saveFlowChanges}>
        <div className="edit-flow-summary field full"><span className={`company-badge company-${editingFlow.companyId}`}>{companyName(editingFlow.companyId)}</span><div><b>{editingFlow.kind==="client"?"Encaissement":"Règlement"}</b><small>{editingFlow.isForecast?"Prévision manuelle":"Ligne du plan de trésorerie"}</small></div><em>{approvalForFlow(editingFlow.id)?.status==="pending"?"En attente de validation":"Modifiable avant validation"}</em></div>
        <div className="field full"><label>Société propriétaire</label><select name="companyId" defaultValue={editingFlow.companyId} required>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select><small className="field-help">Le changement déplace cette ligne et ses calculs vers la société sélectionnée.</small></div>
        <div className="field full"><label>Client / Fournisseur</label><input name="party" defaultValue={editingFlow.party} required/></div><div className="field full"><label>Libellé de l’opération</label><input name="operationLabel" defaultValue={editingFlow.operationLabel||""} placeholder="Ex. Achat carburant, réparation moteur, transport…" required/><small className="field-help">Description claire de l’objet de l’encaissement ou du règlement.</small></div>{editingFlow.kind==="fournisseur"&&<div className="field full beneficiary-edit-field"><label>Bénéficiaire du règlement</label><input name="beneficiaryName" defaultValue={editingFlow.beneficiaryName||editingFlow.party} required/><small className="field-help">Nom du destinataire réel du paiement. Le responsable pourra encore le corriger au moment de la validation.</small></div>}
        <div className="field"><label>Référence</label><input name="reference" defaultValue={editingFlow.reference} required/></div>
        <div className="field"><label>Montant (MAD)</label><input name="amount" type="number" min="0.01" step="0.01" defaultValue={Math.abs(editingFlow.amount)} required/></div>
        <div className="field"><label>Date de facture</label><input name="invoiceDate" type="date" defaultValue={editingFlow.invoiceDate||""}/></div><div className="field"><label>Échéance source</label><input name="dueDate" type="date" defaultValue={editingFlow.dueDate} required/></div>
        <div className="field"><label>Date planifiée</label><input name="plannedDate" type="date" defaultValue={editingFlow.plannedDate} required/></div>
        <div className="field full"><label>Note</label><textarea name="note" rows={3} defaultValue={editingFlow.note||""} placeholder="Motif ou précision de la modification"/></div>
        <div className="field full"><label>Prise en compte</label><select name="excluded" defaultValue={editingFlow.excluded?"true":"false"}><option value="false">Oui · inclure dans les calculs</option><option value="true">Non · exclure des calculs</option></select></div>
        <div className="edit-flow-lock field full"><b>Contrôle hiérarchique</b><span>Après approbation, cette ligne sera verrouillée. Toute correction nécessitera d’abord un retour en arrière.</span></div>
        <div className="drawer-actions edit-flow-actions field full"><button type="reset" className="ghost-btn">Réinitialiser</button><button type="button" className="close-edit-flow-btn" disabled={busy} onClick={event=>confirmCloseFlowEditor(event.currentTarget.form)}>Fermer sans enregistrer</button><button type="submit" value="save" className="primary-btn" disabled={busy}>{busy?"Enregistrement…":"Enregistrer les modifications"}</button><button type="submit" value="save-close" data-save-and-close="true" data-confirm="none" hidden aria-hidden="true" tabIndex={-1}>Enregistrer et fermer</button></div>
      </form>}      {panel==="invoice"&&<form onSubmit={addFlow} className="form-grid"><div className="field full"><label>Société propriétaire</label><select name="companyId" defaultValue={selectedCompanyId} required>{companies.map(company=><option value={company.id} key={company.id}>{company.name}</option>)}</select></div><div className="field full"><label>Nature du flux</label><select name="kind" required><option value="fournisseur">Facture fournisseur · règlement</option><option value="client">Facture client · encaissement</option></select></div><div className="field full"><label>Client ou fournisseur</label><input name="party" placeholder="Nom du tiers" required/></div><div className="field"><label>Référence</label><input name="reference" placeholder="FA-2026-0001" required/></div><div className="field"><label>Montant TTC (MAD)</label><input name="amount" type="number" min="1" placeholder="25 000" required/></div><div className="field"><label>Date de facture</label><input name="invoiceDate" type="date" required/></div><div className="field"><label>Date d’échéance</label><input name="dueDate" type="date" required/></div><div className="drawer-actions field full"><button type="button" className="ghost-btn" onClick={event=>event.currentTarget.closest("form")?.reset()}>Réinitialiser</button><button type="submit" className="primary-btn">Ajouter au plan</button></div></form>}
      {panel===null&&view!=="overview"&&<button type="button" className="drawer-floating-exit" onClick={exitPanel} aria-label="Sortir vers la vue globale">← Sortir</button>}
    </aside></div>}
    {printOrder&&<TransferOrderDocument order={printOrder}/>}
    {internalTransferPrintOrder&&<InternalTransferOrderDocument order={internalTransferPrintOrder}/>}
    {accountFundingPrintOrder&&<AccountFundingDocument order={accountFundingPrintOrder}/>}
    {supplierPaymentAdvice&&printMode==="remittanceAdvice"&&<SupplierPaymentAdviceDocument advice={supplierPaymentAdvice}/>}
    {toast&&<div className="toast">✓ {toast}</div>}
  </div>;
}
