/**
 * Fichier  : assets/js/pages/movements.js
 * Auteur   : KREMER Régis
 * Version  : 2.2.0
 * Date     : 2026-02-21
 * Description : JS — Mouvements de stock : liste, filtres, saisie multi-type
 *
 * Changelog :
 *   v2.1.0 (2026-02-21) — Version initiale auditée et sécurisée
 *   v2.1.1 (2026-02-21) — Corrections sécurité, utils.js centralisé
 *   v2.2.0 (2026-02-21) — Permissions magasins, bons de commande, rapport d'écart,
 *                      import CSV, historique prix, bon de livraison multi-lignes
 */
// ================================================================
// movements.js — Full movement history with SCANNER support
// ================================================================
const MvPage = {
    pageIndex: 1,
    pageSize: 20,
    _all: [],
    _filtered: [],
    contextProduct: null,  // NEW: Pre-fill context
    contextWarehouse: null, // NEW: Pre-fill context

    async init(){
        const [whs, prods, suppliers, stock] = await Promise.all([
            Api.warehouses(), 
            Api.products(), 
            Api.suppliers(),
            Api.stock()
        ]);
        this.whs = whs; 
        this.prods = prods;
        this.suppliers = suppliers || [];
        this.stock = stock || [];
        
        // ── Filtre magasin ───────────────────────────────────────
        const whSel = document.getElementById('mvWhFilter');
        if(whSel) whSel.innerHTML = '<option value="">Tous les magasins</option>' +
            whs.map(w=>`<option value="${w.id}">${w.name}</option>`).join('');

        // ── Filtre produit (cascade : se met à jour selon le magasin) ──
        const prodSel = document.getElementById('mvProdFilter');
        const _buildProdOptions = (wid) => {
            if(!prodSel) return;
            let available = prods;
            if(wid) {
                // Pour filtrer les produits : ceux qui ont du stock dans ce magasin
                // OU tous si on veut aussi voir les mouvements passés
                const inWh = new Set((stock||[])
                    .filter(s=>String(s.warehouseId)===String(wid))
                    .map(s=>String(s.productId)));
                // On filtre sur ceux qui ont/ont eu du stock dans ce magasin
                // (on utilise _all si dispo, sinon tous les produits)
                const seen = new Set(
                    (this._all||[])
                        .filter(m=>String(m.warehouseId)===String(wid))
                        .map(m=>String(m.productId))
                );
                available = prods.filter(p=>inWh.has(String(p.id))||seen.has(String(p.id)));
                if(!available.length) available = prods; // fallback
            }
            const cur = prodSel.value;
            prodSel.innerHTML = '<option value="">Tous les produits</option>' +
                available.map(p=>{
                    const sku = p.sku ? ` — ${p.sku}` : '';
                    return `<option value="${p.id}"${String(p.id)===cur?' selected':''}>${p.name}${sku}</option>`;
                }).join('');
        };
        _buildProdOptions('');
        if(whSel) whSel.addEventListener('change', ()=>{
            _buildProdOptions(whSel.value);
            // Reset produit quand on change de magasin
            if(prodSel) prodSel.value='';
            this.load();
        });
        if(prodSel) prodSel.addEventListener('change', ()=>this.load());

        // ── Filtres avancés ──────────────────────────────────────
        ['mvOpFilter','mvSiteFilter','mvDeliveryFilter','mvSupplierFilter'].forEach(id=>{
            const el = document.getElementById(id);
            if(el) el.addEventListener('input', ()=>this.load());
        });

        // ── Dates par défaut : 30 derniers jours ─────────────────
        const today = new Date();
        const from  = new Date(today);
        from.setDate(from.getDate()-30);
        document.getElementById('mvDateFrom').value = from.toISOString().slice(0,10);
        document.getElementById('mvDateTo').value   = today.toISOString().slice(0,10);

        // ── Événements filtres principaux ────────────────────────
        ['mvSearch','mvTypeFilter','mvDateFrom','mvDateTo'].forEach(id=>{
            const el = document.getElementById(id);
            if(el) el.addEventListener('input', ()=>this.load());
        });

        // ── Reset ────────────────────────────────────────────────
        document.getElementById('btnMvReset').onclick = ()=>{
            ['mvSearch','mvOpFilter','mvSiteFilter','mvDeliveryFilter','mvSupplierFilter'].forEach(id=>{
                const el=document.getElementById(id); if(el) el.value='';
            });
            if(document.getElementById('mvTypeFilter')) document.getElementById('mvTypeFilter').value='';
            if(whSel) { whSel.value=''; _buildProdOptions(''); }
            if(prodSel) prodSel.value='';
            document.getElementById('mvDateFrom').value = from.toISOString().slice(0,10);
            document.getElementById('mvDateTo').value   = today.toISOString().slice(0,10);
            this.load();
        };
        
        // Quick action buttons (admin)
        const btnAdd = document.getElementById('btnMvAdd');
        const btnRem = document.getElementById('btnMvRemove');
        const btnTr  = document.getElementById('btnMvTransfer');
        const btnEntry = document.getElementById('btnMvEntry');
        
        if(btnAdd) btnAdd.onclick = ()=> this.openMvModal('add');
        if(btnRem) btnRem.onclick = ()=> this.openMvModal('remove');
        if(btnTr)  btnTr.onclick  = ()=> this.openMvModal('transfer');
        if(btnEntry) btnEntry.onclick = ()=> this.openMvModal('entry');

        // Advanced filters toggle
        const advBtn = document.getElementById('btnMvAdvanced');
        const advWrap = document.getElementById('mvAdvancedWrap');
        if(advBtn && advWrap){
            advBtn.onclick = ()=>{
                const open = advWrap.style.display !== 'none';
                advWrap.style.display = open ? 'none' : 'block';
                advBtn.textContent = open ? 'Filtres +' : 'Filtres −';
            };
        }
        
        // Pagination events
        const prevBtn = document.getElementById('mvPrev');
        const nextBtn = document.getElementById('mvNext');
        const sizeSel = document.getElementById('mvPageSize');
        
        if(prevBtn) prevBtn.onclick = () => {
            if(this.pageIndex > 1) {
                this.pageIndex--;
                this.renderTable();
            }
        };
        if(nextBtn) nextBtn.onclick = () => {
            const total = Math.ceil(this._filtered.length / this.pageSize);
            if(this.pageIndex < total) {
                this.pageIndex++;
                this.renderTable();
            }
        };
        if(sizeSel) {
            sizeSel.onchange = () => {
                this.pageSize = parseInt(sizeSel.value) || 20;
                this.pageIndex = 1;
                this.renderTable();
            };
        }
        
        // ══════════════════════════════════════════════════════════════
        // INFINITE SCROLL - Init pour mobile/tablette
        // ══════════════════════════════════════════════════════════════
        this._initInfiniteScroll();

        await this.load();

        // ── Tri des colonnes mouvements ──────────────────────────
        if(typeof TableSort !== 'undefined'){
            TableSort.init('mvTable',
                () => this._filteredRaw || [],
                (sorted) => {
                    this._filteredRaw = sorted;
                    this._filtered = this._groupForRender(sorted);
                    this.pageIndex = 1;
                    this.renderTable();
                },
                {
                    defaultKey: 'date',
                    defaultDir: 'desc',
                    getters: {
                        __product:   m => (this.prods.find(p=>p.id===m.productId)||{}).name||'',
                        __sku:       m => (this.prods.find(p=>p.id===m.productId)||{}).sku||'',
                        __warehouse: m => (this.whs.find(w=>w.id==m.warehouseId)||{}).name||'',
                        __stock:     m => this._getCurrentStock(m.productId, m.warehouseId),
                        date:        m => new Date(m.date).getTime(),
                        quantity:    m => Math.abs(m.quantity||0),
                    }
                }
            );
        }
        const modalType = sessionStorage.getItem('mvModalType');
        const modalProduct = sessionStorage.getItem('mvModalProduct');
        const modalWarehouse = sessionStorage.getItem('mvModalWarehouse');
        
        if(modalType) {
            // Clear sessionStorage
            sessionStorage.removeItem('mvModalType');
            sessionStorage.removeItem('mvModalProduct');
            sessionStorage.removeItem('mvModalWarehouse');
            
            // Open modal with prefill
            setTimeout(() => {
                this.openMvModal(
                    modalType,
                    modalProduct ? parseInt(modalProduct) : null,
                    modalWarehouse ? parseInt(modalWarehouse) : null
                );
            }, 100);
        }
    },

    async load(){
        const q       = (document.getElementById('mvSearch')?.value||'').toLowerCase();
        const typeF   = document.getElementById('mvTypeFilter')?.value||'';
        const whF     = parseInt(document.getElementById('mvWhFilter')?.value)||0;
        const pidF    = parseInt(document.getElementById('mvProdFilter')?.value)||0;
        const dateFrom= document.getElementById('mvDateFrom')?.value||'';
        const dateTo  = document.getElementById('mvDateTo')?.value||'';
        // Filtres avancés
        const opF     = (document.getElementById('mvOpFilter')?.value||'').toLowerCase();
        const siteF   = (document.getElementById('mvSiteFilter')?.value||'').toLowerCase();
        const blF     = (document.getElementById('mvDeliveryFilter')?.value||'').toLowerCase();
        const suppF   = (document.getElementById('mvSupplierFilter')?.value||'').toLowerCase();

        const params = {};
        if(dateFrom) params.dateFrom = dateFrom;
        if(dateTo)   params.dateTo   = dateTo;
        if(typeF)    params.type     = typeF;
        if(whF)      params.warehouseId = whF;
        if(pidF)     params.productId   = pidF;

        const mvs = await Api.movements(params);
        this._all = mvs;

        // Mettre à jour le filtre produit selon le magasin (après chargement)
        const prodSel = document.getElementById('mvProdFilter');
        if(prodSel && whF) {
            const seen = new Set(mvs.map(m=>String(m.productId)));
            const available = this.prods.filter(p=>seen.has(String(p.id)));
            if(available.length) {
                const cur = prodSel.value;
                prodSel.innerHTML = '<option value="">Tous les produits</option>' +
                    available.map(p=>{
                        const sku = p.sku ? ` — ${p.sku}` : '';
                        return `<option value="${p.id}"${String(p.id)===cur?' selected':''}>${p.name}${sku}</option>`;
                    }).join('');
            }
        }

        let filtered = [...mvs];

        // Filtre texte libre
        if(q) filtered = filtered.filter(m=>{
            const p = this.prods.find(x=>x.id===m.productId);
            const w = this.whs.find(x=>x.id==m.warehouseId);
            return (p?.name||'').toLowerCase().includes(q)||
                   (p?.sku||'').toLowerCase().includes(q)||
                   (w?.name||'').toLowerCase().includes(q)||
                   (m.notes||'').toLowerCase().includes(q);
        });

        // Filtres avancés (côté client)
        if(opF)   filtered = filtered.filter(m=>(m.operator||m.user||'').toLowerCase().includes(opF));
        if(siteF) filtered = filtered.filter(m=>(m.notes||'').toLowerCase().includes(siteF));
        if(blF)   filtered = filtered.filter(m=>(m.deliveryNote||m.notes||'').toLowerCase().includes(blF));
        if(suppF) filtered = filtered.filter(m=>(m.supplier||'').toLowerCase().includes(suppF));

        this._filtered = this._groupForRender(filtered);
        this._filteredRaw = filtered; // Conserver données brutes pour le tri
        // Appliquer tri actif
        if(typeof TableSort !== 'undefined'){
            this._filteredRaw = TableSort.sortData('mvTable', filtered);
            this._filtered = this._groupForRender(this._filteredRaw);
        }
        this.pageIndex = 1;
        this.renderTable();
    },

    // Helper pour obtenir le stock actuel d'un produit dans un magasin
    _getCurrentStock(productId, warehouseId) {
        const stockEntry = (this.stock || []).find(s => 
            String(s.productId) === String(productId) && 
            String(s.warehouseId) === String(warehouseId)
        );
        return stockEntry ? (parseInt(stockEntry.quantity) || 0) : 0;
    },

    renderTable(){
        const tbody = document.getElementById('mvBody');
        
        // ══════════════════════════════════════════════════════════════
        // MODE SCROLL INFINI (mobile/tablette)
        // ══════════════════════════════════════════════════════════════
        if(typeof InfiniteScroll !== 'undefined' && InfiniteScroll.isMobileMode()){
            const firstPage = this._filtered.slice(0, this.pageSize);
            
            InfiniteScroll.reset('movements', firstPage);
            InfiniteScroll.setTotal('movements', this._filtered.length);
            
            this._renderTableRows(firstPage, false);
            document.getElementById('mvCount').textContent = this._filtered.length + ' mouvement(s)';
            return;
        }
        
        // ══════════════════════════════════════════════════════════════
        // MODE PAGINATION CLASSIQUE (desktop)
        // ══════════════════════════════════════════════════════════════
        const start = (this.pageIndex-1)*this.pageSize;
        const page = this._filtered.slice(start, start+this.pageSize);

        if(!page.length){
            tbody.innerHTML = '<tr><td colspan="12" class="tbl-empty">Aucun mouvement</td></tr>';
            this.renderPagination();
            document.getElementById('mvCount').textContent = '0 mouvement(s)';
            return;
        }

        const typeLabel = {
            add: 'AJOUT',
            remove: 'SORTIE', 
            transfer: 'TRANSFERT',
            entry: 'ENTRÉE',
            inventory: 'INVENTAIRE'
        };
        const typeCls = {
            add: 'success',
            remove: 'danger',
            transfer: 'info',
            entry: 'success',
            inventory: 'warning'
        };

        tbody.innerHTML = page.map(m=>{
            
            if (m && m.__group) return this._renderGroupBlock(m);
const p = this.prods.find(x=>x.id===m.productId);
            const w = this.whs.find(x=>x.id==m.warehouseId);
            const date = new Date(m.date).toLocaleString('fr-FR', {
                day: '2-digit',
                month: '2-digit', 
                year: 'numeric',
                hour: '2-digit',
                minute: '2-digit'
            });
            
            // Format quantity with sign
            const qtySign = m.type === 'remove' ? '-' : '+';
            const qtyClass = m.type === 'remove' ? 'text-danger' : 'text-success';
            const qtyDisplay = `<span class="${qtyClass}">${qtySign}${Math.abs(m.quantity)}</span>`;
            
            // Stock actuel (depuis les données de stock)
            const currentStock = this._getCurrentStock(m.productId, m.warehouseId);
            
            // Reason/Site combined
            const reasonSite = [m.reason, m.site].filter(Boolean).join(' · ') || '—';
            
// Document associé : BC ou BL selon contexte
const bcMatch = (m.reason || '').match(/\bBC-\d{8}-[A-Z0-9]+\b/);
const bcId    = bcMatch ? bcMatch[0] : '';
const docLabel = bcId || '—';

let docBtn = '—';
if (bcId) {
    // Réception BC → bouton vers le bon de commande
    const bcUrl = 'orders?open=' + encodeURIComponent(bcId);
    docBtn = `<button class="btn btn-xs btn-primary" title="Ouvrir le bon de commande ${bcId}"
        onclick="window.open('${bcUrl}','_blank')">
        <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:3px;vertical-align:-1px"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>${bcId}</button>`;
} else if (['remove','transfer'].includes(m.type)) {
    // Sortie / transfert uniquement → BL individuel
    docBtn = `<button class="btn btn-xs btn-outline-secondary mv-btn-bl" title="Bon de livraison"
        onclick="window.open('api/delivery_note.php?movementId=${m.id}','_blank')">
        <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:3px;vertical-align:-1px"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>BL</button>`;
    if (m.site) {
        // Sortie chantier → BL groupé par site
        const d = (m.date || '').slice(0, 10);
        const siteUrl = 'api/delivery_note.php?site=' + encodeURIComponent(m.site) + '&date=' + encodeURIComponent(d);
        docBtn += `<button class="btn btn-xs btn-outline-info mv-btn-site" title="BL groupé — toutes les sorties du chantier ce jour"
            onclick="window.open('${siteUrl}','_blank')">
            <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:3px;vertical-align:-1px"><circle cx="12" cy="10" r="3"/><path d="M12 2a8 8 0 00-8 8c0 5.4 7.05 11.5 7.72 12.07a.5.5 0 00.56 0C13 21.5 20 15.4 20 10a8 8 0 00-8-8z"/></svg>Site</button>`;
    }
}
            return `
                <tr>
                    <td>${date}</td>
                    <td><span class="badge badge-${typeCls[m.type]||'secondary'}">${typeLabel[m.type]||m.type}</span></td>
                    <td><strong>${p?.name||'?'}</strong></td>
                    <td class="col-mono">${p?.sku||'—'}</td>
                    <td>${w?.name||'?'}</td>
                    <td class="col-qty">${qtyDisplay}</td>
                    <td class="col-stock">${currentStock}</td>
                    <td>${reasonSite}</td>
                    <td>${m.operator||'—'}</td>
                    <td>${m.note||'—'}</td>
                                        <td>${docLabel}</td>
                    <td class="cell-center">${docBtn}</td>
                </tr>
            `;
        }).join('');

        document.getElementById('mvCount').textContent = this._filtered.length + ' mouvement(s)';
        this.renderPagination();
    },

    renderPagination(){
        const total = Math.ceil(this._filtered.length / this.pageSize);
        const pageInfo = document.getElementById('mvPageInfo');
        const prevBtn = document.getElementById('mvPrev');
        const nextBtn = document.getElementById('mvNext');
        
        if(pageInfo) pageInfo.textContent = `Page ${this.pageIndex} / ${Math.max(1, total)}`;
        if(prevBtn) prevBtn.disabled = this.pageIndex <= 1;
        if(nextBtn) nextBtn.disabled = this.pageIndex >= total;
    },
    
    /**
     * Initialise le scroll infini pour mobile
     */
    _initInfiniteScroll(){
        if(typeof InfiniteScroll === 'undefined') return;
        
        const tbody = document.getElementById('mvBody');
        const pager = document.querySelector('#pgMovements .pager, #pgMovements .pagination-bar');
        
        if(!tbody) return;
        
        // Ajouter classe pour cacher le pager sur mobile
        if(pager) pager.classList.add('hide-mobile');
        
        InfiniteScroll.init({
            id: 'movements',
            container: document.querySelector('.main-content') || window,
            target: tbody,
            pagerEl: pager,
            pageSize: this.pageSize || 20,
            threshold: 300,
            loadMore: async (page, size) => {
                const start = (page - 1) * size;
                const end = start + size;
                const items = (this._filtered || []).slice(start, end);
                
                return {
                    items: items,
                    hasMore: end < (this._filtered || []).length
                };
            },
            renderItems: (items, append) => {
                this._renderTableRows(items, append);
            }
        });
    },
    
    /**
     * Render les lignes du tableau avec support append
     */

// Regroupe les entrées issues d'un même BC en une ligne déroulable (ergonomie)
_groupForRender(mvs) {
    const getBcId = (m) => {
        const bcMatch = (m.reason || '').match(/\bBC-\d{8}-[A-Z0-9]+\b/);
        return bcMatch ? bcMatch[0] : '';
    };

    const groups = new Map();
    const singles = [];

    for (const m of mvs) {
        const bcId = getBcId(m);
        const isEntry = (m.type === 'entry');
        const isReception = (m.reason || '').toLowerCase().includes('réception');
        if (bcId && isEntry && isReception) {
            if (!groups.has(bcId)) {
                groups.set(bcId, {
                    __group: true,
                    id: `grp_${bcId}`,
                    bcId,
                    date: m.date,
                    type: 'entry',
                    items: []
                });
            }
            const g = groups.get(bcId);
            g.items.push(m);
            if (m.date && g.date && m.date > g.date) g.date = m.date;
        } else {
            singles.push(m);
        }
    }

    const grouped = [...groups.values()].map(g => {
        g.quantity = g.items.reduce((s, it) => s + (Number(it.quantity) || 0), 0);
        g.reason = `Réception BC ${g.bcId}`;
        return g;
    });

    const all = [...singles, ...grouped];
    all.sort((a,b) => (b.date || '').localeCompare(a.date || ''));
    return all;
},

_renderGroupBlock(g) {
    const date = g.date ? new Date(g.date).toLocaleString('fr-FR', {
        day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit'
    }) : '—';

    const qty = Number(g.quantity) || 0;
    const qtyDisplay = `<span class="text-success">+${Math.abs(qty)}</span>`;

    const docLabel = g.bcId || '—';
    const bcUrl = g.bcId ? ('orders?open=' + encodeURIComponent(g.bcId)) : '';
    const docBtn = g.bcId
        ? `<button class="btn btn-xs btn-primary" title="Ouvrir le bon de commande"
            onclick="window.open('${bcUrl}','_blank')">📦 ${g.bcId}</button>`
        : '—';

    const count = g.items ? g.items.length : 0;

    const detailsRows = (g.items || []).map(m => {
        const p = this.prods.find(x=>x.id===m.productId);
        const w = this.whs.find(x=>x.id==m.warehouseId);
        const currentStock = this._getCurrentStock(m.productId, m.warehouseId);
        const qtySign2 = m.type === 'remove' ? '-' : '+';
        const qtyClass2 = m.type === 'remove' ? 'text-danger' : 'text-success';
        const qtyDisplay2 = `<span class="${qtyClass2}">${qtySign2}${Math.abs(m.quantity)}</span>`;
        return `
            <tr>
                <td>${p?.name || '—'}</td>
                <td>${p?.sku || '—'}</td>
                <td>${w?.name || '—'}</td>
                <td>${qtyDisplay2}</td>
                <td>${currentStock}</td>
            </tr>
        `;
    }).join('');

    return `
        <tr class="movement-group" data-group="${g.id}">
            <td>
                <button class="btn btn-xs btn-secondary" title="Dérouler / replier"
                    onclick="MvPage._toggleGroup('${g.id}')">▸</button>
                <span style="margin-left:6px">${date}</span>
            </td>
            <td><span class="badge badge-success">ENTRÉE</span></td>
            <td><strong>Réception ${g.bcId}</strong><div class="muted">${count} ligne(s)</div></td>
            <td>—</td>
            <td>—</td>
            <td>${qtyDisplay}</td>
            <td>—</td>
            <td>${g.reason}</td>
            <td>—</td>
            <td>—</td>
            <td>${docLabel}</td>
            <td class="cell-center">${docBtn}</td>
        </tr>
        <tr class="movement-group-details" data-group-details="${g.id}" style="display:none">
            <td colspan="12" class="group-details-cell">
                <div class="group-details-wrap">
                    <table class="table table-sm">
                        <thead>
                            <tr>
                                <th>Produit</th>
                                <th>SKU</th>
                                <th>Magasin</th>
                                <th>Qté</th>
                                <th>Stock</th>
                            </tr>
                        </thead>
                        <tbody>${detailsRows}</tbody>
                    </table>
                </div>
            </td>
        </tr>
    `;
},

_toggleGroup(groupId) {
    const details = document.querySelector(`tr[data-group-details="${groupId}"]`);
    const btn = document.querySelector(`tr[data-group="${groupId}"] button`);
    if (!details) return;
    const open = details.style.display !== 'none';
    details.style.display = open ? 'none' : '';
    if (btn) btn.textContent = open ? '▸' : '▾';
},

    _renderTableRows(mvs, append = false){
        const tbody = document.getElementById('mvBody');
        if(!tbody) return;
        
        if(!mvs.length && !append){
            tbody.innerHTML = '<tr><td colspan="12" class="tbl-empty">Aucun mouvement</td></tr>';
            return;
        }
        
        const typeLabel = {
            add: 'AJOUT',
            remove: 'SORTIE', 
            transfer: 'TRANSFERT',
            entry: 'ENTRÉE',
            inventory: 'INVENTAIRE'
        };
        const typeCls = {
            add: 'success',
            remove: 'danger',
            transfer: 'info',
            entry: 'success',
            inventory: 'warning'
        };
        
        const html = mvs.map(m=>{
            
            if (m && m.__group) return this._renderGroupBlock(m);
const p = this.prods.find(x=>x.id===m.productId);
            const w = this.whs.find(x=>x.id==m.warehouseId);
            const date = new Date(m.date).toLocaleString('fr-FR', {
                day: '2-digit',
                month: '2-digit', 
                year: 'numeric',
                hour: '2-digit',
                minute: '2-digit'
            });
            
            const qtySign = m.type === 'remove' ? '-' : '+';
            const qtyClass = m.type === 'remove' ? 'text-danger' : 'text-success';
            const qtyDisplay = `<span class="${qtyClass}">${qtySign}${Math.abs(m.quantity)}</span>`;
            const currentStock = this._getCurrentStock(m.productId, m.warehouseId);
            const reasonSite = [m.reason, m.site].filter(Boolean).join(' · ') || '—';
            
            return `
                <tr>
                    <td>${date}</td>
                    <td><span class="badge badge-${typeCls[m.type]||'secondary'}">${typeLabel[m.type]||m.type}</span></td>
                    <td><strong>${p?.name||'?'}</strong></td>
                    <td class="col-mono">${p?.sku||'—'}</td>
                    <td>${w?.name||'?'}</td>
                    <td class="col-qty">${qtyDisplay}</td>
                    <td class="col-stock">${currentStock}</td>
                    <td>${reasonSite}</td>
                    <td>${m.operator||'—'}</td>
                    <td>${m.note||'—'}</td>
                </tr>
            `;
        }).join('');
        
        if(append){
            tbody.insertAdjacentHTML('beforeend', html);
        } else {
            tbody.innerHTML = html;
        }
    },

    // ================================================================
    // ================================================================
    // MODAL MOUVEMENT — Cascade Magasin → Produit → Détails
    // v2.3.15 — UX logique : on choisit le magasin d'abord,
    // la liste produits se filtre en temps réel, le stock courant
    // est affiché comme contexte. Recherche live incluse.
    // ================================================================
    openMvModal(type, prefilledProduct = null, prefilledWarehouse = null) {
        const lbls = {
            add:      'Ajout de stock',
            remove:   'Sortie de stock',
            transfer: 'Transfert de stock',
            entry:    'Entrée de stock'
        };
        const icn = { add:'success', remove:'error', transfer:'info', entry:'success' };
        this._mvType = type;

        // ── Unités ──────────────────────────────────────────────────
        const unitLabel = u => ({
            piece:'pièce', ml:'mètre linéaire', m2:'m²', m3:'m³',
            litre:'litre', kg:'kg', fut:'fût', aerosol:'aérosol',
            boite:'boîte', carton:'carton', palette:'palette',
            bidon:'bidon', sac:'sac', seau:'seau', rouleau:'rouleau',
            bobine:'bobine', tube:'tube', paquet:'paquet',
            m:'mètre'
        }[u] || u);
        const allUnits = ['piece','carton','palette','bidon','fut','aerosol',
            'sac','seau','rouleau','bobine','tube','boite','paquet',
            'ml','litre','kg','m','m2','m3'];
        const unitOpts = allUnits.map(u=>`<option value="${u}">${unitLabel(u)}</option>`).join('');

        // ── HTML du formulaire ───────────────────────────────────────
        const whOpts = this.whs.map(w=>
            `<option value="${w.id}">${w.name}</option>`
        ).join('');
        const suppOpts = (this.suppliers||[]).map(s=>
            `<option value="${s.name}">${s.name}</option>`
        ).join('');

        // Scanner block (entry only)
        const scannerBlock = type === 'entry' ? `
            <div class="mv-scanner-zone">
                <div class="mv-scanner-label">🔍 Scanner code-barres</div>
                <div class="mv-scanner-row">
                    <input type="text" class="inp" id="mvmScanner"
                        placeholder="Scannez ou saisissez le code-barres…" autocomplete="off">
                    <button type="button" class="btn btn-secondary" id="mvmScanCamera">📷 Caméra</button>
                </div>
                <small class="mv-scanner-hint">💡 Le scan pré-remplit le produit automatiquement</small>
            </div>` : '';

        // Destination (transfer only)
        const transferDst = type === 'transfer' ? `
            <div class="mv-field-group">
                <label class="lbl">Magasin destination *</label>
                <select class="sel" id="mvmWhDst">
                    <option value="">— Choisir —</option>${whOpts}
                </select>
            </div>` : '';

        // Fournisseur (entry only)
        const supplierBlock = type === 'entry' ? `
            <div class="mv-field-group">
                <label class="lbl">Fournisseur *</label>
                <select class="sel" id="mvmSupplier">
                    <option value="">— Choisir —</option>${suppOpts}
                </select>
            </div>` : '';

        // Raison (remove only)
        const removeBlock = type === 'remove' ? `
            <div class="mv-section-sep"></div>
            <div class="mv-section-title">📋 Destination</div>
            <div class="mv-field-group">
                <label class="lbl">Raison / Destination</label>
                <select class="sel" id="mvmReason">
                    <option value="Chantier">Chantier</option>
                    <option value="Réparation">Réparation</option>
                    <option value="Livraison client">Livraison client</option>
                    <option value="Consommable">Consommable</option>
                    <option value="Autre">Autre</option>
                </select>
            </div>
            <div class="mv-field-group">
                <label class="lbl">Chantier / Lieu</label>
                <input type="text" class="inp" id="mvmSite" placeholder="Ex: Chantier Rue Dupont…">
            </div>
            <div class="mv-field-group">
                <label class="lbl">Nom opérateur</label>
                <input type="text" class="inp" id="mvmOper" placeholder="Nom prénom…">
            </div>` : '';

        // Prix achat (add) / coût unitaire (entry)
        const costBlock = type === 'add' ? `
            <div class="mv-field-group">
                <label class="lbl">Prix d'achat (pour l'unité sélectionnée)</label>
                <input type="number" class="inp" id="mvmCost" min="0" step="0.0001" placeholder="Ex: 12.50">
            </div>` :
            type === 'entry' ? `
            <div class="mv-field-group">
                <label class="lbl">Coût unitaire (€)</label>
                <input type="number" class="inp" id="mvmUnitCost" min="0" step="0.01" value="0">
            </div>` : '';

        // BL (entry only)
        const blBlock = type === 'entry' ? `
            <div class="mv-field-group">
                <label class="lbl">BL / Référence</label>
                <input type="text" class="inp" id="mvmDeliveryNote" placeholder="Ex: BL-2026-001">
            </div>` : '';

        // Note (add / transfer)
        const noteBlock = (type === 'add' || type === 'transfer') ? `
            <div class="mv-field-group">
                <label class="lbl">Note</label>
                <input type="text" class="inp" id="mvmNote">
            </div>` : '';

        const html = `
<div class="mv-modal-form">

${scannerBlock}

    <!-- ① MAGASIN (toujours en premier) -->
    <div class="mv-step" id="mvStepWh">
        <div class="mv-step-header">
            <span class="mv-step-num">1</span>
            <span class="mv-step-label">Magasin ${type==='transfer'?'source':''} *</span>
        </div>
        <select class="sel" id="mvmWh">
            <option value="">— Choisir un magasin —</option>
            ${whOpts}
        </select>
    </div>

    ${transferDst ? `<div class="mv-step">${transferDst}</div>` : ''}

    <!-- ② PRODUIT (filtré + recherche, activé après magasin) -->
    <div class="mv-step mv-step-locked" id="mvStepProd">
        <div class="mv-step-header">
            <span class="mv-step-num">2</span>
            <span class="mv-step-label">Produit *</span>
            <span class="mv-stock-badge" id="mvStockBadge" style="display:none"></span>
        </div>
        <!-- Recherche texte -->
        <div class="mv-prod-search-wrap">
            <input type="text" class="inp mv-prod-search" id="mvmProdSearch"
                placeholder="Rechercher un produit…" autocomplete="off" disabled>
        </div>
        <!-- Liste déroulante filtrée -->
        <select class="sel" id="mvmProd" disabled>
            <option value="">— Choisir d'abord un magasin —</option>
        </select>
    </div>

    ${supplierBlock}

    <!-- ③ QUANTITÉ & CONDITIONNEMENT (activé après produit) -->
    <div class="mv-step mv-step-locked" id="mvStepQty">
        <div class="mv-step-header">
            <span class="mv-step-num">3</span>
            <span class="mv-step-label">Quantité & Conditionnement</span>
        </div>
        <div class="mv-qty-row">
            <div class="mv-qty-unit">
                <label class="lbl">Unité</label>
                <select class="sel" id="mvmUnit" disabled>${unitOpts}</select>
            </div>
            <div class="mv-qty-num">
                <label class="lbl">Quantité *</label>
                <input type="number" class="inp" id="mvmQty" min="0.01" step="0.01" value="1" disabled>
            </div>
        </div>
        <small class="mv-conv-hint">Conversion automatique vers l'unité de base du produit lors de l'enregistrement.</small>
        ${costBlock}
    </div>

    <!-- ④ TRAÇABILITÉ -->
    <div class="mv-step mv-step-locked" id="mvStepTrace">
        <div class="mv-step-header">
            <span class="mv-step-num">4</span>
            <span class="mv-step-label">Traçabilité</span>
        </div>
        <div class="mv-field-group">
            <label class="lbl">Numéro de lot</label>
            <input type="text" class="inp" id="mvmLot" placeholder="Ex: LOT-2026-001" disabled>
        </div>
        <div class="mv-field-group">
            <label class="lbl">DLUO / Péremption</label>
            <input type="date" class="inp" id="mvmExpiry" disabled>
        </div>
        ${blBlock}
        ${removeBlock}
        ${noteBlock}
    </div>

</div>`;

        Modal.show(lbls[type], html, [
            { label:'Annuler', cls:'btn-secondary', onclick:()=>Modal.close() },
            { label:'Valider', cls:'btn-'+(type==='remove'?'danger':'primary'), onclick:()=>this.submitMv() }
        ], icn[type]);

        // ── Injecter le CSS du formulaire (une seule fois) ───────────
        this._injectMvModalCss();

        // ── Prefill si contexte ──────────────────────────────────────
        if (prefilledWarehouse) {
            const whSel = document.getElementById('mvmWh');
            if (whSel) {
                whSel.value = prefilledWarehouse;
                this._onWhChange(type, prefilledProduct);
            }
        }

        // ── Cascade Magasin → Produit ────────────────────────────────
        const whSel = document.getElementById('mvmWh');
        if (whSel) whSel.addEventListener('change', () => {
            this._onWhChange(type, null);
        });

        // ── Scanner entry ────────────────────────────────────────────
        if (type === 'entry') {
            const scanInp = document.getElementById('mvmScanner');
            if (scanInp) {
                setTimeout(() => scanInp.focus(), 80);
                scanInp.addEventListener('keypress', async e => {
                    if (e.key === 'Enter') {
                        e.preventDefault();
                        const v = scanInp.value.trim();
                        if (v) await this.handleBarcodeScan(v);
                    }
                });
            }
            const scanBtn = document.getElementById('mvmScanCamera');
            if (scanBtn) scanBtn.onclick = () => this._openCameraModal();
        }
    },

    // ─────────────────────────────────────────────────────────────
    // Cascade : changement de magasin
    // ─────────────────────────────────────────────────────────────
    _onWhChange(type, prefillPid) {
        const whSel  = document.getElementById('mvmWh');
        const prodSel = document.getElementById('mvmProd');
        const search  = document.getElementById('mvmProdSearch');
        const stepProd = document.getElementById('mvStepProd');
        const stepQty  = document.getElementById('mvStepQty');
        const stepTrace = document.getElementById('mvStepTrace');
        const badge   = document.getElementById('mvStockBadge');

        const wid = whSel ? whSel.value : '';
        if (!wid) return;

        // Débloquer l'étape produit
        if (stepProd) stepProd.classList.remove('mv-step-locked');
        if (prodSel)  prodSel.disabled = false;
        if (search)   search.disabled = false;

        const widInt = parseInt(wid);

        // ── Filtre par allowedWarehouses ─────────────────────────
        // Un produit est disponible dans un magasin si :
        //   • allowedWarehouses est vide/null (pas de restriction), OU
        //   • allowedWarehouses contient ce magasin
        const widStr = String(wid);
        const _isAllowed = (p) => {
            if(window.StockCascade) return StockCascade.productInWarehouse(p, widStr, { includeUnrestricted: true });
            let allowed = p.allowedWarehouses;
            if (typeof allowed === 'string') {
                try { allowed = JSON.parse(allowed || '[]'); } catch(e) { allowed = []; }
            }
            if (!Array.isArray(allowed) || allowed.length === 0) return true;
            return allowed.some(a => String(a) === widStr);
        };

        // ── Stock dans ce magasin (pour remove/transfer) ─────────
        const stockInWh = (this.stock || [])
            .filter(s => String(s.warehouseId) === String(wid) && (Number(s.quantity)||0) > 0)
            .map(s => String(s.productId));

        // ── Produits disponibles selon le type ───────────────────
        let availableProds;
        if (type === 'remove' || type === 'transfer') {
            // Seulement ceux avec du stock ET autorisés dans ce magasin
            availableProds = this.prods.filter(p =>
                stockInWh.includes(String(p.id)) && _isAllowed(p)
            );
        } else {
            // add/entry : tous les produits autorisés dans ce magasin
            availableProds = this.prods.filter(p => _isAllowed(p));
        }

        this._mvAvailableProds = availableProds;
        this._renderProdOptions(availableProds, prefillPid);

        // ── Bind recherche avec auto-sélection ───────────────────
        if (search) {
            search.value = '';
            search.oninput = () => {
                const q = search.value.toLowerCase().trim();
                const filtered = q
                    ? availableProds.filter(p =>
                        (p.name||'').toLowerCase().includes(q) ||
                        (p.sku||'').toLowerCase().includes(q) ||
                        (p.barcode||'').toLowerCase().includes(q)
                      )
                    : availableProds;
                this._renderProdOptions(filtered, null);

                // Auto-sélection si un seul résultat
                if (filtered.length === 1) {
                    const prodSel2 = document.getElementById('mvmProd');
                    if (prodSel2) {
                        prodSel2.value = filtered[0].id;
                        this._onProdChange(type);
                    }
                }
            };
            // Auto-sélection aussi sur Enter dans la recherche
            search.onkeydown = (e) => {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    const prodSel2 = document.getElementById('mvmProd');
                    if (prodSel2 && prodSel2.options.length === 2) { // 1 option + placeholder
                        prodSel2.selectedIndex = 1;
                        this._onProdChange(type);
                    }
                }
            };
        }

        // Bind changement produit
        if (prodSel) {
            prodSel.onchange = () => this._onProdChange(type);
            if (prefillPid) {
                prodSel.value = prefillPid;
                this._onProdChange(type);
            }
        }

        // Reset étapes suivantes
        if (stepQty)   stepQty.classList.add('mv-step-locked');
        if (stepTrace) stepTrace.classList.add('mv-step-locked');
        if (badge) badge.style.display = 'none';

        ['mvmUnit','mvmQty','mvmLot','mvmExpiry'].forEach(id => {
            const el = document.getElementById(id);
            if (el) el.disabled = true;
        });
    },

    _renderProdOptions(prods, prefillPid) {
        const prodSel = document.getElementById('mvmProd');
        if (!prodSel) return;
        if (!prods.length) {
            prodSel.innerHTML = '<option value="">Aucun produit disponible</option>';
            return;
        }
        prodSel.innerHTML = '<option value="">— Choisir un produit —</option>' +
            prods.map(p => {
                const sku = p.sku ? ` — ${p.sku}` : '';
                const sel = prefillPid && String(p.id) === String(prefillPid) ? ' selected' : '';
                return `<option value="${p.id}"${sel}>${p.name}${sku}</option>`;
            }).join('');
    },

    // ─────────────────────────────────────────────────────────────
    // Cascade : changement de produit
    // ─────────────────────────────────────────────────────────────
    _onProdChange(type) {
        const prodSel  = document.getElementById('mvmProd');
        const unitSel  = document.getElementById('mvmUnit');
        const qtyInp   = document.getElementById('mvmQty');
        const lotInp   = document.getElementById('mvmLot');
        const expInp   = document.getElementById('mvmExpiry');
        const stepQty  = document.getElementById('mvStepQty');
        const stepTrace = document.getElementById('mvStepTrace');
        const badge    = document.getElementById('mvStockBadge');
        const whSel    = document.getElementById('mvmWh');

        const pid = prodSel ? prodSel.value : '';
        if (!pid) return;

        const p   = this.prods.find(x => String(x.id) === String(pid));
        const wid = whSel ? whSel.value : '';
        if (!p) return;

        // Débloquer étapes Qty et Trace
        if (stepQty)   stepQty.classList.remove('mv-step-locked');
        if (stepTrace) stepTrace.classList.remove('mv-step-locked');

        ['mvmUnit','mvmQty','mvmLot','mvmExpiry'].forEach(id => {
            const el = document.getElementById(id);
            if (el) el.disabled = false;
        });

        // Unité de base du produit
        const base = (p.packBaseUnit || p.unitType || 'piece');
        if (unitSel && [...unitSel.options].some(o => o.value === base)) {
            unitSel.value = base;
        }

        // Obligations traçabilité
        if (lotInp) {
            lotInp.placeholder = p.trackLot
                ? 'OBLIGATOIRE — ex: LOT-2026-001'
                : 'Ex: LOT-2026-001';
        }
        if (expInp) expInp.required = !!p.trackExpiry;

        // Badge stock actuel dans ce magasin
        if (badge && wid) {
            const stockEntry = (this.stock||[]).find(s =>
                String(s.productId) === String(pid) &&
                String(s.warehouseId) === String(wid)
            );
            const qty = stockEntry ? (Number(stockEntry.quantity)||0) : 0;
            badge.style.display = '';
            if (type === 'remove' || type === 'transfer') {
                badge.className = `mv-stock-badge ${qty <= 0 ? 'mv-stock-zero' : qty < (p.minStock||5) ? 'mv-stock-low' : 'mv-stock-ok'}`;
                badge.textContent = `Stock : ${qty} ${base}`;
            } else {
                badge.className = 'mv-stock-badge mv-stock-ok';
                badge.textContent = `Stock actuel : ${qty} ${base}`;
            }
        }

        // Focus quantité
        setTimeout(() => { if (qtyInp) qtyInp.focus(); }, 60);
    },

    // ─────────────────────────────────────────────────────────────
    // CSS injecté dynamiquement (formulaire modal)
    // ─────────────────────────────────────────────────────────────
    _injectMvModalCss() {
        if (document.getElementById('mv-modal-css')) return;
        const style = document.createElement('style');
        style.id = 'mv-modal-css';
        style.textContent = `
/* ── Scanner zone ────────────────────────────────────────── */
.mv-scanner-zone {
    background: var(--blue-pale, #eff6ff);
    border: 2px dashed var(--blue, #3b82f6);
    border-radius: var(--r, 8px);
    padding: 14px;
    margin-bottom: 4px;
}
.mv-scanner-label {
    font-weight: 700;
    font-size: 13px;
    color: var(--blue, #3b82f6);
    margin-bottom: 8px;
}
.mv-scanner-row {
    display: flex;
    gap: 8px;
    align-items: center;
}
.mv-scanner-row .inp { flex: 1; }
.mv-scanner-hint {
    display: block;
    margin-top: 6px;
    font-size: 12px;
    color: var(--text-muted);
}

/* ── Steps ───────────────────────────────────────────────── */
.mv-modal-form {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.mv-step {
    border: 1px solid var(--border);
    border-radius: var(--r-lg, 10px);
    padding: 14px 16px;
    transition: opacity .2s, background .2s;
    background: var(--bg-card);
}
.mv-step-locked {
    opacity: .45;
    pointer-events: none;
    background: var(--bg-elevated);
}
.mv-step-header {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 10px;
    flex-wrap: wrap;
}
.mv-step-num {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--blue, #3b82f6);
    color: #fff;
    font-size: 12px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.mv-step-locked .mv-step-num {
    background: var(--text-muted);
}
.mv-step-label {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    flex: 1;
}

/* ── Stock badge ─────────────────────────────────────────── */
.mv-stock-badge {
    font-size: 12px;
    font-weight: 600;
    padding: 3px 10px;
    border-radius: 999px;
}
.mv-stock-ok   { background: var(--green-pale, #dcfce7); color: var(--green, #16a34a); }
.mv-stock-low  { background: var(--yellow-pale, #fef9c3); color: #b45309; }
.mv-stock-zero { background: var(--red-pale, #fee2e2); color: var(--red, #dc2626); }

/* ── Recherche produit ───────────────────────────────────── */
.mv-prod-search-wrap { margin-bottom: 8px; }
.mv-prod-search { width: 100%; }

/* ── Qty row ─────────────────────────────────────────────── */
.mv-qty-row {
    display: grid;
    grid-template-columns: 1fr 2fr;
    gap: 12px;
}
@media (max-width: 420px) {
    .mv-qty-row { grid-template-columns: 1fr; }
}

/* ── Misc ────────────────────────────────────────────────── */
.mv-conv-hint {
    display: block;
    font-size: 12px;
    color: var(--text-muted);
    margin-top: 6px;
    margin-bottom: 8px;
}
.mv-section-sep {
    height: 1px;
    background: var(--border);
    margin: 8px 0;
}
.mv-section-title {
    font-size: 13px;
    font-weight: 700;
    color: var(--text-secondary);
    margin-bottom: 8px;
}
.mv-field-group {
    margin-top: 10px;
}
.mv-field-group .lbl {
    display: block;
    margin-bottom: 4px;
}
        `;
        document.head.appendChild(style);
    },

    // ─────────────────────────────────────────────────────────────
    // Scan code-barres : remplit le produit dans le formulaire
    // ─────────────────────────────────────────────────────────────
    async handleBarcodeScan(barcode) {
        const scanInp = document.getElementById('mvmScanner');
        const prodSel = document.getElementById('mvmProd');

        const product = this.prods.find(p => p.barcode === barcode || p.sku === barcode);
        if (product) {
            this.playSuccessSound();
            // Si magasin pas encore choisi, on préfill sans filtrer
            if (prodSel && [...prodSel.options].some(o => String(o.value) === String(product.id))) {
                prodSel.value = product.id;
                this._onProdChange(this._mvType);
            } else {
                // Magasin pas encore sélectionné : stocker pour after
                this._pendingBarcodePid = product.id;
                toast(`✅ ${product.name} — choisissez d'abord un magasin`, 'info');
            }
            if (scanInp) scanInp.value = '';
            toast(`✅ ${product.name}`, 'success');
        } else {
            this.playErrorSound();
            toast(`❌ Code introuvable : ${barcode}`, 'error');
            if (scanInp) scanInp.value = '';
        }
    },

    // Ouvre la modal caméra secondaire
    _openCameraModal() {
        Modal.show('Scanner avec caméra', `
            <div class="qr-scan-wrap" style="max-width:340px">
                <div id="mvmCameraReader"></div>
            </div>
            <p style="text-align:center;font-size:13px;color:var(--text-muted);margin-top:12px">
                Présentez un code-barres devant la caméra
            </p>
        `, [
            { label:'Fermer', cls:'btn-secondary', onclick:()=>{ this.stopCameraScan(); Modal.close(); } }
        ], 'info');
        this.startCameraScan();
    },

    // ======== SOUNDS ========
    playSuccessSound() {
        try {
            const ac = new (window.AudioContext||window.webkitAudioContext)();
            const osc = ac.createOscillator(); const g = ac.createGain();
            osc.connect(g); g.connect(ac.destination);
            osc.frequency.value = 800; osc.type = 'sine';
            g.gain.setValueAtTime(0.3, ac.currentTime);
            g.gain.exponentialRampToValueAtTime(0.01, ac.currentTime + 0.2);
            osc.start(ac.currentTime); osc.stop(ac.currentTime + 0.2);
        } catch(_) {}
    },
    playErrorSound() {
        try {
            const ac = new (window.AudioContext||window.webkitAudioContext)();
            const osc = ac.createOscillator(); const g = ac.createGain();
            osc.connect(g); g.connect(ac.destination);
            osc.frequency.value = 200; osc.type = 'sawtooth';
            g.gain.setValueAtTime(0.3, ac.currentTime);
            g.gain.exponentialRampToValueAtTime(0.01, ac.currentTime + 0.3);
            osc.start(ac.currentTime); osc.stop(ac.currentTime + 0.3);
        } catch(_) {}
    },
    async submitMv(){
        const type = this._mvType;
        const pid  = document.getElementById('mvmProd').value;
        const wid  = document.getElementById('mvmWh').value;
        const qty  = parseFloat(document.getElementById('mvmQty').value)||0;
        const unit = document.getElementById('mvmUnit')?.value || 'piece';

        if(!pid || !wid || qty<=0){
            toast('Produit, magasin et quantité obligatoires', 'error');
            return;
        }

        if(type === 'entry') {
            const supplier = document.getElementById('mvmSupplier')?.value;
            if(!supplier) { toast('Fournisseur obligatoire pour les entrées', 'error'); return; }
        }

        const p = this.prods.find(x=>String(x.id)===String(pid));
        if(!p) return toast('Produit introuvable','error');

        const lot = (document.getElementById('mvmLot')?.value||'').trim();
        if(p.trackLot && !lot){ toast('Numéro de lot obligatoire pour ce produit','error'); return; }

        const expiry = document.getElementById('mvmExpiry')?.value || '';
        if(p.trackExpiry && !expiry){ toast('Date de péremption obligatoire pour ce produit','error'); return; }

        if(type==='transfer'){
            const wdst = document.getElementById('mvmWhDst')?.value;
            if(!wdst || wdst===wid){ toast('Magasin destination requis et différent de la source','error'); return; }
        }

        const data = {
            type,
            productId: parseInt(pid),
            warehouseId: parseInt(wid),
            quantity: (type==='remove' ? -Math.abs(qty) : Math.abs(qty)),
            unit,
            lotNumber: lot || null,
            expiryDate: expiry || null
        };

        if(type==='entry'){
            data.unitCost      = parseFloat(document.getElementById('mvmUnitCost')?.value) || 0;
            data.deliveryNote  = document.getElementById('mvmDeliveryNote')?.value?.trim() || '';
            data.supplier      = document.getElementById('mvmSupplier')?.value || '';
        }
        if(type==='add'){
            const cost = parseFloat(document.getElementById('mvmCost')?.value) || 0;
            if(cost > 0) data.unitCost = cost;
            data.notes = document.getElementById('mvmNote')?.value?.trim() || '';
        }
        if(type==='remove'){
            const reason = document.getElementById('mvmReason')?.value || '';
            const site   = document.getElementById('mvmSite')?.value?.trim() || '';
            const oper   = document.getElementById('mvmOper')?.value?.trim() || '';
            data.notes   = [reason, site, oper].filter(Boolean).join(' — ');
        }
        if(type==='transfer'){
            data.warehouseIdDest = parseInt(document.getElementById('mvmWhDst').value);
            data.notes = document.getElementById('mvmNote')?.value?.trim() || '';
        }

        try {
            await Api.movementAdd(data);
            toast('Mouvement enregistré','success');
            Modal.close();
            await this.load();
        } catch(e){
            toast(e.message,'error');
        }
    },

    async startCameraScan() {
        try {
            if(typeof ScannerService === 'undefined'){
                toast('Service scanner indisponible (script manquant).', 'error');
                return;
            }
            const host = document.getElementById('mvmCameraReader');
            if(!host) return;

            await ScannerService.start({
                target: host,
                mode: 'auto',
                onDetected: async (code) => {
                    await ScannerService.stop();
                    Modal.close();
                    await this.handleBarcodeScan(code);
                },
                onError: (e) => {
                    console.error(e);
                    toast('Caméra indisponible (autorisation / HTTPS requis).', 'error');
                },
                fps: 12
            });
        } catch(e) {
            console.error(e);
            toast('Impossible de démarrer la caméra.', 'error');
        }
    },

    stopCameraScan() {
        if(typeof ScannerService !== 'undefined') ScannerService.stop();
    },


async loadQuagga() {
        return new Promise((resolve, reject) => {
            if(typeof Quagga !== 'undefined') {
                resolve();
                return;
            }
            
            const script = document.createElement('script');
            script.src = 'https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.min.js';
            script.onload = () => resolve();
            script.onerror = () => reject(new Error('Failed to load Quagga2'));
            document.head.appendChild(script);
        });
    }
};
