/**
 * Fichier  : assets/js/pages/orders.js
 * Auteur   : KREMER Régis
 * Version  : 2.6.101
 * Date     : 2026-07-11
 * Changelog : v2.6.101 (2026-07-20) — Libellés d’unités neutres « (s) » dans tous les écrans Commandes.
 * Description : JS — Bons de commande : liste, formulaire plein écran, création
 *               produits/fournisseurs à la volée, responsive mobile/tablette/PC
 *
 * Changelog :
 *   v2.2.x (2026-02-21/22) — Versions initiales
 *   v2.3.0 (2026-02-22)    — Refonte complète : formulaire plein-écran SPA,
 *                             création produit/fournisseur à la volée,
 *                             référence SKU fournisseur par ligne, responsive
 *   v2.3.1 (2026-06-02)    — Amélioration UX commandes : aperçu image produit par ligne,
 *                             validation détaillée avant envoi, réception visuelle,
 *                             accessibilité tactile et affichage print enrichi.
 *   v2.6.22 (2026-07-08) — Impression commandes : marges et table compacte via trame commune.
 *   v2.6.5 (2026-07-08)    — Harmonisation Lot 5 : data-label mobile, actions tactiles et cohérence visuelle admin.
 *   v2.6.8 (2026-07-08)    — Bon de commande imprimé avec le modèle A4 MageStock commun.
 *   v2.6.18 (2026-07-08)   — Images produits locales via proxy sécurisé.
 *   v2.6.70 (2026-07-11)   — I18N CORE LOCALE CURRENCY1 : montants et dates via la locale/devise centrale.
 */
const OrdersPage = {
    orders:    [],
    suppliers: [],
    products:  [],
    warehouses:[],
    categories:[],
    editId:    null,
    receiveId: null,
    _view:     'list',   // 'list' | 'form' | 'receive'

    // ═══════════════════════════════════════════════════════════
    // INIT
    // ═══════════════════════════════════════════════════════════
    async init() {
        try { await this.loadData(); } catch(e) {
            console.error('[OrdersPage] loadData:', e);
        }
        this.setupFilters();
        this.showList();
        document.getElementById('btnNewOrder')?.addEventListener('click', () => this.showForm());
    },

    async loadData() {
        [this.orders, this.suppliers, this.products, this.warehouses, this.categories] = await Promise.all([
            Api.orders(), Api.suppliers(), Api.products(), Api.warehouses(),
            Api.get('api/categories.php').catch(() => []),
        ]);
    },


    _ensureProdPickerStyles() {
        if (document.getElementById('sm-prod-picker-styles')) return;
        const css = `
            /* Product picker — responsive UX (non-régressif) */
            .ol-prod-row{display:grid;grid-template-columns:220px 1fr;gap:10px;margin-bottom:.4rem}
            .ol-prod-pick{display:grid;grid-template-columns:1fr 320px;gap:10px;align-items:center}
            .ol-prod-cat-wrap,.ol-prod-pick-wrap{min-width:0}
            .ol-prod-q{width:100%}
            /* Tablet */
            @media (max-width: 1024px){
              .ol-prod-row{grid-template-columns:1fr}
              .ol-prod-pick{grid-template-columns:1fr}
            }
            /* Small mobile */
            @media (max-width: 520px){
              .sm-prod-overlay{width: min(96vw, 980px) !important; left: 2vw !important;}
            }
        `;
        const st = document.createElement('style');
        st.id = 'sm-prod-picker-styles';
        st.textContent = css;
        document.head.appendChild(st);
    },

    // ═══════════════════════════════════════════════════════════
    // NAVIGATION ENTRE VUES (SPA dans le main-content)
    // ═══════════════════════════════════════════════════════════
    showList() {
        this._view = 'list';
        const root = document.getElementById('ordersRoot');
        if (!root) return;
        root.innerHTML = this._listHTML();
        this.setupFilters();
        this._bindListEvents();
    },

    showForm(orderId = null) {
        this._view = 'form';
        this.editId = orderId;
        const order = orderId ? this.orders.find(o => String(o.id) === String(orderId)) : null;
        const root  = document.getElementById('ordersRoot');
        if (!root) return;
        root.innerHTML = this._formHTML(order);
        this._ensureProdPickerStyles();
        this._bindFormEvents(order);
    },

    showReceive(orderId) {
        this._view = 'receive';
        this.receiveId = orderId;
        const order = this.orders.find(o => String(o.id) === String(orderId));
        if (!order) return;

        // Blocage dur : validation obligatoire
        if ((order.validationStatus||'pending') !== 'approved') {
            toast('Bon de commande non validé : un admin doit valider avant réception.', 'error');
            this.showList();
            return;
        }

        // Blocage dur : statut doit être envoyé ou partiel
        if (!['sent','partial'].includes(order.status)) {
            toast('Réception impossible : le BC doit être envoyé ou en réception partielle.', 'error');
            this.showList();
            return;
        }

        const root = document.getElementById('ordersRoot');
        root.innerHTML = this._receiveHTML(order);
        this._bindReceiveEvents(order);
    },

    // ═══════════════════════════════════════════════════════════
    // VUE LISTE
    // ═══════════════════════════════════════════════════════════
    setupFilters() {
        const statusEl   = document.getElementById('orderStatusFilter');
        const supplierEl = document.getElementById('orderSupplierFilter');
        if (supplierEl && this.suppliers.length) {
            const curVal = supplierEl.value;
            supplierEl.innerHTML = '<option value="">Tous les fournisseurs</option>' +
                this.suppliers.map(s => `<option value="${s.id}" ${s.id==curVal?'selected':''}>${s.name}</option>`).join('');
        }
        statusEl?.addEventListener('change',   () => this._renderList());
        supplierEl?.addEventListener('change', () => this._renderList());
        document.getElementById('btnOrderReset')?.addEventListener('click', () => {
            if (statusEl)   statusEl.value   = '';
            if (supplierEl) supplierEl.value = '';
            this._renderList();
        });
    },

    _listHTML() {
        const sc = {
            draft:     { label:'Brouillon', bg:'#64748b' },
            sent:      { label:'Envoyé',    bg:'var(--blue)'   },
            partial:   { label:'Partiel',   bg:'var(--orange)' },
            received:  { label:'Reçu',      bg:'var(--green)'  },
            cancelled: { label:'Annulé',    bg:'var(--red)'    },
        };
        const stats = { draft:0, sent:0, partial:0, received:0, cancelled:0 };
        const vstats = { pending:0, approved:0, refused:0 };

        const vc = {
            pending:  { label:'À valider', bg:'#64748b' },
            approved: { label:'Validé',    bg:'var(--green)' },
            refused:  { label:'Refusé',    bg:'var(--red)' },
        };

        this.orders.forEach(o => {
            if (sc[o.status]) stats[o.status]++;
            const vs = (o.validationStatus || 'pending');
            if (o.status === 'sent' && vstats[vs] !== undefined) vstats[vs]++;
        });

        // KPI : étapes clés (logistique) + validation en KPI dédiés
        // Envoyés = BC soumis à validation (status=sent)
        // Validation = 3 KPI séparés (à valider / validés / refusés)
        // "Partiels" n'est plus un KPI (reste un statut filtrable dans la liste)
        const statsBars = [
            { num: stats.draft,     label: 'Brouillons', color: sc.draft.bg },
            { num: stats.sent,      label: 'Envoyés',    color: sc.sent.bg },
            { num: vstats.pending,  label: 'À valider',  color: vc.pending.bg },
            { num: vstats.approved, label: 'Validés',    color: vc.approved.bg },
            { num: vstats.refused,  label: 'Refusés',    color: vc.refused.bg },
            { num: stats.received,  label: 'Reçus',      color: sc.received.bg },
            { num: stats.cancelled, label: 'Annulés',    color: sc.cancelled.bg },
        ].map(x => `
            <div class="order-stat-card">
                <span class="osc-num">${x.num}</span>
                <span class="osc-label" style="color:${x.color}">${x.label}</span>
            </div>`).join('');

        const rows = this.orders.length ? '' : '<tr><td colspan="9" class="tbl-empty">Aucun bon de commande — cliquez sur "+ Nouveau BC"</td></tr>';

        return `
        <div class="orders-list-view">
            <!-- Header -->
            <div class="olv-header">
                <div class="olv-title">
                    <h2 style="margin:0;font-size:1.3rem">📋 Bons de commande</h2>
                </div>
                <button class="btn btn-primary" id="btnNewOrderInner">
                    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:6px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
                    Nouveau BC
                </button>
            </div>

            <!-- Stats -->
            <div class="orders-stats">${statsBars}</div>

            <!-- Filtres -->
            <div class="orders-filters">
                <select id="orderStatusFilter" class="form-control" style="width:auto;min-width:160px">
                    <option value="">Tous les statuts</option>
                    <option value="draft">📝 Brouillon</option>
                    <option value="sent">📤 Envoyé</option>
                    <option value="partial">📦 Partiel</option>
                    <option value="received">✅ Reçu</option>
                    <option value="cancelled">❌ Annulé</option>
                </select>
                <select id="orderSupplierFilter" class="form-control" style="width:auto;min-width:180px">
                    <option value="">Tous les fournisseurs</option>
                    ${this.suppliers.map(s=>`<option value="${s.id}">${s.name}</option>`).join('')}
                </select>
                <button class="btn btn-secondary btn-sm" id="btnOrderReset">Réinitialiser</button>
            </div>

            <!-- Table -->
            <div class="card" style="overflow:hidden">
                <div style="overflow-x:auto">
                <table class="tbl" id="ordersTable">
                    <thead><tr>
                        <th>N° BC</th><th>Réf.</th><th>Fournisseur</th>
                        <th>Lignes</th><th>Valeur</th><th>Statut</th><th>Validation</th><th>Date</th><th></th>
                    </tr></thead>
                    <tbody id="ordersBody">${rows}</tbody>
                </table>
                </div>
            </div>
        </div>`;
    },

    _renderList() {
        const isAdmin = (window.SM_ROLE === 'admin');
        const statusF   = document.getElementById('orderStatusFilter')?.value   || '';
        const supplierF = document.getElementById('orderSupplierFilter')?.value || '';
        const tbody = document.getElementById('ordersBody');
        if (!tbody) return;

        let data = this.orders.filter(o => {
            if (statusF   && o.status !== statusF) return false;
            if (supplierF && String(o.supplierId) !== String(supplierF)) return false;
            return true;
        });

        const sc = {
            draft:     { label:'Brouillon', bg:'#64748b' },
            sent:      { label:'Envoyé',    bg:'var(--blue)'   },
            partial:   { label:'Partiel',   bg:'var(--orange)' },
            received:  { label:'Reçu',      bg:'var(--green)'  },
            cancelled: { label:'Annulé',    bg:'var(--red)'    },
        };

        const vc = {
            pending:  { label:'À valider', bg:'#64748b' },
            approved: { label:'Validé',    bg:'var(--green)' },
            refused:  { label:'Refusé',    bg:'var(--red)' },
        };

        if (!data.length) {
            tbody.innerHTML = '<tr><td colspan="9" class="tbl-empty">Aucun résultat</td></tr>';
            return;
        }

        tbody.innerHTML = data.map(o => {
            const sup  = this.suppliers.find(s => String(s.id) === String(o.supplierId));
            const lc   = (o.lines||[]).length;
            const val  = (o.lines||[]).reduce((s,l) => s+(parseFloat(l.unitCost||0)*parseFloat(l.quantity||0)), 0);
            const s    = sc[o.status] || {label:o.status, bg:'#64748b'};
            const date = o.createdAt ? Locale.formatDate(o.createdAt) : '—';
            const vs   = o.validationStatus || 'pending';
            const vv   = vc[vs] || vc.pending;
            const isClosed = ['received','cancelled'].includes(o.status);

            // ── Conditions d'actions cohérentes ──
            const canEdit     = o.status === 'draft';
            const canSend     = o.status === 'draft';
            const canApprove  = isAdmin && o.status === 'sent' && vs === 'pending';
            const canRefuse   = isAdmin && o.status === 'sent' && vs === 'pending';
            const canReceive  = ['sent','partial'].includes(o.status) && vs === 'approved';
            const canCancel   = isAdmin && ['draft','sent'].includes(o.status) && !isClosed;
            const canResubmit = o.status === 'sent' && vs === 'refused';
            const canStore    = isAdmin && o.status === 'received' && !o.storedAt;
            const canDelete   = ['draft','cancelled'].includes(o.status);

            // ── Colonne validation enrichie ──
            let validationHtml = `<span style="background:${vv.bg};color:#fff;padding:2px 10px;border-radius:99px;font-size:.78rem;font-weight:600;white-space:nowrap">${vv.label}</span>`;
            if (vs === 'approved') {
                const byEvt = (o.events||[]).find(e => e.type === 'approved');
                const byName = byEvt?.by || '';
                const byDate = byEvt?.t ? Locale.formatDate(byEvt.t) : '';
                if (byName || byDate) validationHtml += `<div style="font-size:.72rem;color:var(--text-muted);margin-top:2px">${byName}${byDate ? ' — '+byDate : ''}</div>`;
            } else if (vs === 'refused') {
                const byEvt = (o.events||[]).find(e => e.type === 'refused');
                const byName = byEvt?.by || '';
                const reason = o.validationNote || byEvt?.note || '';
                if (byName) validationHtml += `<div style="font-size:.72rem;color:var(--text-muted);margin-top:2px">${byName}</div>`;
                if (reason) validationHtml += `<div style="font-size:.72rem;color:var(--red);margin-top:1px" title="${this._esc(reason)}">« ${this._esc(reason.length > 30 ? reason.substring(0,30)+'…' : reason)} »</div>`;
            }

            return `<tr>
                <td data-label="N° BC"><strong style="font-family:monospace;font-size:.85rem">${o.orderNumber||'—'}</strong></td>
                <td data-label="Réf." style="color:var(--text-muted);font-size:.85rem">${o.ref||'—'}</td>
                <td data-label="Fournisseur">${sup?.name||'<span style="color:var(--text-muted)">?</span>'}</td>
                <td data-label="Lignes">${lc} ligne${lc>1?'s':''}</td>
                <td data-label="Valeur">${val>0?'<strong>'+Locale.formatMoney(val)+'</strong>':'—'}</td>
                <td data-label="Statut"><span style="background:${s.bg};color:#fff;padding:2px 10px;border-radius:99px;font-size:.78rem;font-weight:600;white-space:nowrap">${s.label}</span></td>
                <td data-label="Validation">${validationHtml}</td>
                <td data-label="Date" style="font-size:.85rem;color:var(--text-muted)">${date}</td>
                <td class="order-actions" data-label="Actions" style="white-space:nowrap;display:flex;gap:4px;align-items:center;flex-wrap:nowrap">
                    <button class="btn btn-sm btn-secondary oact" onclick="OrdersPage.viewDetail('${o.id}')" title="Aperçu"><svg viewBox="0 0 24 24"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
                    ${canEdit      ? `<button class="btn btn-sm btn-primary oact"   onclick="OrdersPage.showForm('${o.id}')" title="Modifier"><svg viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></button>` : ''}
                    ${canSend      ? `<button class="btn btn-sm btn-info oact"      onclick="OrdersPage.markSent('${o.id}')" title="Envoyer"><svg viewBox="0 0 24 24"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg></button>` : ''}
                    ${canApprove   ? `<button class="btn btn-sm btn-success oact"   onclick="OrdersPage.approve('${o.id}')" title="Valider"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg></button>` : ''}
                    ${canRefuse    ? `<button class="btn btn-sm btn-warning oact"   onclick="OrdersPage.refuse('${o.id}')" title="Refuser"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg></button>` : ''}
                    ${canResubmit  ? `<button class="btn btn-sm btn-info oact"      onclick="OrdersPage.resubmit('${o.id}')" title="Re-soumettre"><svg viewBox="0 0 24 24"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 102.13-9.36L1 10"/></svg></button>` : ''}
                    ${canReceive   ? `<button class="btn btn-sm btn-success oact"   onclick="OrdersPage.showReceive('${o.id}')" title="Réceptionner"><svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg></button>` : ''}
                    ${canStore     ? `<button class="btn btn-sm btn-secondary oact" onclick="OrdersPage.store('${o.id}')" title="Rangé"><svg viewBox="0 0 24 24"><path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg></button>` : ''}
                    ${canCancel    ? `<button class="btn btn-sm btn-danger oact"    onclick="OrdersPage.cancelOrder('${o.id}')" title="Annuler"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg></button>` : ''}
                    ${canDelete    ? `<button class="btn btn-sm btn-ghost oact"     onclick="OrdersPage.deleteOrder('${o.id}')" title="Supprimer"><svg viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/></svg></button>` : ''}
                </td>
            </tr>`;
        }).join('');
    },

    _bindListEvents() {
        this._renderList();
        document.getElementById('btnNewOrderInner')?.addEventListener('click', () => this.showForm());
        document.getElementById('btnNewOrder')?.addEventListener('click', () => this.showForm());
    },

    // ═══════════════════════════════════════════════════════════
    // VUE FORMULAIRE — plein écran, responsive
    // ═══════════════════════════════════════════════════════════
    _formHTML(order = null) {
        const isEdit  = !!order;
        const suppOpts = this.suppliers.map(s =>
            `<option value="${s.id}" ${order?.supplierId==s.id?'selected':''}>${s.name}</option>`
        ).join('');
        const whOpts = this.warehouses.map(w =>
            `<option value="${w.id}" ${order?.warehouseId==w.id?'selected':''}>${w.name}</option>`
        ).join('');

        return `
        <div class="order-form-view">
            <!-- ── Barre de nav sticky ── -->
            <div class="ofv-nav">
                <button class="btn btn-ghost btn-sm" id="btnBackToList">
                    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
                    <span class="ofv-nav-label">Retour</span>
                </button>
                <h3 class="ofv-nav-title">
                    ${isEdit ? '✏️ Modifier — '+(order.orderNumber||'') : '📋 Nouveau bon de commande'}
                </h3>
                <div style="display:flex;gap:.5rem;align-items:center">
                    <div id="of_total_nav" class="ofv-total-pill"></div>
                    <button class="btn btn-secondary btn-sm" id="btnSaveDraft" aria-label="Enregistrer le bon de commande en brouillon">💾 Brouillon</button>
                    <button class="btn btn-success btn-sm" id="btnSendOrder" aria-label="Contrôler et envoyer le bon de commande">📤 Envoyer</button>
                </div>
            </div>

            <!-- ── Corps du formulaire ── -->
            <div class="ofv-body">

                <!-- ══ COLONNE GAUCHE : infos BC ══ -->
                <div class="ofv-left">

                    <div class="card ofv-card ofv-progress-card" aria-label="Progression du bon de commande">
                        <div class="ofv-card-title">✅ Contrôle de création</div>
                        <div class="order-step-list">
                            <span class="order-step" id="stepSupplier">1. Fournisseur</span>
                            <span class="order-step" id="stepWarehouse">2. Magasin</span>
                            <span class="order-step" id="stepLines">3. Produits</span>
                            <span class="order-step" id="stepReview">4. Vérification</span>
                        </div>
                        <div id="orderValidationPanel" class="order-validation-panel" role="status" aria-live="polite">
                            Complétez le fournisseur, le magasin et au moins une ligne produit.
                        </div>
                    </div>

                    <!-- Bloc fournisseur -->
                    <div class="card ofv-card">
                        <div class="ofv-card-title">🏢 Fournisseur</div>

                        <label class="lbl">Fournisseur *</label>
                        <div class="ofv-inline-row">
                            <select id="of_supplier" class="sel" style="flex:1">
                                <option value="">— Choisir —</option>${suppOpts}
                            </select>
                            <button class="btn btn-secondary btn-sm" id="btnNewSupplier">+ Nouveau</button>
                        </div>

                        <!-- Panel création fournisseur inline -->
                        <div id="newSupplierPanel" class="ofv-inline-panel" style="display:none">
                            <div class="ofv-inline-panel-title">➕ Nouveau fournisseur</div>
                            <div class="ofv-grid-2">
                                <div><label class="lbl">Nom *</label><input type="text" id="ns_name" class="inp" placeholder="Nom fournisseur"></div>
                                <div><label class="lbl">Contact</label><input type="text" id="ns_contact" class="inp" placeholder="Nom contact"></div>
                                <div><label class="lbl">Email</label><input type="email" id="ns_email" class="inp" placeholder="email@example.com"></div>
                                <div><label class="lbl">Téléphone</label><input type="tel" id="ns_phone" class="inp" placeholder="06…"></div>
                            </div>
                            <div class="ofv-inline-panel-footer">
                                <button class="btn btn-secondary btn-sm" id="btnCancelNewSupplier">Annuler</button>
                                <button class="btn btn-primary btn-sm" id="btnSaveNewSupplier">✅ Créer</button>
                            </div>
                        </div>
                    </div>

                    <!-- Bloc magasin GLOBAL — une fois pour tout le BC -->
                    <div class="card ofv-card">
                        <div class="ofv-card-title">🏪 Magasin de destination</div>
                        <p class="ofv-hint">Choisi une seule fois — s'applique à toutes les lignes. Modifiable ligne par ligne si besoin.</p>
                        <select id="of_warehouse" class="sel" onchange="OrdersPage._applyGlobalWarehouse()">
                            <option value="">— Choisir le magasin —</option>${whOpts}
                        </select>
                    </div>

                    <!-- Bloc référence / note -->
                    <div class="card ofv-card">
                        <div class="ofv-card-title">📝 Détails</div>
                        <label class="lbl">Référence interne</label>
                        <input type="text" id="of_ref" class="inp" placeholder="CMD-2026-001" value="${order?.ref||''}">
                        <label class="lbl" style="margin-top:.65rem">Note / Instructions</label>
                        <textarea id="of_note" class="inp" rows="3" placeholder="Délais, conditions, urgence…" style="resize:vertical">${order?.note||''}</textarea>
                    </div>
                </div>

                <!-- ══ COLONNE DROITE : lignes de commande ══ -->
                <div class="ofv-right">
                    <div class="card ofv-card">
                        <div class="ofv-card-title" style="margin-bottom:.75rem">📦 Lignes de commande</div>

                        <!-- Zone lignes — les boutons d'ajout sont EN BAS de chaque ligne -->
                        <div id="of_lines">
                            <!-- Les lignes sont injectées ici par JS -->
                        </div>

                        <!-- Boutons d'ajout APRÈS les lignes — toujours visibles en bas -->
                        <div class="ofv-add-btns" id="of_add_btns">
                            <button class="btn btn-secondary ofv-add-btn" id="btnAddExistingLine">
                                <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:5px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
                                Produit catalogue
                            </button>
                            <button class="btn btn-primary ofv-add-btn" id="btnAddNewProductLine">
                                <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:5px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
                                Nouveau produit
                            </button>
                        </div>

                        <!-- Pied : total -->
                        <div id="of_total" class="ofv-total-bar" style="display:none"></div>
                    </div>
                </div>
            </div>
        </div>

        <style>
        /* ── Layout ── */
        .order-form-view { display:flex;flex-direction:column;min-height:100% }
        .ofv-nav {
            display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex-wrap:wrap;
            padding:.6rem 1rem;background:var(--bg-card);border-bottom:1px solid var(--border);
            position:sticky;top:0;z-index:20
        }
        .ofv-nav-title { margin:0;font-size:.95rem;font-weight:700;flex:1;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis }
        .ofv-nav-label {}
        .ofv-total-pill {
            background:var(--blue-pale);color:var(--blue);font-size:.82rem;font-weight:700;
            padding:3px 10px;border-radius:99px;white-space:nowrap
        }
        .ofv-body {
            flex:1;padding:1rem;
            display:grid;grid-template-columns:300px 1fr;gap:1rem;align-items:start
        }
        /* ── Cards ── */
        .ofv-card { padding:1rem;margin-bottom:.75rem }
        .ofv-card:last-child { margin-bottom:0 }
        .ofv-card-title {
            font-size:.78rem;font-weight:700;text-transform:uppercase;
            letter-spacing:.06em;color:var(--text-muted);margin-bottom:.65rem
        }
        .ofv-hint { font-size:.78rem;color:var(--text-muted);margin:0 0 .5rem;line-height:1.4 }
        .ofv-grid-2 { display:grid;grid-template-columns:1fr 1fr;gap:.4rem }
        .ofv-inline-row { display:flex;gap:.4rem;align-items:center }
        .ofv-inline-panel {
            background:var(--bg-elevated);border:1px solid var(--border);
            border-radius:var(--r-lg);padding:.75rem;margin-top:.5rem
        }
        .ofv-inline-panel-title { font-size:.82rem;font-weight:700;margin-bottom:.5rem }
        .ofv-inline-panel-footer { display:flex;justify-content:flex-end;gap:.4rem;margin-top:.5rem }
        /* ── Boutons ajout ── */
        .ofv-add-btns {
            display:flex;gap:.5rem;padding:.75rem;margin:.25rem 0;
            background:var(--bg-elevated);border-radius:var(--r-lg);
            border:2px dashed var(--border);justify-content:center;flex-wrap:wrap
        }
        .ofv-add-btn { font-size:.83rem!important;display:flex;align-items:center }
        /* ── Ligne de commande ── */
        .ol-card {
            border:1px solid var(--border);border-radius:var(--r-lg);
            background:var(--bg-elevated);margin-bottom:.5rem;
            transition:border-color .15s,box-shadow .15s;overflow:hidden
        }
        .ol-card:hover { border-color:var(--blue);box-shadow:0 0 0 3px var(--blue-pale) }
        .ol-card.ol-new { border-color:var(--orange) }
        .ol-card.ol-new:hover { box-shadow:0 0 0 3px rgba(251,146,60,.15) }
        .ol-head {
            display:flex;align-items:center;justify-content:space-between;
            padding:.5rem .75rem;border-bottom:1px solid var(--border);
            background:var(--bg-card)
        }
        .ol-badge {
            display:inline-flex;align-items:center;gap:4px;
            padding:1px 8px;border-radius:99px;font-size:.72rem;font-weight:700
        }
        .ol-body { padding:.65rem .75rem }
        .ol-row { display:grid;gap:.4rem;margin-bottom:.4rem }
        .ol-row-3 { grid-template-columns:2fr 1fr 1fr }
        .ol-row-4 { grid-template-columns:1fr 1fr 1fr 1fr }
        .ol-row-2h { grid-template-columns:1fr 1fr }
        /* ── Total ── */
        .ofv-total-bar {
            margin-top:.75rem;padding:.6rem 1rem;background:var(--blue-pale);
            border-radius:var(--r-lg);text-align:right;
            font-weight:700;font-size:1rem;color:var(--blue)
        }
        /* ── Responsive ── */
        @media(max-width:860px){
            .ofv-body { grid-template-columns:1fr }
            .ofv-nav-title { display:none }
        }
        @media(max-width:500px){
            .ol-row-3 { grid-template-columns:1fr 1fr }
            .ol-row-4 { grid-template-columns:1fr 1fr }
            .ofv-nav-label { display:none }
        }
        </style>`;
    },



    // ─── Helpers UX produits / images / accessibilité ─────────────────────
    _escapeHtml(value) {
        return String(value ?? '')
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;');
    },

    _productById(productId) {
        return this.products.find(p => String(p.id) === String(productId)) || null;
    },

    _productBarcode(product) {
        if (!product) return '';
        if (product.barcode) return String(product.barcode);
        if (product.ean) return String(product.ean);
        if (Array.isArray(product.eans) && product.eans.length) {
            const first = product.eans.find(e => typeof e === 'string') || product.eans[0];
            return String(first?.value || first?.ean || first || '');
        }
        return '';
    },

    _productImageSrc(product) {
        const raw = String(product?.image || product?.photo || product?.img || product?.image_url || product?.photo_url || '').trim();
        if (!raw) return '';
        if (/^(https?:|data:|blob:|\/)/i.test(raw)) return raw;
        if (raw.startsWith('uploads/') || raw.startsWith('assets/')) return raw;
        return 'uploads/' + raw.replace(/^\/+/, '');
    },

    _productThumbHTML(product, size = 64) {
        const img = this._productImageSrc(product);
        const name = this._escapeHtml(product?.name || 'Produit');
        if (img) {
            return `<img class="order-product-thumb" src="${this._escapeHtml(img)}" alt="Image produit : ${name}" loading="lazy" width="${size}" height="${size}" onerror="this.outerHTML='<div class=&quot;order-product-thumb order-product-thumb--empty&quot; aria-hidden=&quot;true&quot;>📦</div>'">`;
        }
        return '<div class="order-product-thumb order-product-thumb--empty" aria-hidden="true">📦</div>';
    },

    _productMetaHTML(product) {
        if (!product) return '';
        const bits = [];
        if (product.sku) bits.push(`SKU ${this._escapeHtml(product.sku)}`);
        const barcode = this._productBarcode(product);
        if (barcode) bits.push(`EAN ${this._escapeHtml(barcode)}`);
        if (product.category) bits.push(this._escapeHtml(product.category));
        return bits.length ? `<div class="order-product-meta">${bits.join(' · ')}</div>` : '';
    },

    _refreshLineProductPreview(card) {
        if (!card || card.dataset.isNew === '1') return;
        const box = card.querySelector('.ol-product-preview');
        if (!box) return;
        const productId = card.querySelector('.ol-prod')?.value || '';
        const product = this._productById(productId);
        if (!product) {
            box.innerHTML = '<div class="ol-product-preview-empty">Sélectionnez un produit pour afficher son image, sa référence, sa catégorie et son prix.</div>';
            return;
        }
        const cost = parseFloat((product.cump ?? product.cost ?? 0).toString().replace(',', '.')) || 0;
        const stockInfo = this._stockTextForProduct(product);
        box.innerHTML = `
            <div class="order-product-summary">
                ${this._productThumbHTML(product, 64)}
                <div class="order-product-summary-body">
                    <div class="order-product-title">${this._escapeHtml(product.name || 'Produit sans nom')}</div>
                    ${this._productMetaHTML(product)}
                    <div class="order-product-extra">
                        ${cost > 0 ? `<span>Prix indicatif : <strong>${Locale.formatMoney(cost)}</strong></span>` : '<span>Prix indicatif non renseigné</span>'}
                        ${stockInfo ? `<span>${this._escapeHtml(stockInfo)}</span>` : ''}
                    </div>
                </div>
            </div>`;
    },

    _stockTextForProduct(product) {
        if (!product) return '';
        const pid = String(product.id);
        const stockRows = Array.isArray(this.stock) ? this.stock : [];
        const rows = stockRows.filter(s => String(s.productId) === pid);
        if (!rows.length) return '';
        const total = rows.reduce((sum, s) => sum + (parseFloat(s.quantity || 0) || 0), 0);
        return `Stock connu : ${total}`;
    },

    _setOrderValidationPanel(errors = [], warnings = []) {
        const panel = document.getElementById('orderValidationPanel');
        const supplierOk = !!document.getElementById('of_supplier')?.value;
        const warehouseOk = !!document.getElementById('of_warehouse')?.value || Array.from(document.querySelectorAll('.ol-card .ol-wh')).some(sel => !!sel.value);
        const lineCount = document.querySelectorAll('.ol-card').length;
        const valid = !errors.length;

        const toggleStep = (id, ok) => {
            const el = document.getElementById(id);
            if (!el) return;
            el.classList.toggle('is-ok', !!ok);
            el.classList.toggle('is-warn', !ok);
        };
        toggleStep('stepSupplier', supplierOk);
        toggleStep('stepWarehouse', warehouseOk);
        toggleStep('stepLines', lineCount > 0);
        toggleStep('stepReview', valid && supplierOk && warehouseOk && lineCount > 0);

        if (!panel) return;
        if (errors.length) {
            panel.className = 'order-validation-panel is-error';
            panel.innerHTML = '<strong>À corriger avant envoi :</strong><ul>' + errors.map(e => `<li>${this._escapeHtml(e)}</li>`).join('') + '</ul>';
        } else if (warnings.length) {
            panel.className = 'order-validation-panel is-warning';
            panel.innerHTML = '<strong>Points à vérifier :</strong><ul>' + warnings.map(w => `<li>${this._escapeHtml(w)}</li>`).join('') + '</ul>';
        } else {
            panel.className = 'order-validation-panel is-ok';
            panel.innerHTML = 'Commande prête : fournisseur, magasin et lignes produits sont cohérents.';
        }
    },

    _validateOrderFormDetailed(showToast = false, mode = 'draft') {
        const errors = [];
        const warnings = [];
        const supplierId = document.getElementById('of_supplier')?.value || '';
        const globalWh = document.getElementById('of_warehouse')?.value || '';
        const cards = Array.from(document.querySelectorAll('.ol-card'));

        if (!supplierId) errors.push('Fournisseur obligatoire.');
        if (!cards.length) errors.push('Ajoutez au moins une ligne de commande.');

        cards.forEach((card, index) => {
            const n = index + 1;
            const isNew = card.dataset.isNew === '1';
            const qty = parseFloat(card.querySelector('.ol-qty')?.value || 0);
            const costRaw = card.querySelector('.ol-cost')?.value;
            const cost = parseFloat(costRaw || 0);
            const whId = card.querySelector('.ol-wh')?.value || globalWh;
            const supplierSku = card.querySelector('.ol-skuf')?.value.trim() || '';

            if (!whId) errors.push(`Ligne ${n} : magasin manquant.`);
            if (!(qty > 0)) errors.push(`Ligne ${n} : quantité invalide.`);
            if (isNew) {
                const name = card.querySelector('.ol-np-name')?.value.trim() || '';
                if (!name) errors.push(`Ligne ${n} : nom du nouveau produit manquant.`);
            } else {
                const prodId = card.querySelector('.ol-prod')?.value || '';
                if (!prodId) errors.push(`Ligne ${n} : produit catalogue non sélectionné.`);
                else {
                    const product = this._productById(prodId);
                    if (product && !this._productImageSrc(product)) warnings.push(`Ligne ${n} : produit sans image.`);
                }
            }
            if (!supplierSku) warnings.push(`Ligne ${n} : référence fournisseur non renseignée.`);
            if (!(cost >= 0) || costRaw === '') warnings.push(`Ligne ${n} : prix unitaire non renseigné.`);
        });

        if (mode === 'sent' && warnings.length) {
            // Les avertissements restent non bloquants, mais visibles avant l'envoi.
        }
        this._setOrderValidationPanel(errors, warnings);
        if (showToast && errors.length) toast('Le bon de commande contient des erreurs à corriger.', 'error');
        return { ok: errors.length === 0, errors, warnings };
    },

    _getProductSupplierLinks(product){
        const raw = Array.isArray(product?.supplierLinks) ? product.supplierLinks : [];
        const rows = raw.map(row => ({
            supplierId: String(row?.supplierId || ''),
            supplierSku: String(row?.supplierSku || ''),
            purchasePrice: Number(row?.purchasePrice || 0),
            packagingLabel: String(row?.packagingLabel || ''),
            packagingQty: Number(row?.packagingQty || 0),
            minOrderQty: Number(row?.minOrderQty || 0),
            leadTimeDays: Number(row?.leadTimeDays || 0),
            discountRate: Number(row?.discountRate || 0),
            preferred: !!row?.preferred,
            allowedWarehouses: Array.isArray(row?.allowedWarehouses) ? row.allowedWarehouses.map(String) : []
        })).filter(row => row.supplierId || row.supplierSku || row.purchasePrice > 0 || row.packagingLabel);
        if(!rows.length && product?.supplier){
            rows.push({
                supplierId: String(product.supplier || ''),
                supplierSku: String(product?.sku || ''),
                purchasePrice: Number(product?.cump ?? product?.cost ?? 0),
                packagingLabel: String(product?.unitType || product?.packBaseUnit || ''),
                packagingQty: 1,
                minOrderQty: 0,
                leadTimeDays: 0,
                discountRate: 0,
                preferred: true,
                allowedWarehouses: Array.isArray(product?.allowedWarehouses) ? product.allowedWarehouses.map(String) : []
            });
        }
        return rows;
    },

    _pickSupplierOffer(product, supplierId='', warehouseId=''){
        const links = this._getProductSupplierLinks(product);
        if(!links.length) return null;
        let out = links.slice();
        if(warehouseId){
            out = out.filter(link => !link.allowedWarehouses.length || link.allowedWarehouses.includes(String(warehouseId)));
            if(!out.length) out = links.slice();
        }
        if(supplierId){
            const bySupplier = out.filter(link => String(link.supplierId) === String(supplierId));
            if(bySupplier.length) out = bySupplier;
        }
        out.sort((a,b) => Number(!!b.preferred) - Number(!!a.preferred));
        return out[0] || null;
    },

    _unitLabelForQty(unit, qty){
        const labels = {
            piece:'Pièce(s)', ml:'Mètre(s) linéaire(s) (ml)', litre:'Litre(s)', kg:'kg', m:'m', m2:'m²', m3:'m³',
            carton:'Carton(s)', palette:'Palette(s)', bidon:'Bidon(s)', 'fût':'Fût(s)', fut:'Fût(s)',
            'aérosol':'Aérosol(s)', aerosol:'Aérosol(s)', sac:'Sac(s)', seau:'Seau(x)', rouleau:'Rouleau(x)',
            bobine:'Bobine(s)', tube:'Tube(s)', 'boîte':'Boîte(s)', boite:'Boîte(s)', paquet:'Paquet(s)'
        };
        return labels[String(unit || '').toLowerCase()] || String(unit || '');
    },

    _refreshLineSupplierOffer(card){
        if(!card || card.dataset.isNew === '1') return;
        const pid = card.querySelector('.ol-prod')?.value || '';
        const product = this.products.find(p => String(p.id) === String(pid));
        if(!product) return;
        const supplierId = document.getElementById('of_supplier')?.value || '';
        const warehouseId = card.querySelector('.ol-wh')?.value || document.getElementById('of_warehouse')?.value || '';
        const offer = this._pickSupplierOffer(product, supplierId, warehouseId);
        const refEl  = card.querySelector('.ol-skuf');
        const costEl = card.querySelector('.ol-cost');
        const unitEl = card.querySelector('.ol-unit');
        const qtyEl  = card.querySelector('.ol-qty');
        const infoEl = card.querySelector('.ol-supplier-offer-info');

        if(offer){
            if(refEl) refEl.value = offer.supplierSku || product.sku || '';
            if(costEl && offer.purchasePrice > 0) costEl.value = offer.purchasePrice.toFixed(2);
            if(unitEl && offer.packagingLabel) unitEl.value = offer.packagingLabel;
            const curQty = parseFloat(qtyEl?.value || 0) || 0;
            if(qtyEl && curQty <= 0 && offer.minOrderQty > 0) qtyEl.value = String(offer.minOrderQty);
            if(infoEl){
                const bits = [];
                if(offer.packagingLabel) {
                    const label = this._unitLabelForQty(offer.packagingLabel, 2);
                    bits.push(`Cond. : ${label}${offer.packagingQty > 0 ? ' · 1 = ' + offer.packagingQty : ''}`);
                }
                if(offer.minOrderQty > 0) bits.push(`Mini : ${offer.minOrderQty}`);
                if(offer.leadTimeDays > 0) bits.push(`Délai : ${offer.leadTimeDays} j`);
                if(offer.discountRate > 0) bits.push(`Remise : ${offer.discountRate}%`);
                infoEl.innerHTML = bits.join(' · ');
            }
        } else {
            if(refEl) refEl.value = product.sku || '';
            const price = parseFloat((product.cump ?? product.cost ?? 0).toString().replace(',', '.')) || 0;
            if(costEl && price > 0) costEl.value = price.toFixed(2);
            if(unitEl && product.unitType) unitEl.value = product.unitType;
            if(infoEl) infoEl.innerHTML = '';
        }
        this._refreshLineProductPreview(card);
        this._calcTotal();
        this._validateOrderFormDetailed(false);
    },

    _refreshAllProductLineOffers(){
        document.querySelectorAll('.ol-card[data-is-new="0"]').forEach(card => this._refreshLineSupplierOffer(card));
    },

    _bindFormEvents(order) {
        document.getElementById('btnBackToList')?.addEventListener('click', () => this.showList());

        // Fournisseur toggle
        document.getElementById('btnNewSupplier')?.addEventListener('click', () => {
            document.getElementById('newSupplierPanel').style.display = 'block';
            document.getElementById('btnNewSupplier').style.display  = 'none';
            document.getElementById('ns_name').focus();
        });
        document.getElementById('btnCancelNewSupplier')?.addEventListener('click', () => {
            document.getElementById('newSupplierPanel').style.display = 'none';
            document.getElementById('btnNewSupplier').style.display  = 'inline-flex';
        });
        document.getElementById('btnSaveNewSupplier')?.addEventListener('click', () => this._createSupplierInline());
        document.getElementById('of_supplier')?.addEventListener('change', () => { this._refreshAllProductLineOffers(); this._validateOrderFormDetailed(false); });

        // Boutons d'ajout en bas
        document.getElementById('btnAddExistingLine')?.addEventListener('click', () => this._addLine(false));
        document.getElementById('btnAddNewProductLine')?.addEventListener('click', () => this._addLine(true));

        // Sauvegardes
        document.getElementById('btnSaveDraft')?.addEventListener('click', () => this._saveOrder('draft'));
        document.getElementById('btnSendOrder')?.addEventListener('click',  () => this._saveOrder('sent'));
        document.getElementById('of_ref')?.addEventListener('input', () => this._validateOrderFormDetailed(false));
        document.getElementById('of_note')?.addEventListener('input', () => this._validateOrderFormDetailed(false));

        // Pré-remplir les lignes si édition
        if (order?.lines?.length) {
            // Magasin global = 1er magasin trouvé dans les lignes
            const firstWh = order.lines[0]?.warehouseId;
            if (firstWh) {
                const sel = document.getElementById('of_warehouse');
                if (sel) sel.value = firstWh;
            }
            order.lines.forEach(l => this._addLine(false, l));
        } else {
            this._addLine(false);
        }
    },

    // ─── Appliquer le magasin global à toutes les lignes existantes ───
    _applyGlobalWarehouse() {
        const whId = document.getElementById('of_warehouse')?.value;
        if (!whId) return;
        document.querySelectorAll('.ol-card .ol-wh').forEach(sel => {
            sel.value = whId;
        });
        this._refreshAllProductLineOffers();
        this._validateOrderFormDetailed(false);
    },

    // ─── Retourne la valeur du magasin global ou d'une ligne ──────────
    _getWarehouseForLine(prefillWhId) {
        if (prefillWhId) return String(prefillWhId);
        return document.getElementById('of_warehouse')?.value || '';
    },

    // ─── Création fournisseur à la volée ─────────────────────────────
    async _createSupplierInline() {
        const name    = document.getElementById('ns_name')?.value.trim();
        const contact = document.getElementById('ns_contact')?.value.trim() || '';
        const email   = document.getElementById('ns_email')?.value.trim()   || '';
        const phone   = document.getElementById('ns_phone')?.value.trim()   || '';
        if (!name) { toast('Le nom du fournisseur est obligatoire', 'error'); return; }
        try {
            const sup = await Api.supplierAdd({ name, contact, email, phone });
            this.suppliers.push(sup);
            const sel = document.getElementById('of_supplier');
            const opt = document.createElement('option');
            opt.value = sup.id; opt.textContent = sup.name; opt.selected = true;
            sel.appendChild(opt);
            document.getElementById('newSupplierPanel').style.display = 'none';
            document.getElementById('btnNewSupplier').style.display   = 'inline-flex';
            ['ns_name','ns_contact','ns_email','ns_phone'].forEach(id => {
                const el = document.getElementById(id); if (el) el.value = '';
            });
            toast(`Fournisseur "${name}" créé ✅`, 'success');
        } catch(e) { toast('Erreur : ' + e.message, 'error'); }
    },

    // ─── Ajout d'une ligne de commande ───────────────────────────────
    _lineCounter: 0,
    _addLine(isNewProduct = false, prefill = null) {
        const container = document.getElementById('of_lines');
        if (!container) return;

        const id    = ++this._lineCounter;
        const whId  = this._getWarehouseForLine(prefill?.warehouseId);
        // Catégories du magasin (cache) — pour la section "Nouveau produit"
        this._whCats = this._whCats || {};
        if (whId && this._whCats[parseInt(whId,10)]) this.categories = this._whCats[parseInt(whId,10)];

        // Options unités
        const unitLabel = (u)=>this._unitLabelForQty(u, 0);
        const unitOpts = ['piece','ml','litre','kg','m','m2','m3','carton','palette','bidon','fût','aérosol','sac','seau','rouleau','bobine','tube','boîte','paquet'].map(u =>
            `<option value="${u}" ${prefill?.unit===u?'selected':''}>${unitLabel(u)}</option>`
        ).join('');

        // Options magasins (pour override par ligne si besoin)
        const whOpts = this.warehouses.map(w =>
            `<option value="${w.id}" ${String(w.id)===String(whId)?'selected':''}>${w.name}</option>`
        ).join('');

        const card = document.createElement('div');
        card.className = 'ol-card' + (isNewProduct ? ' ol-new' : '');
        card.dataset.lineId = id;
        card.dataset.isNew  = isNewProduct ? '1' : '0';

        if (isNewProduct) {
            // ── NOUVEAU PRODUIT ──
            const cats = Array.isArray(this.categories) ? this.categories : [];
            const catOpts = cats.map(c =>
                `<option value="${c}">${c}</option>`
            ).join('');

            card.innerHTML = `
                <div class="ol-head">
                    <span class="ol-badge" style="background:rgba(251,146,60,.15);color:var(--orange)">🆕 Nouveau produit</span>
                    <button class="btn btn-sm btn-ghost" style="color:var(--red);padding:2px 8px"
                        onclick="OrdersPage._removeLine(${id})">✕ Retirer</button>
                </div>
                <div class="ol-body">
                    <div class="ol-row ol-row-2h" style="margin-bottom:.4rem">
                        <div>
                            <label class="lbl">Nom du produit *</label>
                            <input type="text" class="inp ol-np-name" placeholder="Désignation complète" data-lid="${id}">
                        </div>
                        <div>
                            <label class="lbl">SKU / Référence interne</label>
                            <input type="text" class="inp ol-np-sku" placeholder="Ex: PRD-0042" data-lid="${id}">
                        </div>
                    </div>
                    <div class="ol-row ol-row-2h" style="margin-bottom:.4rem">
                        <div>
                            <label class="lbl">Catégorie</label>
                            <select class="sel ol-np-cat" data-lid="${id}">
                                <option value="">— Sans catégorie —</option>${catOpts}
                            </select>
                            <div style="display:flex;gap:8px;margin-top:6px">
                                <input type="text" class="inp ol-np-newcat" placeholder="Créer une catégorie..." data-lid="${id}">
                                <button type="button" class="btn btn-sm btn-secondary" onclick="OrdersPage._createCategoryFromNewProd(${id})">Créer</button>
                            </div>
                        </div>
                        <div>
                            <label class="lbl">Unité de base</label>
                            <select class="sel ol-np-unit" data-lid="${id}">${unitOpts}</select>
                        </div>
                    </div>
                    <div class="ol-row ol-row-4">
                        <div>
                            <label class="lbl">Magasin</label>
                            <select class="sel ol-wh ol-np-wh" data-lid="${id}" onchange="OrdersPage._onNewProdWarehouseChange(this)">
                                <option value="">— Magasin —</option>${whOpts}
                            </select>
                        </div>
                        <div>
                            <label class="lbl">Quantité *</label>
                            <input type="number" class="inp ol-qty" min="0.01" step="0.01" placeholder="0"
                                oninput="OrdersPage._calcTotal()" data-lid="${id}">
                        </div>
                        <div>
                            <label class="lbl">Réf. fournisseur</label>
                            <input type="text" class="inp ol-skuf" placeholder="REF-FOUR" data-lid="${id}">
                        </div>
                        <div>
                            <label class="lbl">Prix unit. (${Locale.currencyCode()})</label>
                            <input type="number" class="inp ol-cost" min="0" step="0.01" placeholder="0.00"
                                oninput="OrdersPage._calcTotal()" data-lid="${id}">
                        </div>
                    </div>
                </div>`;
        } else {
            // ── PRODUIT CATALOGUE ──
            const prodOpts = this.products.map(p =>
                `<option value="${p.id}" ${prefill?.productId==p.id?'selected':''}>${p.name}${p.sku?' ('+p.sku+')':''}</option>`
            ).join('');

            card.innerHTML = `
                <div class="ol-head">
                    <span class="ol-badge" style="background:var(--blue-pale);color:var(--blue)">📦 Produit catalogue</span>
                    <button class="btn btn-sm btn-ghost" style="color:var(--red);padding:2px 8px"
                        onclick="OrdersPage._removeLine(${id})">✕ Retirer</button>
                </div>
                <div class="ol-body">
                    <div class="ol-row ol-prod-row">
                        <div class="ol-prod-cat-wrap">
                            <label class="lbl">Catégorie</label>
                            <select class="sel ol-prod-cat" data-lid="${id}" onchange="OrdersPage._onProdFilterChange(this)">
                                <option value="">— Toutes catégories —</option>
                            </select>
                        </div>
                        <div class="ol-prod-pick-wrap">
                            <label class="lbl">Produit *</label>
                            <div class="ol-prod-pick">
                                <select class="sel ol-prod" data-lid="${id}" onchange="OrdersPage._onProductChange(this)">
                                    <option value="">— Choisir dans le catalogue —</option>${prodOpts}
                                </select>
                                <input type="text" class="inp ol-prod-q" data-lid="${id}" placeholder="Rechercher (nom, SKU, code-barres)…" oninput="OrdersPage._onProdFilterChange(this)">
                            </div>
                        </div>
                    </div>
                    <div class="ol-product-preview" data-lid="${id}" aria-live="polite">
                        <div class="ol-product-preview-empty">Sélectionnez un produit pour afficher son image, sa référence, sa catégorie et son prix.</div>
                    </div>
                    <div class="ol-row ol-row-4">
                        <div>
                            <label class="lbl">Magasin</label>
                            <select class="sel ol-wh ol-np-wh" data-lid="${id}" onchange="OrdersPage._onNewProdWarehouseChange(this)">
                                <option value="">— Magasin —</option>${whOpts}
                            </select>
                        </div>
                        <div>
                            <label class="lbl">Quantité *</label>
                            <input type="number" class="inp ol-qty" min="0.01" step="0.01"
                                value="${prefill?.quantity||''}" placeholder="0"
                                oninput="OrdersPage._calcTotal()" data-lid="${id}">
                        </div>
                        <div>
                            <label class="lbl">Unité</label>
                            <select class="sel ol-unit" data-lid="${id}">${unitOpts}</select>
                        </div>
                        <div>
                            <label class="lbl">Prix unit. (${Locale.currencyCode()})</label>
                            <input type="number" class="inp ol-cost" min="0" step="0.01"
                                value="${prefill?.unitCost||''}" placeholder="0.00"
                                oninput="OrdersPage._calcTotal()" data-lid="${id}">
                        </div>
                    </div>
                    <div class="ol-row" style="grid-template-columns:1fr;margin-top:.4rem">
                        <div>
                            <label class="lbl">Réf. article fournisseur</label>
                            <input type="text" class="inp ol-skuf" placeholder="Référence article chez le fournisseur"
                                value="${prefill?.supplierSku||''}" data-lid="${id}">
                            <div class="ol-supplier-offer-info" style="font-size:12px;color:var(--text-muted);margin-top:6px"></div>
                        </div>
                    </div>
                </div>`;
        }

        container.appendChild(card);

        // Init UX picker (catalogue): filtre magasin/catégorie + dropdown overlay avec images (non régressif)
        try { this._initCataloguePicker(card); } catch(e) {}
        if (!isNewProduct) {
            this._refreshLineSupplierOffer(card);
            this._refreshLineProductPreview(card);
        }

        card.querySelectorAll('input, select, textarea').forEach(el => {
            el.addEventListener('input', () => this._validateOrderFormDetailed(false));
            el.addEventListener('change', () => this._validateOrderFormDetailed(false));
        });

        this._calcTotal();
        this._validateOrderFormDetailed(false);
        // Scroll vers la nouvelle ligne
        setTimeout(() => card.scrollIntoView({ behavior: 'smooth', block: 'nearest' }), 60);
    },

    _removeLine(id) {
        document.querySelectorAll('.ol-card').forEach(el => {
            if (String(el.dataset.lineId) === String(id)) el.remove();
        });
        this._calcTotal();
        this._validateOrderFormDetailed(false);
    },

    _onProductChange(select) {
        const card = select.closest('.ol-card');
        this._refreshLineSupplierOffer(card);
        this._refreshLineProductPreview(card);
        this._validateOrderFormDetailed(false);
    },



    // ─── UX: Catalogue picker (filtrage magasin + catégorie + recherche + overlay produits avec images) ─────────────
    _initCataloguePicker(card) {
        if (!card) return;
        const prodSel = card.querySelector('.ol-prod');
        if (!prodSel) return;

        // Éléments filtres (ajoutés dans le template)
        const catSel = card.querySelector('.ol-prod-cat');
        const qInp   = card.querySelector('.ol-prod-q');
        const whSel  = card.querySelector('.ol-wh'); // magasin de destination (dans la ligne)

        // Snapshot options d'origine (source de vérité)
        const baseOptions = Array.from(prodSel.options)
            .filter(o => o.value && o.value !== '' && !o.disabled)
            .map(o => ({ value: o.value, label: (o.textContent||'').trim(), opt: o }));

        // Map produit par id
        const byId = new Map(this.products.map(p => [String(p.id), p]));

        const getWhId = () => (whSel && whSel.value) ? String(whSel.value) : '';
        const getCatVal = () => (catSel && catSel.value) ? String(catSel.value) : '';
        const getQuery = () => (qInp && qInp.value) ? String(qInp.value).trim().toLowerCase() : '';

        const prodInWarehouse = (p, whId) => {
            if(window.StockCascade) return StockCascade.productInWarehouse(p, whId, { includeUnrestricted: true });
            if (!whId) return true;
            if (!p) return true;
            if (p.warehouse_id != null) return String(p.warehouse_id) === whId;
            if (p.warehouseId != null)  return String(p.warehouseId) === whId;
            if (Array.isArray(p.warehouses)) return p.warehouses.map(String).includes(whId);
            if (Array.isArray(p.allowedWarehouses)) return p.allowedWarehouses.map(String).includes(whId);
            return true;
        };

        const prodCategory = (p) => {
            if (!p) return '';
            return String(p.category || p.category_name || p.category_label || p.categorie || p.categoryId || p.category_id || '').trim();
        };

        const prodSku = (p) => String(p.sku || p.code || p.ref || p.reference || p.barcode || p.ean || '').trim();
        const prodImg = (p) => String(p.image || p.photo || p.img || p.image_url || p.photo_url || '').trim();

        const rebuildCategorySelect = (whId) => {
            if (!catSel) return;
            const cur = catSel.value || '';

            // Catégories autorisées par magasin (source de vérité : magasin + catégories déclarées)
            let allowed = [];
            try {
                const wObj = (this.warehouses || []).find(w => String(w.id) === String(whId));
                allowed = window.StockCascade ? StockCascade.parseNameList(wObj?.categories) : [];
            } catch(e) {
                allowed = [];
            }

            const categories = window.StockCascade
                ? StockCascade.getWarehouseCategories(this.warehouses || [], whId || '', this.products || [], allowed)
                : allowed;

            catSel.innerHTML =
                `<option value="">— Toutes catégories —</option>` +
                categories.map(c => `<option value="${this._esc(c)}">${this._esc(c)}</option>`)
                    .join('');

            // restore
            if (cur && Array.from(catSel.options).some(o => o.value === cur)) catSel.value = cur;
            else catSel.value = '';
        };

        const filterAndRebuildSelectOptions = () => {
            const whId = getWhId();
            const cat  = getCatVal();
            const q    = getQuery();

            // Catégories selon magasin
            rebuildCategorySelect(whId);

            // Filtrer
            const filtered = baseOptions.filter(o => {
                const p = byId.get(String(o.value));
                if (!prodInWarehouse(p, whId)) return false;
                const c = prodCategory(p);
                if (cat && String(c) !== String(cat)) return false;
                if (!q) return true;
                const label = (p?.name || o.label || '').toString().toLowerCase();
                const sku   = prodSku(p).toLowerCase();
                const ean = this._productBarcode(p).toLowerCase();
                return label.includes(q) || sku.includes(q) || ean.includes(q);
            });

            // Rebuild native select (fallback + compat)
            const current = prodSel.value;
            const head = prodSel.querySelector('option[value=""]')?.outerHTML || `<option value="">— Choisir dans le catalogue —</option>`;
            prodSel.innerHTML = head + filtered.slice(0, 200).map(o => o.opt.outerHTML).join(''); // limite perf
            if (current && Array.from(prodSel.options).some(o => o.value === current)) prodSel.value = current;
        };

        // Exposer un petit escaper HTML local si pas déjà présent
        if (!this._esc) {
            this._esc = (s) => String(s??'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#039;');
        }

        // Overlay singleton (pour éviter 1 overlay par ligne)
        if (!this._prodOverlay) {
            const ov = document.createElement('div');
            ov.className = 'sm-prod-overlay';
            ov.style.position = 'absolute';
            ov.style.zIndex = '9999';
            ov.style.display = 'none';
            ov.style.background = 'var(--card,#fff)';
            ov.style.border = '1px solid rgba(0,0,0,.08)';
            ov.style.borderRadius = '12px';
            ov.style.boxShadow = '0 16px 40px rgba(0,0,0,.14)';
            ov.style.maxHeight = '420px';
            ov.style.overflow = 'auto';
            ov.style.padding = '10px';

            const grid = document.createElement('div');
            grid.className = 'sm-prod-overlay-grid';
            grid.style.display = 'grid';
            grid.style.gridTemplateColumns = 'repeat(3, minmax(0, 1fr))';
            grid.style.gap = '10px';

            ov.appendChild(grid);
            document.body.appendChild(ov);

            this._prodOverlay = ov;
            this._prodOverlayGrid = grid;

            // Close on outside
            document.addEventListener('mousedown', (e) => {
                if (this._prodOverlay.style.display === 'none') return;
                if (this._prodOverlay.contains(e.target)) return;
                if (e.target?.classList?.contains('ol-prod')) return;
                this._hideProdOverlay();
            });
            window.addEventListener('scroll', () => { if (this._prodOverlay.style.display !== 'none') this._repositionProdOverlay(); }, true);
            window.addEventListener('resize', () => { if (this._prodOverlay.style.display !== 'none') this._repositionProdOverlay(); });
        }

        // Bind: changement filtre => refresh
        const onFilter = () => {
            filterAndRebuildSelectOptions();
            // Si overlay ouvert sur ce select => refresh overlay
            if (this._activeProdSelect === prodSel && this._prodOverlay.style.display !== 'none') {
                this._renderProdOverlay(prodSel, byId, prodInWarehouse, prodCategory, prodSku, prodImg, baseOptions);
            }
        };

        if (whSel) whSel.addEventListener('change', onFilter);
        if (catSel) catSel.addEventListener('change', onFilter);
        if (qInp)   qInp.addEventListener('input', onFilter);

        // Open overlay on click/focus of select (pour avoir images + infos)
        prodSel.addEventListener('mousedown', (e) => {
            // On empêche l'ouverture native du select uniquement quand overlay est possible
            e.preventDefault();
            this._showProdOverlay(prodSel, byId, prodInWarehouse, prodCategory, prodSku, prodImg, baseOptions);
        });
        prodSel.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' || e.key === ' ') {
                e.preventDefault();
                this._showProdOverlay(prodSel, byId, prodInWarehouse, prodCategory, prodSku, prodImg, baseOptions);
            }
            if (e.key === 'Escape') this._hideProdOverlay();
        });

        // Init first render (cat + select options)
        filterAndRebuildSelectOptions();
    },

    _showProdOverlay(prodSel, byId, prodInWarehouse, prodCategory, prodSku, prodImg, baseOptions) {
        this._activeProdSelect = prodSel;
        this._renderProdOverlay(prodSel, byId, prodInWarehouse, prodCategory, prodSku, prodImg, baseOptions);
        this._repositionProdOverlay();
        this._prodOverlay.style.display = 'block';
    },

    _hideProdOverlay() {
        if (this._prodOverlay) this._prodOverlay.style.display = 'none';
        this._activeProdSelect = null;
    },

    _repositionProdOverlay() {
        const sel = this._activeProdSelect;
        if (!sel || !this._prodOverlay) return;
        const r = sel.getBoundingClientRect();
        const top = window.scrollY + r.bottom + 6;
        const left = window.scrollX + r.left;
        const width = Math.min(Math.max(r.width, 560), 980);
        this._prodOverlay.style.top = `${top}px`;
        this._prodOverlay.style.left = `${left}px`;
        this._prodOverlay.style.width = `${width}px`;

        // Responsive cols
        const w = width;
        if (w < 560) this._prodOverlayGrid.style.gridTemplateColumns = 'repeat(1, minmax(0,1fr))';
        else if (w < 840) this._prodOverlayGrid.style.gridTemplateColumns = 'repeat(2, minmax(0,1fr))';
        else this._prodOverlayGrid.style.gridTemplateColumns = 'repeat(3, minmax(0,1fr))';
    },

    _renderProdOverlay(prodSel, byId, prodInWarehouse, prodCategory, prodSku, prodImg, baseOptions) {
        const card = prodSel.closest('.ol-card');
        const whSel = card?.querySelector('.ol-wh');
        const catSel = card?.querySelector('.ol-prod-cat');
        const qInp = card?.querySelector('.ol-prod-q');
        const whId = (whSel && whSel.value) ? String(whSel.value) : '';
        const cat  = (catSel && catSel.value) ? String(catSel.value) : '';
        const q    = (qInp && qInp.value) ? String(qInp.value).trim().toLowerCase() : '';

        const items = baseOptions.filter(o => {
            const p = byId.get(String(o.value));
            if (!prodInWarehouse(p, whId)) return false;
            const c = prodCategory(p);
            if (cat && String(c) !== String(cat)) return false;
            if (!q) return true;
            const label = (p?.name || o.label || '').toString().toLowerCase();
            const sku   = prodSku(p).toLowerCase();
            const ean = this._productBarcode(p).toLowerCase();
                return label.includes(q) || sku.includes(q) || ean.includes(q);
        }).slice(0, 60);

        const grid = this._prodOverlayGrid;
        grid.innerHTML = '';

        if (!items.length) {
            const empty = document.createElement('div');
            empty.style.padding = '14px';
            empty.style.color = 'var(--text-muted,#6b7280)';
            empty.textContent = 'Aucun produit pour ce filtre.';
            grid.appendChild(empty);
            return;
        }

        items.forEach(o => {
            const p = byId.get(String(o.value)) || {};
            const name = (p.name || o.label || '').toString();
            const sku  = prodSku(p);
            const catL = prodCategory(p);
            const img  = prodImg(p);

            const btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'sm-prod-item';
            btn.style.display = 'grid';
            btn.style.gridTemplateColumns = '56px 1fr';
            btn.style.gap = '10px';
            btn.style.alignItems = 'center';
            btn.style.textAlign = 'left';
            btn.style.padding = '10px';
            btn.style.borderRadius = '12px';
            btn.style.border = '1px solid rgba(0,0,0,.06)';
            btn.style.background = 'var(--card,#fff)';
            btn.style.cursor = 'pointer';

            const thumb = document.createElement('div');
            thumb.style.width = '56px';
            thumb.style.height = '56px';
            thumb.style.borderRadius = '10px';
            thumb.style.background = 'rgba(0,0,0,.04)';
            thumb.style.display = 'grid';
            thumb.style.placeItems = 'center';
            thumb.style.overflow = 'hidden';

            if (img) {
                const im = document.createElement('img');
                im.src = img;
                im.alt = `Image produit : ${name}`;
                im.style.width = '100%';
                im.style.height = '100%';
                im.style.objectFit = 'cover';
                thumb.appendChild(im);
            } else {
                thumb.textContent = '📦';
                thumb.style.fontSize = '22px';
            }

            const info = document.createElement('div');
            const title = document.createElement('div');
            title.textContent = name;
            title.style.fontWeight = '650';
            title.style.fontSize = '13px';
            title.style.lineHeight = '1.2';

            const meta = document.createElement('div');
            meta.style.display = 'flex';
            meta.style.gap = '8px';
            meta.style.marginTop = '6px';
            meta.style.flexWrap = 'wrap';
            meta.style.fontSize = '11px';
            meta.style.color = 'var(--text-muted,#6b7280)';

            if (sku) { const s=document.createElement('span'); s.textContent = sku; meta.appendChild(s); }
            if (catL){ const c=document.createElement('span'); c.textContent = catL; meta.appendChild(c); }

            info.appendChild(title);
            info.appendChild(meta);

            btn.appendChild(thumb);
            btn.appendChild(info);

            btn.addEventListener('mouseenter', () => {
                btn.style.borderColor = 'rgba(37,99,235,.35)';
                btn.style.boxShadow = '0 8px 18px rgba(37,99,235,.12)';
            });
            btn.addEventListener('mouseleave', () => {
                btn.style.borderColor = 'rgba(0,0,0,.06)';
                btn.style.boxShadow = 'none';
            });

            btn.addEventListener('click', () => {
                prodSel.value = String(o.value);
                prodSel.dispatchEvent(new Event('change', { bubbles: true }));
                this._hideProdOverlay();
            });

            grid.appendChild(btn);
        });
    },

    // Handler utilisé par onchange/oninput dans le template (non régressif)
    _onProdFilterChange(el) {
        const card = el?.closest?.('.ol-card');
        if (!card) return;
        // Re-init sur la carte (idempotent via listeners multi: accepté, mais on évite double en check)
        // Ici on déclenche simplement un refresh overlay si ouvert
        const prodSel = card.querySelector('.ol-prod');
        if (this._activeProdSelect === prodSel && this._prodOverlay?.style.display !== 'none') {
            // re-render via _initCataloguePicker qui a déjà les closures : on force click pour refresh
            this._repositionProdOverlay();
        }
    },

    _calcTotal() {
        let total = 0;
        document.querySelectorAll('.ol-card').forEach(card => {
            total += (parseFloat(card.querySelector('.ol-qty')?.value||0)) *
                     (parseFloat(card.querySelector('.ol-cost')?.value||0));
        });
        // Total dans la colonne droite
        const el = document.getElementById('of_total');
        if (el) {
            if (total > 0) {
                el.textContent = `Total estimé : ${Locale.formatMoney(total)}`;
                el.style.display = 'block';
            } else {
                el.style.display = 'none';
            }
        }
        // Total dans la barre nav sticky
        const navPill = document.getElementById('of_total_nav');
        if (navPill) navPill.textContent = total > 0 ? Locale.formatMoney(total) : '';
    },

    // ─── Collecte des lignes ──────────────────────────────────────────
    async _collectLines() {
        const lines    = [];
        let   hasError = false;

        // Si magasin global défini, l'utiliser comme fallback pour les lignes sans magasin
        const globalWh = document.getElementById('of_warehouse')?.value || '';

        const cards = document.querySelectorAll('.ol-card');
        for (const card of cards) {
            const isNew = card.dataset.isNew === '1';
            const qty   = parseFloat(card.querySelector('.ol-qty')?.value||0);
            const cost  = parseFloat(card.querySelector('.ol-cost')?.value||0);
            const whId  = card.querySelector('.ol-wh')?.value || globalWh;
            const skuf  = card.querySelector('.ol-skuf')?.value.trim()||'';

            if (!whId) { toast('Choisissez un magasin (global ou par ligne)', 'error'); hasError=true; break; }
            if (qty <= 0) { toast('Quantité invalide ou manquante sur une ligne', 'error'); hasError=true; break; }

            if (isNew) {
                const name = card.querySelector('.ol-np-name')?.value.trim();
                const sku  = card.querySelector('.ol-np-sku')?.value.trim()  || '';
                const cat  = card.querySelector('.ol-np-cat')?.value.trim()  || '';
                const unit = card.querySelector('.ol-np-unit')?.value         || 'piece';
                if (!name) { toast('Nom obligatoire pour les nouveaux produits', 'error'); hasError=true; break; }
                try {
                    toast(`Création du produit "${name}"…`, 'info', 1500);
                    const currentSupplierId = document.getElementById('of_supplier')?.value || '';
                    const newProd = await Api.prodAdd({
                        name, sku, category: cat, unitType: unit, packBaseUnit: unit,
                        cost, cump: cost, costByUnit: [],
                        supplier: currentSupplierId,
                        supplierLinks: currentSupplierId ? [{ supplierId: currentSupplierId, supplierSku: skuf, purchasePrice: cost, packagingLabel: unit, packagingQty: 1, minOrderQty: qty, leadTimeDays: 0, discountRate: 0, preferred: true, allowedWarehouses: whId ? [String(whId)] : [] }] : [],
                        trackLot: false, trackExpiry: false, image: '', description: '',
                    });
                    this.products.push(newProd);
                    lines.push({ productId: newProd.id, warehouseId: parseInt(whId), quantity: qty, unitCost: cost, unit, supplierSku: skuf });
                } catch(e) { toast('Erreur création produit : ' + e.message, 'error'); hasError=true; break; }
            } else {
                const prodId = card.querySelector('.ol-prod')?.value;
                const unit   = card.querySelector('.ol-unit')?.value || 'piece';
                if (!prodId) { toast('Choisissez un produit sur chaque ligne', 'error'); hasError=true; break; }
                lines.push({ productId: parseInt(prodId), warehouseId: parseInt(whId), quantity: qty, unitCost: cost, unit, supplierSku: skuf });
            }
        }

        return hasError ? null : lines;
    },

    // ─── Sauvegarde du BC ────────────────────────────────────────────
    async _saveOrder(action = 'draft') {
        const supplierId = document.getElementById('of_supplier')?.value;
        const ref        = document.getElementById('of_ref')?.value.trim()||'';
        const note       = document.getElementById('of_note')?.value.trim()||'';

        const check = this._validateOrderFormDetailed(true, action);
        if (!check.ok) return;
        if (action === 'sent' && check.warnings.length) {
            const ok = confirm('Le bon de commande contient des points à vérifier (prix, référence fournisseur ou image manquante). Voulez-vous quand même l’envoyer ?');
            if (!ok) return;
        }
        if (!supplierId) { toast('Choisissez un fournisseur', 'error'); return; }

        const lines = await this._collectLines();
        if (!lines) return;
        if (!lines.length) { toast('Ajoutez au moins une ligne de commande', 'error'); return; }

        try {
            let order;
            if (this.editId) {
                order = await Api.orderUpdate({ id: this.editId, action: 'update', supplierId, ref, note, lines });
            } else {
                order = await Api.orderCreate({ supplierId, ref, note, lines });
            }

            // Si action = sent, changer le statut
            if (action === 'sent' && order?.id) {
                await Api.orderStatus(order.id, 'sent');
                toast('Bon de commande créé et envoyé ✅', 'success');
            } else {
                toast('Bon de commande enregistré en brouillon 💾', 'success');
            }

            await this.loadData();
            this.showList();
        } catch(e) {
            console.error('[OrdersPage._saveOrder]', e);
            toast('Erreur : ' + (e.message||'inconnue'), 'error');
        }
    },

    // ═══════════════════════════════════════════════════════════
    // VUE RÉCEPTION
    // ═══════════════════════════════════════════════════════════
    _receiveHTML(order) {
        const sup  = this.suppliers.find(s => String(s.id) === String(order.supplierId));
        const rows = (order.lines||[]).map((l, i) => {
            const prod      = this.products.find(p => p.id == l.productId);
            const wh        = this.warehouses.find(w => w.id == l.warehouseId);
            const remaining = Math.max(0, l.quantity - (l.quantityReceived||0));
            const done      = remaining <= 0;
            const pct       = l.quantity > 0 ? Math.round(((l.quantityReceived||0) / l.quantity) * 100) : 0;
            return `
            <div class="order-line-card recv-product-card ${done?'is-done':''}">
                <div class="recv-product-head">
                    ${this._productThumbHTML(prod, 72)}
                    <div class="recv-product-title">
                        <strong>${this._escapeHtml(prod?.name||'?')}</strong>
                        ${prod ? this._productMetaHTML(prod) : ''}
                        <div class="order-product-extra">
                            <span>Magasin : <strong>${this._escapeHtml(wh?.name||'?')}</strong></span>
                            ${l.supplierSku ? `<span>Réf. four. : <code>${this._escapeHtml(l.supplierSku)}</code></span>` : '<span>Réf. four. non renseignée</span>'}
                        </div>
                    </div>
                    ${done ? '<span class="olc-badge" style="background:var(--green-pale);color:var(--green)">✅ Complet</span>' : `<span class="olc-badge" style="background:var(--orange-pale);color:var(--orange)">Reste ${remaining} ${this._escapeHtml(this._unitLabelForQty(l.unit, remaining))}</span>`}
                </div>
                <div class="recv-progress" aria-label="Progression de réception"><span style="width:${Math.min(100, Math.max(0, pct))}%"></span></div>
                <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:.5rem">
                    <div><label class="lbl">Commandé</label>
                        <div class="inp" style="background:var(--bg-elevated);cursor:default">${l.quantity} ${this._escapeHtml(this._unitLabelForQty(l.unit, l.quantity))}</div></div>
                    <div><label class="lbl">Déjà reçu</label>
                        <div class="inp" style="background:var(--bg-elevated);cursor:default">${l.quantityReceived||0}</div></div>
                    <div><label class="lbl">À réceptionner</label>
                        <input type="number" class="inp recv-qty" data-line="${i}"
                            min="0" max="${remaining}" step="0.01" value="${remaining}"
                            ${done?'disabled':''} style="${done?'opacity:.5':''}" aria-label="Quantité à réceptionner pour ${this._escapeHtml(prod?.name||'produit')}"></div>
                    <div><label class="lbl">N° de lot</label>
                        <input type="text" class="inp recv-lot" data-line="${i}" placeholder="LOT-2026-001"></div>
                    <div><label class="lbl">Date limite (DLC/DLUO)</label>
                        <input type="date" class="inp recv-dlc" data-line="${i}"></div>
                    <div><label class="lbl">Prix réel unitaire (${Locale.currencyCode()})</label>
                        <input type="number" class="inp recv-cost" data-line="${i}"
                            min="0" step="0.01" value="${l.unitCost||''}" placeholder="0.00"></div>
                </div>
            </div>`;
        }).join('');

        return `
        <div class="order-form-view">
            <div class="ofv-nav">
                <button class="btn btn-ghost btn-sm" id="btnBackToListR">
                    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
                    Retour
                </button>
                <div style="text-align:center">
                    <h3 style="margin:0;font-size:1rem;font-weight:700">📥 Réceptionner — ${order.orderNumber}</h3>
                    <div style="font-size:.82rem;color:var(--text-muted)">${sup?.name||'?'}${order.ref?' — '+order.ref:''}</div>
                </div>
                <button class="btn btn-success" id="btnConfirmReceive">✅ Confirmer la réception</button>
            </div>
            <div style="padding:1rem;overflow-y:auto">
                ${order.note ? `<div style="background:var(--blue-pale);border-radius:var(--r-lg);padding:.75rem;margin-bottom:1rem;font-size:.88rem">📝 ${order.note}</div>` : ''}
                <div id="receive_lines">${rows}</div>
            </div>
        </div>`;
    },

    _bindReceiveEvents(order) {
        document.getElementById('btnBackToListR')?.addEventListener('click', () => this.showList());
        document.getElementById('btnConfirmReceive')?.addEventListener('click', () => this._confirmReceive());
        document.querySelectorAll('.recv-qty').forEach(el => {
            el.addEventListener('input', () => {
                const max = parseFloat(el.getAttribute('max') || 0);
                let val = parseFloat(el.value || 0);
                if (val > max) el.value = String(max);
                if (val < 0) el.value = '0';
            });
        });
    },

    async _confirmReceive() {
        const lines = [];
        document.querySelectorAll('.recv-qty').forEach(el => {
            let qty = parseFloat(el.value||0);
            const max = parseFloat(el.getAttribute('max') || 0);
            if (qty > max) qty = max;
            if (qty <= 0) return;
            const i = el.dataset.line;
            lines.push({
                lineIndex:        parseInt(i),
                quantityReceived: qty,
                lotNumber:   document.querySelector(`.recv-lot[data-line="${i}"]`)?.value.trim()||'',
                expiryDate:  document.querySelector(`.recv-dlc[data-line="${i}"]`)?.value||'',
                unitCost:    parseFloat(document.querySelector(`.recv-cost[data-line="${i}"]`)?.value||0),
            });
        });

        if (!lines.length) { toast('Saisissez au moins une quantité reçue', 'error'); return; }

        try {
            await Api.orderReceive({ id: this.receiveId, lines });
            toast('Réception enregistrée — mouvements de stock créés ✅', 'success');
            await this.loadData();
            this.showList();
        } catch(e) {
            console.error('[confirmReceive]', e);
            toast('Erreur : ' + e.message, 'error');
        }
    },

    // ═══════════════════════════════════════════════════════════
    // ACTIONS LISTE
    // ═══════════════════════════════════════════════════════════
    async markSent(id) {
        if (!confirm('Envoyer ce BC (soumission à validation admin) ?')) return;
        try {
            await Api.orderStatus(id, 'sent');
            toast('BC envoyé (à valider) 📤', 'success');
            await this.loadData(); this.showList();
        } catch(e) { toast(e.message||'Erreur', 'error'); }
    },

    
    async approve(id) {
        try {
            const note = prompt('Note de validation (optionnel) :', '') ?? '';
            await Api.orderApproval(id, 'approved', note);
            toast('BC validé ✅', 'success');
            await this.loadData(); this.showList();
        } catch(e) { toast(e.message||'Erreur', 'error'); }
    },

    async refuse(id) {
        try {
            const note = prompt('Motif de refus (optionnel) :', '') ?? '';
            await Api.orderApproval(id, 'refused', note);
            toast('BC refusé ⛔', 'warning');
            await this.loadData(); this.showList();
        } catch(e) { toast(e.message||'Erreur', 'error'); }
    },

    async store(id) {
        try {
            await Api.orderStore(id);
            toast('Commande rangée 📦', 'success');
            await this.loadData(); this.showList();
        } catch(e) { toast(e.message||'Erreur', 'error'); }
    },
async deleteOrder(id) {
        if (!confirm('Supprimer ce bon de commande ?')) return;
        try {
            await Api.orderDelete(id);
            toast('Supprimé', 'success');
            await this.loadData(); this.showList();
        } catch(e) { toast(e.message||'Erreur', 'error'); }
    },

    async cancelOrder(id) {
        const note = prompt('Motif d\'annulation (optionnel) :', '') ?? '';
        if (note === null) return; // annulé par l'utilisateur
        try {
            await Api.orderStatus(id, 'cancelled');
            toast('BC annulé ❌', 'warning');
            await this.loadData(); this.showList();
        } catch(e) { toast(e.message||'Erreur', 'error'); }
    },

    async resubmit(id) {
        if (!confirm('Re-soumettre ce BC à validation après corrections ?\nLe statut repassera en « À valider ».')) return;
        try {
            // Reset validation status to pending, then keep status as sent
            await Api.orderApproval(id, 'pending', 'Re-soumission après corrections');
            toast('BC re-soumis à validation 🔄', 'success');
            await this.loadData(); this.showList();
        } catch(e) { toast(e.message||'Erreur', 'error'); }
    },

    _esc(v) {
        return String(v??'').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
    },

    // ═══════════════════════════════════════════════════════════
    // APERÇU IMPRESSION
    // ═══════════════════════════════════════════════════════════
    viewDetail(id) {
        const order = this.orders.find(o => String(o.id) === String(id));
        if (!order) return;
        const sup   = this.suppliers.find(s => String(s.id) === String(order.supplierId));
        const total = (order.lines||[]).reduce((s,l) => s+(l.unitCost||0)*(l.quantity||0), 0);
        const _esc = (v) => String(v==null?'':v).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
        const _evtLabel = (t) => ({created:'Créé',sent:'Envoyé',approved:'Validé',refused:'Refusé',pending:'Re-soumis',partial:'Réception partielle',received:'Réception complète',cancelled:'Annulé',stored:'Rangé'}[t] || t);
        const _evtColor = (t) => ({created:'#64748b',sent:'#3b82f6',approved:'#22c55e',refused:'#ef4444',pending:'#f59e0b',partial:'#f59e0b',received:'#22c55e',cancelled:'#ef4444',stored:'#64748b'}[t] || '#64748b');
        const fmtD = (iso) => iso ? Locale.formatDate(iso) : '—';
        const events = (order.events||[]).slice().sort((a,b)=>new Date(a.t)-new Date(b.t));
        const evtRows = events.map(e => '<tr><td style="padding:8px 10px;border-bottom:1px solid #eee;white-space:nowrap;font-size:12px">'+Locale.formatDateTime(e.t)+'</td><td style="padding:8px 10px;border-bottom:1px solid #eee"><span style="display:inline-block;padding:2px 8px;border-radius:99px;font-size:11px;font-weight:700;color:#fff;background:'+_evtColor(e.type)+'">'+_esc(_evtLabel(e.type))+'</span></td><td style="padding:8px 10px;border-bottom:1px solid #eee;font-weight:600">'+_esc(e.by||'—')+'</td><td style="padding:8px 10px;border-bottom:1px solid #eee;color:#64748b">'+_esc(e.note||'—')+'</td></tr>').join('');
        const rows = (order.lines||[]).map(l => {
            const p = this.products.find(x => x.id == l.productId);
            const w = this.warehouses.find(x => x.id == l.warehouseId);
            const pct = l.quantity > 0 ? Math.round(((l.quantityReceived||0) / l.quantity) * 100) : 0;
            const pctColor = pct >= 100 ? '#22c55e' : pct > 0 ? '#f59e0b' : '#94a3b8';
            const img = this._productImageSrc(p);
            const productCell = (img ? '<img src="'+_esc(img)+'" alt="" style="width:34px;height:34px;object-fit:cover;border-radius:6px;border:1px solid #e2e8f0;margin-right:8px;vertical-align:middle">' : '<span style="display:inline-flex;width:34px;height:34px;align-items:center;justify-content:center;border-radius:6px;background:#f1f5f9;margin-right:8px;vertical-align:middle">📦</span>') + _esc(p?.name||'?');
            return '<tr><td>'+productCell+'</td><td>'+(p?.sku||'—')+'</td><td>'+(l.supplierSku||'—')+'</td><td>'+(w?.name||'?')+'</td><td style="text-align:right">'+l.quantity+' '+_esc(this._unitLabelForQty(l.unit, l.quantity))+'</td><td style="text-align:right"><span style="color:'+pctColor+';font-weight:700">'+(l.quantityReceived||0)+'</span> <span style="font-size:11px;color:#94a3b8">('+pct+'%)</span></td><td style="text-align:right">'+(l.unitCost?Locale.formatMoney(l.unitCost):'—')+'</td><td style="text-align:right"><strong>'+Locale.formatMoney((l.unitCost||0)*(l.quantity||0))+'</strong></td></tr>';
        }).join('');
        const createdByName = order.userName || order.userId || '—';
        const statusLabels = { draft:'Brouillon', sent:'Envoyé', partial:'Réceptionné partiellement', received:'Réceptionné', cancelled:'Annulé' };
        const statusColors = { draft:'#64748b', sent:'#3b82f6', partial:'#f59e0b', received:'#22c55e', cancelled:'#ef4444' };
        const vsLabels = { pending:'En attente', approved:'Validé', refused:'Refusé' };
        const vsColors = { pending:'#64748b', approved:'#22c55e', refused:'#ef4444' };
        const vs = order.validationStatus || 'pending';
        const approvedEvt = (order.events||[]).find(e => e.type === 'approved');
        const refusedEvt  = (order.events||[]).find(e => e.type === 'refused');
        let validationDetail = '';
        if (vs === 'approved' && approvedEvt) {
            validationDetail = '<div style="margin-top:3px;font-size:12px;color:#22c55e">Par <strong>'+_esc(approvedEvt.by)+'</strong> le '+fmtD(approvedEvt.t)+'</div>';
        } else if (vs === 'refused' && refusedEvt) {
            validationDetail = '<div style="margin-top:3px;font-size:12px;color:#ef4444">Par <strong>'+_esc(refusedEvt.by)+'</strong> le '+fmtD(refusedEvt.t)+'</div>';
            if (order.validationNote || refusedEvt.note) validationDetail += '<div style="margin-top:2px;font-size:12px;color:#ef4444;font-style:italic">Motif : « '+_esc(order.validationNote || refusedEvt.note)+' »</div>';
        }

        if (window.MageStockPrint) {
            let bodyHtml = '';
            if (order.ref) bodyHtml += '<div class="ms-print-note"><strong>Référence :</strong> '+_esc(order.ref)+'</div>';
            if (order.note) bodyHtml += '<div class="ms-print-note"><strong>Note / instructions :</strong> '+_esc(order.note)+'</div>';
            bodyHtml += '<div class="ms-print-section-title">Lignes de commande</div>';
            const mainTable = '<table class="ms-print-order-table"><thead><tr><th>Produit</th><th>SKU</th><th>Réf. four.</th><th>Magasin</th><th style="text-align:right">Commandé</th><th style="text-align:right">Reçu</th><th style="text-align:right">Prix unit.</th><th style="text-align:right">Total</th></tr></thead><tbody>'+rows+'</tbody></table>';
            bodyHtml += mainTable + '<div class="total-row">Total estimé : '+Locale.formatMoney(total)+'</div>';
            if (events.length) {
                bodyHtml += '<div class="ms-print-section-title">Journal de suivi</div>';
                bodyHtml += '<table><thead><tr><th>Date</th><th>Événement</th><th>Par</th><th>Note</th></tr></thead><tbody>'+evtRows+'</tbody></table>';
            }
            bodyHtml += '<div class="ms-print-signatures"><div class="ms-print-signature">Responsable de la commande : '+_esc(createdByName)+'</div><div class="ms-print-signature">Visa validation</div></div>';
            window.MageStockPrint.open({
                title: 'Bon de commande '+(order.orderNumber || ''),
                subtitle: 'Commande fournisseur',
                orientation: 'landscape',
                meta: {
                    'Fournisseur': sup?.name || '?',
                    'Date': fmtD(order.createdAt),
                    'Créé par': createdByName,
                    'Statut': statusLabels[order.status] || order.status || '—',
                    'Validation': vsLabels[vs] || vs,
                    'Total estimé': Locale.formatMoney(total)
                },
                bodyHtml,
                footer: 'Bon de commande imprimé depuis MageStock'
            });
            return;
        }

        const win = window.open('', '_blank', 'width=820,height=750');
        if (!win) { toast('Autorisez les popups', 'warning'); return; }
        const css = '*{box-sizing:border-box}body{font-family:Segoe UI,Arial,sans-serif;padding:2rem;color:#222;font-size:13px;max-width:900px;margin:0 auto}.doc-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1.5rem;padding-bottom:1rem;border-bottom:3px solid #1e40af}.doc-title h1{margin:0;font-size:1.3rem;color:#1e40af}.doc-title h2{margin:4px 0 0;font-size:1rem;color:#333;font-weight:600}.doc-meta{text-align:right;font-size:12px;color:#555;line-height:1.8}.doc-meta strong{color:#222}.badge{display:inline-block;padding:3px 12px;border-radius:99px;font-size:11px;font-weight:700;color:#fff}.info-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;margin-bottom:1.5rem}.info-block{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:.75rem 1rem}.info-block label{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:#64748b;font-weight:700;margin-bottom:4px}.info-block span{font-size:13px;color:#1e293b;font-weight:600}table{width:100%;border-collapse:collapse;margin-bottom:1rem}thead th{background:#1e40af;color:#fff;padding:8px 10px;text-align:left;font-size:11px;font-weight:600;text-transform:uppercase}tbody td{border-bottom:1px solid #e2e8f0;padding:7px 10px;font-size:12px}tbody tr:nth-child(even){background:#f8fafc}.total-row{font-weight:700;font-size:14px;text-align:right;padding:.75rem 0;border-top:3px solid #1e40af}.section-title{margin:20px 0 10px;font-size:13px;font-weight:700;color:#1e40af;padding-bottom:4px;border-bottom:1px solid #dbeafe}.footer{display:flex;gap:3rem;margin-top:2.5rem;padding-top:1rem}.sign-block{flex:1}.sign-block label{display:block;font-size:11px;text-transform:uppercase;color:#64748b;font-weight:700;margin-bottom:4px}.sign-block .sign-name{font-size:13px;font-weight:600;color:#222;margin-bottom:2rem}.sign-block .sign-line{border-top:1px solid #94a3b8;padding-top:4px;font-size:11px;color:#94a3b8}@media print{.no-print{display:none!important}}';
        let h = '<!DOCTYPE html><html lang="'+(window.SM_LANG||'fr')+'"><head><meta charset="utf-8"><title>BC '+(order.orderNumber||'')+'</title><style>'+css+'</style></head><body>';
        h += '<div class="doc-header"><div class="doc-title"><h1>BON DE COMMANDE</h1><h2>'+(order.orderNumber||'')+'</h2>';
        if (order.ref) h += '<div style="font-size:12px;color:#64748b;margin-top:2px">Réf. : '+_esc(order.ref)+'</div>';
        h += '</div><div class="doc-meta">';
        h += '<div>Date : <strong>'+fmtD(order.createdAt)+'</strong></div>';
        h += '<div>Créé par : <strong>'+_esc(createdByName)+'</strong></div>';
        h += '<div style="margin-top:6px">Statut : <span class="badge" style="background:'+(statusColors[order.status]||'#64748b')+'">'+(statusLabels[order.status]||order.status)+'</span></div>';
        h += '<div style="margin-top:4px">Validation : <span class="badge" style="background:'+(vsColors[vs]||'#64748b')+'">'+(vsLabels[vs]||vs)+'</span></div>';
        h += validationDetail + '</div></div>';
        h += '<div class="info-grid-3"><div class="info-block"><label>Fournisseur</label><span>'+(sup?.name||'?')+'</span>';
        if (sup?.contact) h += '<br><small style="color:#64748b">Contact : '+_esc(sup.contact)+'</small>';
        if (sup?.email) h += '<br><small style="color:#64748b">'+_esc(sup.email)+'</small>';
        if (sup?.phone) h += '<br><small style="color:#64748b">'+_esc(sup.phone)+'</small>';
        h += '</div><div class="info-block"><label>Dates clés</label><span style="font-size:12px;line-height:1.8">';
        h += 'Créé : '+fmtD(order.createdAt)+'<br>';
        if (order.sentAt) h += 'Envoyé : '+fmtD(order.sentAt)+'<br>';
        if (order.validatedAt) h += 'Validé : '+fmtD(order.validatedAt)+'<br>';
        if (order.refusedAt) h += 'Refusé : '+fmtD(order.refusedAt)+'<br>';
        if (order.receivedAt) h += 'Réceptionné : '+fmtD(order.receivedAt)+'<br>';
        if (order.storedAt) h += 'Rangé : '+fmtD(order.storedAt);
        h += '</span></div><div class="info-block"><label>Note / Instructions</label><span style="font-size:12px;color:#334155">'+(order.note?_esc(order.note):'—')+'</span></div></div>';
        h += '<div class="section-title">📦 Lignes de commande</div>';
        h += '<table><thead><tr><th>Produit</th><th>SKU</th><th>Réf. four.</th><th>Magasin</th><th style="text-align:right">Commandé</th><th style="text-align:right">Reçu</th><th style="text-align:right">Prix unit.</th><th style="text-align:right">Total</th></tr></thead><tbody>'+rows+'</tbody></table>';
        h += '<div class="total-row">Total estimé : '+Locale.formatMoney(total)+'</div>';
        if (events.length) {
            h += '<div class="section-title">📜 Journal de suivi</div>';
            h += '<table><thead><tr><th>Date</th><th>Événement</th><th>Par</th><th>Note</th></tr></thead><tbody>'+evtRows+'</tbody></table>';
        }
        h += '<div class="footer"><div class="sign-block"><label>Responsable de la commande</label><div class="sign-name">'+_esc(createdByName)+'</div><div class="sign-line">Signature</div></div>';
        h += '<div class="sign-block"><label>Visa validation</label><div class="sign-name">'+(vs==='approved'&&approvedEvt?_esc(approvedEvt.by):'&nbsp;')+'</div><div class="sign-line">Signature &amp; cachet</div></div></div>';
        h += '<br><button class="no-print" onclick="window.print()" style="padding:8px 24px;cursor:pointer;margin-top:12px;background:#1e40af;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:600">🖨 Imprimer</button>';
        h += '</body></html>';
        win.document.write(h);
    },
    // ─── Création d'une catégorie depuis le bloc "Nouveau produit" (BC) ─────────
    async _createCategoryFromNewProd(lid){
        try {
            const inp = document.querySelector(`.ol-np-newcat[data-lid="${lid}"]`);
            if(!inp) return;
            const card = inp.closest('.ol-card');
            if(!card) return;

            const whSel = card.querySelector('.ol-wh') || document.getElementById('of_warehouse');
            const warehouseId = parseInt(whSel?.value || '0', 10);
            const category = (inp.value || '').trim();

            if(!warehouseId){
                alert("Choisir un magasin avant de créer une catégorie.");
                return;
            }
            if(!category){
                alert("Saisir un nom de catégorie.");
                return;
            }

            // Création côté magasin (comme la page Magasins)
            await Api.post(`api/warehouse-categories.php`, { warehouseId, category });

            // Recharger la liste depuis l'API (source de vérité)
            const data = await Api.get(`api/warehouse-categories.php?warehouseId=${warehouseId}`);
            const cats = Array.isArray(data?.categories) ? data.categories : [];

            const sel = card.querySelector(`.ol-np-cat[data-lid="${lid}"]`);
            if(sel){
                const esc = (s)=>String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
                sel.innerHTML = `<option value="">— Sans catégorie —</option>` + cats.map(c => `<option value="${esc(c)}">${esc(c)}</option>`).join('');
                sel.value = category;
            }

            inp.value = '';
        } catch(err){
            console.error(err);
            alert(err?.message || "Erreur lors de la création de la catégorie.");
        }
    },

};
