// inventory.jsx — MODULE KHO (P1): tồn kho (tính từ sổ nhập/xuất), nhập kho, điều chỉnh,
// cảnh báo định mức, cài đặt duyệt xuất. Dùng CHUNG danh mục Sản phẩm (p.products) — mặt hàng
// có stockTracked = quản lý tồn. (Xuất kho có duyệt + đối chiếu báo giá/HĐ: pha P2.)
const INV_APP = APPS.find(a => a.id === 'inventory');

const ITEM_TYPES = {
  product:  { vi:'Sản phẩm', en:'Product' },
  material: { vi:'Vật tư',   en:'Material' },
  service:  { vi:'Dịch vụ',  en:'Service' },
};
const STOCK_STATUS = {
  ok:  { vi:'Đủ',            en:'OK',       cls:'badge-success' },
  low: { vi:'Dưới định mức', en:'Low',      cls:'badge-warning' },
  out: { vi:'Hết hàng',      en:'Out',      cls:'badge-error' },
};
const ISSUE_STATUS = {
  draft:     { vi:'Nháp',            en:'Draft',      cls:'badge-neutral' },
  l1:        { vi:'Chờ duyệt cấp 1', en:'Pending L1', cls:'badge-warning' },
  l2:        { vi:'Chờ duyệt cấp 2', en:'Pending L2', cls:'badge-warning' },
  approved:  { vi:'Đã duyệt',        en:'Approved',   cls:'badge-info' },
  issued:    { vi:'Đã xuất',         en:'Issued',     cls:'badge-success' },
  cancelled: { vi:'Đã hủy',          en:'Cancelled',  cls:'badge-error' },
};
// Số cấp duyệt phiếu xuất (0 = không duyệt, xuất thẳng).
function issueLevels(p){ return Math.max(0, Math.min(2, parseInt(p.appSettings.issueApprovalLevels || '1', 10) || 0)); }
// Người dùng hiện tại có quyền duyệt phiếu ở trạng thái hiện tại?
function canApproveIssue(iss, p){
  if (iss.status === 'l1') return p.role === 'manager' || p.role === 'admin';
  if (iss.status === 'l2') return p.role === 'admin';
  return false;
}
// Tồn của 1 mặt hàng theo tên (khớp danh mục Sản phẩm có quản lý tồn) — dùng đối chiếu báo giá/HĐ.
function stockOfName(name, products, qm){
  if (!name) return null;
  const n = (window.sdeburr ? sdeburr(name) : String(name).toLowerCase()).trim();
  const it = (products || []).find(pr => pr.stockTracked && (
    (window.sdeburr && sdeburr(pr.name) === n) || (window.productMatches && productMatches(pr, name)) || String(pr.name).toLowerCase() === String(name).toLowerCase()));
  if (!it) return null;
  const qty = (qm || {})[it.code] || 0;
  return { item:it, qty, status:stockStatus(qty, it.minStock) };
}

// Tồn theo từng mặt hàng = Σ nhập(+)/điều chỉnh(±) − xuất(−). out lưu qty dương → trừ.
function stockQtyMap(moves){
  const m = {};
  (moves || []).forEach(mv => {
    const q = +mv.qty || 0;
    m[mv.item] = (m[mv.item] || 0) + (mv.type === 'out' ? -q : q);
  });
  return m;
}
function stockStatus(qty, min){ if (qty <= 0) return 'out'; if (qty <= (min || 0)) return 'low'; return 'ok'; }
function trackedItems(products){ return (products || []).filter(p => p.stockTracked); }
function itemTypeLabel(t, lang){ return (ITEM_TYPES[t] || ITEM_TYPES.product)[lang]; }

function StockStatusBadge({ qty, min }){
  const { lang } = useLang(); const s = STOCK_STATUS[stockStatus(qty, min)];
  return <span className={'badge ' + s.cls}>{s[lang]}</span>;
}

function InventoryModule(){
  const p = usePortal();
  const { t } = useLang();
  const sub = p.route.sub || 'overview';
  const lowN = React.useMemo(() => {
    const qm = stockQtyMap(p.moves);
    return trackedItems(p.products).filter(it => stockStatus(qm[it.code] || 0, it.minStock) !== 'ok').length;
  }, [p.moves, p.products]);
  const pendN = (p.issues || []).filter(i => i.status === 'l1' || i.status === 'l2').length;
  const items = [
    { id:'overview', icon:'bi-pie-chart', label:t('c.overview') },
    { id:'stock', icon:'bi-boxes', label:p.lang === 'vi' ? 'Tồn kho' : 'Stock' },
    { id:'issues', icon:'bi-box-arrow-up', label:p.lang === 'vi' ? 'Phiếu xuất' : 'Issues', count:pendN || undefined },
    { id:'moves', icon:'bi-arrow-left-right', label:p.lang === 'vi' ? 'Nhập / Lịch sử' : 'Receipts / Log' },
    { id:'alerts', icon:'bi-exclamation-triangle', label:p.lang === 'vi' ? 'Cảnh báo' : 'Alerts', count:lowN || undefined },
    { id:'report', icon:'bi-file-earmark-bar-graph', label:p.lang === 'vi' ? 'Báo cáo' : 'Report' },
    { id:'settings', icon:'bi-gear', label:t('c.settings') },
  ];
  const go = (id, extra) => p.navigate({ name:'module', module:'inventory', sub:id, ...extra });
  return (<>
    <ModuleSidebar app={INV_APP} items={items} active={sub === 'issue' ? 'issues' : sub} onNavigate={go} />
    <main className="main">
      {sub === 'overview' && <InvOverview go={go} />}
      {sub === 'stock' && <InvStock go={go} />}
      {sub === 'issues' && <IssueList go={go} />}
      {sub === 'issue' && <IssueDetail code={p.route.id} go={go} />}
      {sub === 'moves' && <InvMoves go={go} />}
      {sub === 'alerts' && <InvAlerts go={go} />}
      {sub === 'report' && <InvReport />}
      {sub === 'settings' && <InvSettings />}
      {sub === 'help' && <ModuleHelp moduleId="inventory" app={INV_APP} />}
    </main>
  </>);
}

function InvOverview({ go }){
  const p = usePortal();
  const { lang } = useLang();
  const showCost = canViewCost(p);
  const qm = stockQtyMap(p.moves);
  const its = trackedItems(p.products);
  const low = its.filter(it => stockStatus(qm[it.code] || 0, it.minStock) === 'low');
  const out = its.filter(it => stockStatus(qm[it.code] || 0, it.minStock) === 'out');
  const value = its.reduce((s, it) => s + (qm[it.code] || 0) * (productCost(it) || 0), 0);
  const recent = [...(p.moves || [])].slice(0, 6);
  const mvLabel = { in:lang === 'vi' ? 'Nhập' : 'In', out:lang === 'vi' ? 'Xuất' : 'Out', adjust:lang === 'vi' ? 'Điều chỉnh' : 'Adjust' };
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{lang === 'vi' ? 'Kho' : 'Inventory'}</h1><div className="sub">{lang === 'vi' ? 'Tồn kho, nhập/xuất & cảnh báo định mức.' : 'Stock, moves & threshold alerts.'}</div></div>
        <button className="btn btn-primary" onClick={() => p.quick('inventory')}><i className="bi bi-box-arrow-in-down"></i>{lang === 'vi' ? 'Nhập kho' : 'Receive'}</button></div>
      <div className="kpi-strip" style={{ gridTemplateColumns: showCost ? 'repeat(4, 1fr)' : 'repeat(3, 1fr)' }}>
        <Kpi label={lang === 'vi' ? 'Mặt hàng theo dõi' : 'Tracked items'} value={its.length} icon="bi-boxes" />
        <Kpi label={lang === 'vi' ? 'Dưới định mức' : 'Low'} value={low.length} icon="bi-exclamation-triangle" deltaDir={low.length ? 'down' : 'up'} />
        <Kpi label={lang === 'vi' ? 'Hết hàng' : 'Out of stock'} value={out.length} icon="bi-x-octagon" deltaDir={out.length ? 'down' : 'up'} />
        {showCost && <Kpi label={lang === 'vi' ? 'Giá trị tồn' : 'Stock value'} value={vndShort(value)} icon="bi-cash-stack" />}
      </div>
      <div className="dash-grid">
        <div className="card span2"><div className="card-head"><h3><i className="bi bi-exclamation-triangle" style={{ color:'var(--warning)', marginRight:6 }}></i>{lang === 'vi' ? 'Cần nhập thêm' : 'Needs restock'}</h3><button className="btn btn-ghost btn-sm" onClick={() => go('alerts')}>{p.t('c.viewAll')}</button></div><div className="card-body">
          {[...out, ...low].slice(0, 6).map(it => { const q = qm[it.code] || 0; return (
            <div key={it.code} className="list-row" style={{ cursor:'pointer' }} onClick={() => p.navigate({ name:'module', module:'products', sub:'detail', id:it.code })}>
              <span className="mono" style={{ fontWeight:600, color:'var(--primary)', width:90 }}>{it.code}</span>
              <span className="grow truncate">{it.name}</span>
              <span className="mono" style={{ fontWeight:600 }}>{q}/{it.minStock} {it.unit}</span>
              <StockStatusBadge qty={q} min={it.minStock} />
            </div>
          ); })}
          {!out.length && !low.length && <EmptyState icon="bi-check2-circle" text={lang === 'vi' ? 'Mọi mặt hàng đều trên định mức' : 'All items above threshold'} />}
        </div></div>
        <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Nhập/xuất gần đây' : 'Recent moves'}</h3></div><div className="card-body">
          {recent.map(mv => { const it = (window.ProductStore && ProductStore.byCode && ProductStore.byCode(mv.item)) || p.products.find(x => x.code === mv.item) || {}; return (
            <div key={mv.id} className="list-row">
              <span className={'badge ' + (mv.type === 'in' ? 'badge-success' : mv.type === 'out' ? 'badge-error' : 'badge-neutral')} style={{ width:64 }}>{mvLabel[mv.type]}</span>
              <span className="grow truncate" style={{ fontSize:'var(--fs-base)' }}>{it.name || mv.item}</span>
              <span className="mono" style={{ fontWeight:600 }}>{mv.type === 'out' ? '−' : '+'}{Math.abs(mv.qty)}</span>
              <span className="subtle mono" style={{ fontSize:'var(--fs-sm)' }}>{fmtDate(mv.date)}</span>
            </div>
          ); })}
          {!recent.length && <EmptyState text={lang === 'vi' ? 'Chưa có giao dịch kho' : 'No moves yet'} />}
        </div></div>
      </div>
    </div>
  );
}

function InvStock({ go }){
  const p = usePortal();
  const { lang } = useLang();
  const [q, setQ] = React.useState('');
  const [typ, setTyp] = React.useState('all');
  const [st, setSt] = React.useState('all');
  const qm = stockQtyMap(p.moves);
  const srt = useTableSort();
  const list = srt.sortRows(trackedItems(p.products).filter(it => {
    const status = stockStatus(qm[it.code] || 0, it.minStock);
    return (typ === 'all' || it.itemType === typ) && (st === 'all' || status === st) &&
      (!q || (it.code + it.name).toLowerCase().includes(q.toLowerCase()));
  }), { code:it => it.code, name:it => it.name, type:it => (ITEM_TYPES[it.itemType] || {})[lang] || it.itemType, unit:it => it.unit,
    qty:it => qm[it.code] || 0, min:it => +it.minStock || 0, status:it => stockStatus(qm[it.code] || 0, it.minStock) });
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{lang === 'vi' ? 'Tồn kho' : 'Stock'}</h1><div className="sub">{list.length} {lang === 'vi' ? 'mặt hàng' : 'items'}</div></div>
        <button className="btn btn-primary" onClick={() => p.quick('inventory')}><i className="bi bi-box-arrow-in-down"></i>{lang === 'vi' ? 'Nhập kho' : 'Receive'}</button></div>
      <div className="toolbar">
        <div className="topsearch" style={{ width:220, background:'var(--bg)', border:'1px solid var(--border-strong)' }}><i className="bi bi-search"></i>
          <input value={q} onChange={e => setQ(e.target.value)} placeholder={p.t('c.search')} style={{ border:'none', outline:'none', background:'transparent', flex:1 }} /></div>
        <select className="select" style={{ width:'auto' }} value={typ} onChange={e => setTyp(e.target.value)}>
          <option value="all">{lang === 'vi' ? 'Loại' : 'Type'}: {p.t('c.all')}</option>
          {Object.entries(ITEM_TYPES).filter(([k]) => k !== 'service').map(([k, v]) => <option key={k} value={k}>{v[lang]}</option>)}
        </select>
        <div className="seg">
          {[['all', p.t('c.all')], ['ok', STOCK_STATUS.ok[lang]], ['low', STOCK_STATUS.low[lang]], ['out', STOCK_STATUS.out[lang]]].map(([k, lbl]) => <button key={k} className={st === k ? 'is-active' : ''} onClick={() => setSt(k)}>{lbl}</button>)}
        </div>
      </div>
      <div className="card"><div style={{ overflow:'auto' }}>
        <table className="tbl">
          <thead><tr><ThSort k="code" sort={srt}>{lang === 'vi' ? 'Mã' : 'Code'}</ThSort><ThSort k="name" sort={srt}>{lang === 'vi' ? 'Tên mặt hàng' : 'Item'}</ThSort><ThSort k="type" sort={srt}>{lang === 'vi' ? 'Loại' : 'Type'}</ThSort><ThSort k="unit" sort={srt}>{lang === 'vi' ? 'ĐVT' : 'Unit'}</ThSort><ThSort k="qty" sort={srt} className="num">{lang === 'vi' ? 'Tồn' : 'On hand'}</ThSort><ThSort k="min" sort={srt} className="num">{lang === 'vi' ? 'Định mức' : 'Min'}</ThSort><ThSort k="status" sort={srt}>{p.t('c.status')}</ThSort><th></th></tr></thead>
          <tbody>
            {list.map(it => { const qty = qm[it.code] || 0; return (
              <tr key={it.code} onClick={() => p.navigate({ name:'module', module:'products', sub:'detail', id:it.code })}>
                <td><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{it.code}</span></td>
                <td style={{ fontWeight:500 }}>{it.name}</td>
                <td className="muted">{itemTypeLabel(it.itemType, lang)}</td>
                <td className="muted">{it.unit}</td>
                <td className="num mono" style={{ fontWeight:600 }}>{qty}</td>
                <td className="num mono muted">{it.minStock}</td>
                <td><StockStatusBadge qty={qty} min={it.minStock} /></td>
                <td style={{ textAlign:'right' }} onClick={e => e.stopPropagation()}><button className="btn btn-sm" onClick={() => p.openStockAdjust(it.code)}><i className="bi bi-sliders"></i>{lang === 'vi' ? 'Điều chỉnh' : 'Adjust'}</button></td>
              </tr>
            ); })}
          </tbody>
        </table>
        {!list.length && <EmptyState text={lang === 'vi' ? 'Chưa có mặt hàng quản lý tồn. Bật "Quản lý tồn kho" cho sản phẩm/vật tư trong module Sản phẩm.' : 'No stock-tracked items yet.'} />}
      </div></div>
    </div>
  );
}

function InvMoves({ go }){
  const p = usePortal();
  const { lang } = useLang();
  const toast = useToast();
  const [typ, setTyp] = React.useState('all');
  const mvLabel = { in:lang === 'vi' ? 'Nhập' : 'In', out:lang === 'vi' ? 'Xuất' : 'Out', adjust:lang === 'vi' ? 'Điều chỉnh' : 'Adjust' };
  const srt = useTableSort();
  const list = srt.sortRows((p.moves || []).filter(m => typ === 'all' || m.type === typ),
    { date:m => m.date, type:m => mvLabel[m.type] || m.type, item:m => (p.products.find(x => x.code === m.item) || {}).name || m.item,
      qty:m => Math.abs(m.qty), ref:m => m.refCode, note:m => m.note, by:m => m.by });
  const receipts = (p.receipts || []).slice(0, 8);
  const srcLabel = { manual:lang === 'vi' ? 'Thủ công' : 'Manual', opening:lang === 'vi' ? 'Đầu kỳ' : 'Opening', buy:lang === 'vi' ? 'Từ HĐ mua' : 'From PO' };
  const printRec = (rec) => { if (!printDocument(stockReceiptHTML(rec, p))) toast(lang === 'vi' ? 'Trình duyệt chặn cửa sổ in' : 'Pop-up blocked', 'error'); };
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{lang === 'vi' ? 'Nhập / Lịch sử kho' : 'Receipts / Log'}</h1><div className="sub">{list.length} {lang === 'vi' ? 'giao dịch' : 'moves'}</div></div>
        <button className="btn btn-primary" onClick={() => p.quick('inventory')}><i className="bi bi-box-arrow-in-down"></i>{lang === 'vi' ? 'Nhập kho' : 'Receive'}</button></div>
      {receipts.length > 0 && (
        <div className="card" style={{ marginBottom:14 }}><div className="card-head"><h3 style={{ fontSize:'var(--fs-md)' }}><i className="bi bi-box-arrow-in-down" style={{ marginRight:6, color:'var(--success)' }}></i>{lang === 'vi' ? 'Phiếu nhập kho gần đây' : 'Recent receipts'}</h3></div>
          <div className="card-body">{receipts.map(r => (
            <div key={r.code} className="list-row">
              <span className="mono" style={{ fontWeight:600, color:'var(--success)', width:120 }}>{r.code}</span>
              <span className="badge badge-neutral" style={{ width:78 }}>{srcLabel[r.source] || r.source}</span>
              <span className="grow truncate" style={{ fontSize:'var(--fs-base)' }}>{(r.lines || []).length} {lang === 'vi' ? 'mặt hàng' : 'items'}{r.buyContract ? ' · ' + r.buyContract : ''}</span>
              <span className="subtle mono" style={{ fontSize:'var(--fs-sm)' }}>{fmtDate(r.date)}</span>
              <button className="iconbtn" title={lang === 'vi' ? 'In phiếu nhập' : 'Print'} onClick={() => printRec(r)}><i className="bi bi-printer"></i></button>
            </div>
          ))}</div>
        </div>
      )}
      <div className="toolbar"><div className="seg">
        {[['all', p.t('c.all')], ['in', mvLabel.in], ['out', mvLabel.out], ['adjust', mvLabel.adjust]].map(([k, lbl]) => <button key={k} className={typ === k ? 'is-active' : ''} onClick={() => setTyp(k)}>{lbl}</button>)}
      </div></div>
      <div className="card"><div style={{ overflow:'auto' }}>
        <table className="tbl">
          <thead><tr><ThSort k="date" sort={srt}>{lang === 'vi' ? 'Ngày' : 'Date'}</ThSort><ThSort k="type" sort={srt}>{lang === 'vi' ? 'Loại' : 'Type'}</ThSort><ThSort k="item" sort={srt}>{lang === 'vi' ? 'Mặt hàng' : 'Item'}</ThSort><ThSort k="qty" sort={srt} className="num">{lang === 'vi' ? 'Số lượng' : 'Qty'}</ThSort><ThSort k="ref" sort={srt}>{lang === 'vi' ? 'Chứng từ' : 'Ref'}</ThSort><ThSort k="note" sort={srt}>{lang === 'vi' ? 'Ghi chú' : 'Note'}</ThSort><ThSort k="by" sort={srt}>{lang === 'vi' ? 'Người' : 'By'}</ThSort></tr></thead>
          <tbody>
            {list.map(mv => { const it = p.products.find(x => x.code === mv.item) || {}; return (
              <tr key={mv.id} style={{ cursor:'default' }}>
                <td className="mono" style={{ fontSize:'var(--fs-base)' }}>{fmtDate(mv.date)}</td>
                <td><span className={'badge ' + (mv.type === 'in' ? 'badge-success' : mv.type === 'out' ? 'badge-error' : 'badge-neutral')}>{mvLabel[mv.type]}</span></td>
                <td style={{ fontWeight:500 }}>{it.name || mv.item} <span className="mono subtle" style={{ fontSize:'var(--fs-sm)' }}>{mv.item}</span></td>
                <td className="num mono" style={{ fontWeight:600, color:mv.type === 'out' ? 'var(--error)' : 'var(--success)' }}>{mv.type === 'out' ? '−' : '+'}{Math.abs(mv.qty)} {it.unit || ''}</td>
                <td>{mv.refCode ? <span className="mono" style={{ fontSize:'var(--fs-base)', color:'var(--primary)' }}>{mv.refCode}</span> : <span className="subtle">—</span>}</td>
                <td className="muted" style={{ fontSize:'var(--fs-base)' }}>{mv.note}</td>
                <td className="muted">{mv.by}</td>
              </tr>
            ); })}
          </tbody>
        </table>
        {!list.length && <EmptyState text={lang === 'vi' ? 'Chưa có giao dịch kho' : 'No moves yet'} />}
      </div></div>
    </div>
  );
}

function InvAlerts({ go }){
  const p = usePortal();
  const { lang } = useLang();
  const qm = stockQtyMap(p.moves);
  const srt = useTableSort();
  const list = srt.sortRows(trackedItems(p.products).map(it => ({ it, qty:qm[it.code] || 0 }))
    .filter(x => stockStatus(x.qty, x.it.minStock) !== 'ok')
    .sort((a, b) => (a.qty - a.it.minStock) - (b.qty - b.it.minStock)),
    { code:x => x.it.code, name:x => x.it.name, qty:x => x.qty, min:x => +x.it.minStock || 0,
      need:x => Math.max(x.it.minStock - x.qty, 0) || (x.it.minStock || 1), status:x => stockStatus(x.qty, x.it.minStock) });
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{lang === 'vi' ? 'Cảnh báo định mức' : 'Threshold alerts'}</h1><div className="sub">{lang === 'vi' ? 'Mặt hàng tồn ≤ định mức — cần nhập thêm.' : 'Items at/below minimum.'}</div></div></div>
      <div className="card"><div style={{ overflow:'auto' }}>
        <table className="tbl">
          <thead><tr><ThSort k="code" sort={srt}>{lang === 'vi' ? 'Mã' : 'Code'}</ThSort><ThSort k="name" sort={srt}>{lang === 'vi' ? 'Mặt hàng' : 'Item'}</ThSort><ThSort k="qty" sort={srt} className="num">{lang === 'vi' ? 'Tồn' : 'On hand'}</ThSort><ThSort k="min" sort={srt} className="num">{lang === 'vi' ? 'Định mức' : 'Min'}</ThSort><ThSort k="need" sort={srt} className="num">{lang === 'vi' ? 'Cần thêm' : 'To order'}</ThSort><ThSort k="status" sort={srt}>{p.t('c.status')}</ThSort><th></th></tr></thead>
          <tbody>
            {list.map(({ it, qty }) => { const need = Math.max(it.minStock - qty, 0) || (it.minStock || 1); return (
              <tr key={it.code} style={{ cursor:'default' }}>
                <td><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{it.code}</span></td>
                <td style={{ fontWeight:500 }}>{it.name}</td>
                <td className="num mono" style={{ fontWeight:600 }}>{qty} {it.unit}</td>
                <td className="num mono muted">{it.minStock}</td>
                <td className="num mono" style={{ fontWeight:600, color:'var(--warning)' }}>{need}</td>
                <td><StockStatusBadge qty={qty} min={it.minStock} /></td>
                <td style={{ textAlign:'right' }}><button className="btn btn-sm btn-accent" onClick={() => p.quick('contracts', { direction:'buy', vendor:it.vendor, item:it.code, qty:need })}><i className="bi bi-cart-plus"></i>{lang === 'vi' ? 'Tạo HĐ mua' : 'Purchase'}</button></td>
              </tr>
            ); })}
          </tbody>
        </table>
        {!list.length && <EmptyState icon="bi-check2-circle" text={lang === 'vi' ? 'Không có cảnh báo — mọi mặt hàng trên định mức.' : 'No alerts.'} />}
      </div></div>
    </div>
  );
}

function InvReport(){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const [from, setFrom] = React.useState(TODAY.slice(0, 8) + '01');
  const [to, setTo] = React.useState(TODAY);
  const srt = useTableSort();
  const rows = srt.sortRows(React.useMemo(() => buildStockReport(p.products, p.moves, from, to), [p.products, p.moves, from, to]),
    { code:r => r.code, name:r => r.name, unit:r => r.unit, opening:r => r.opening, inQty:r => r.inQty, outQty:r => r.outQty, adj:r => r.adjQty, closing:r => r.closing });
  const sum = rows.reduce((a, r) => ({ o:a.o + r.opening, i:a.i + r.inQty, x:a.x + r.outQty, j:a.j + r.adjQty, c:a.c + r.closing }), { o:0, i:0, x:0, j:0, c:0 });
  const doPrint = () => { if (!printDocument(stockReportHTML(rows, from, to, p))) toast(lang === 'vi' ? 'Trình duyệt chặn cửa sổ in' : 'Pop-up blocked', 'error'); };
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{lang === 'vi' ? 'Báo cáo tồn kho' : 'Inventory report'}</h1><div className="sub">{lang === 'vi' ? 'Tồn đầu kỳ · nhập · xuất · điều chỉnh · tồn cuối kỳ.' : 'Opening · in · out · adjust · closing.'}</div></div>
        <button className="btn btn-primary" onClick={doPrint}><i className="bi bi-printer"></i>{lang === 'vi' ? 'In báo cáo' : 'Print report'}</button></div>
      <div className="toolbar">
        <div className="flex g8 items-center"><span className="subtle" style={{ fontSize:'var(--fs-base)' }}>{lang === 'vi' ? 'Từ' : 'From'}</span><div style={{ width:160 }}><DateInput value={from} onChange={setFrom} /></div></div>
        <div className="flex g8 items-center"><span className="subtle" style={{ fontSize:'var(--fs-base)' }}>{lang === 'vi' ? 'Đến' : 'To'}</span><div style={{ width:160 }}><DateInput value={to} onChange={setTo} /></div></div>
      </div>
      <div className="card"><div style={{ overflow:'auto' }}>
        <table className="tbl">
          <thead><tr><ThSort k="code" sort={srt}>{lang === 'vi' ? 'Mã' : 'Code'}</ThSort><ThSort k="name" sort={srt}>{lang === 'vi' ? 'Mặt hàng' : 'Item'}</ThSort><ThSort k="unit" sort={srt}>{lang === 'vi' ? 'ĐVT' : 'Unit'}</ThSort><ThSort k="opening" sort={srt} className="num">{lang === 'vi' ? 'Tồn đầu' : 'Opening'}</ThSort><ThSort k="inQty" sort={srt} className="num">{lang === 'vi' ? 'Nhập' : 'In'}</ThSort><ThSort k="outQty" sort={srt} className="num">{lang === 'vi' ? 'Xuất' : 'Out'}</ThSort><ThSort k="adj" sort={srt} className="num">{lang === 'vi' ? 'Đ.chỉnh' : 'Adjust'}</ThSort><ThSort k="closing" sort={srt} className="num">{lang === 'vi' ? 'Tồn cuối' : 'Closing'}</ThSort></tr></thead>
          <tbody>
            {rows.map(r => (
              <tr key={r.code} style={{ cursor:'default' }}>
                <td><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{r.code}</span></td>
                <td style={{ fontWeight:500 }}>{r.name}</td>
                <td className="muted">{r.unit}</td>
                <td className="num mono">{r.opening}</td>
                <td className="num mono" style={{ color:r.inQty ? 'var(--success)' : undefined }}>{r.inQty}</td>
                <td className="num mono" style={{ color:r.outQty ? 'var(--error)' : undefined }}>{r.outQty}</td>
                <td className="num mono muted">{r.adjQty}</td>
                <td className="num mono" style={{ fontWeight:700 }}>{r.closing}</td>
              </tr>
            ))}
          </tbody>
          {rows.length > 0 && <tfoot><tr style={{ background:'var(--bg-subtle)' }}><td colSpan="3" style={{ fontWeight:700 }}>{lang === 'vi' ? 'Tổng cộng' : 'Total'}</td><td className="num mono" style={{ fontWeight:700 }}>{sum.o}</td><td className="num mono" style={{ fontWeight:700 }}>{sum.i}</td><td className="num mono" style={{ fontWeight:700 }}>{sum.x}</td><td className="num mono" style={{ fontWeight:700 }}>{sum.j}</td><td className="num mono" style={{ fontWeight:700 }}>{sum.c}</td></tr></tfoot>}
        </table>
        {!rows.length && <EmptyState text={lang === 'vi' ? 'Chưa có mặt hàng quản lý tồn.' : 'No stock-tracked items.'} />}
      </div></div>
    </div>
  );
}

function InvSettings(){
  const p = usePortal();
  const { t, lang } = useLang();
  const lv = String(p.appSettings.issueApprovalLevels || '1');
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{t('c.settings')}</h1><div className="sub">{lang === 'vi' ? 'Duyệt xuất kho & định mức.' : 'Issue approval & thresholds.'}</div></div></div>
      <div className="split">
        <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Số cấp duyệt phiếu xuất' : 'Issue approval levels'}</h3></div><div className="card-body col g12">
          <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'Cấp 1 = Quản lý kho; Cấp 2 = Giám đốc. Áp dụng cho Phiếu xuất kho (pha kế).' : 'L1 = warehouse manager; L2 = director.'}</div>
          <div className="seg">
            {[['0', lang === 'vi' ? 'Không duyệt' : 'None'], ['1', lang === 'vi' ? '1 cấp' : '1 level'], ['2', lang === 'vi' ? '2 cấp' : '2 levels']].map(([k, lbl]) => (
              <button key={k} className={lv === k ? 'is-active' : ''} onClick={() => p.setSetting('issueApprovalLevels', k)}>{lbl}</button>
            ))}
          </div>
        </div></div>
        <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Định mức tồn kho' : 'Stock thresholds'}</h3></div><div className="card-body subtle" style={{ fontSize:'var(--fs-base)', lineHeight:1.6 }}>
          {lang === 'vi' ? 'Định mức (ngưỡng cảnh báo) đặt cho từng mặt hàng trong module Sản phẩm — bật "Quản lý tồn kho" và nhập "Định mức tối thiểu". Khi tồn ≤ định mức, mặt hàng hiện ở tab Cảnh báo và có thể tạo HĐ mua.' : 'Set per-item minimum in the Products module.'}
        </div></div>
      </div>
    </div>
  );
}

/* Nhập kho (quick action target) — tạo phiếu nhập + cộng tồn */
function genReceiptCode(list){
  const yr = TODAY.slice(0, 4);
  const max = (list || []).reduce((m, r) => { const mm = (r.code || '').match(new RegExp('PN-' + yr + '-(\\d+)')); return mm ? Math.max(m, +mm[1]) : m; }, 0);
  return 'PN-' + yr + '-' + String(max + 1).padStart(3, '0');
}
function NewReceiptModal({ preset, onClose }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const its = trackedItems(p.products);
  const code = genReceiptCode(p.receipts);
  // buyContract có thể là HĐ mua hoặc BÁO GIÁ MUA chốt-không-HĐ (cùng shape) — phiếu nhập gắn theo mã đó.
  const buyCon = preset && preset.buyContract ? (p.contracts.find(c => c.code === preset.buyContract) || (p.quotes || []).find(x => x.code === preset.buyContract)) : null;
  const buyIsQuote = !!(buyCon && !p.contracts.some(c => c.code === buyCon.code));
  // Nhập từ HĐ mua: prefill các hạng mục khớp mặt hàng quản lý tồn.
  // Matcher đồng bộ với cả app (flow.contractHasStock / stockOfName): tên/deburr HOẶC match-token HOẶC lowercase.
  const matchTracked = (nm) => its.find(pr => (window.sdeburr && sdeburr(pr.name) === sdeburr(nm)) || (window.productMatches && productMatches(pr, nm)) || String(pr.name).toLowerCase() === String(nm).toLowerCase());
  const fromItems = (preset && preset.fromItems) || [];
  const presetLines = fromItems.map(it => { const m = matchTracked(it.name); return m ? { item:m.code, qty:it.qty } : null; }).filter(Boolean);
  const unmatched = fromItems.filter(it => !matchTracked(it.name));
  const [source, setSource] = React.useState((preset && preset.source) || 'manual');
  const [lines, setLines] = React.useState(presetLines.length ? presetLines : [{ item:its[0] ? its[0].code : '', qty:'' }]);
  const [note, setNote] = React.useState(buyCon ? (buyIsQuote ? (lang === 'vi' ? 'Nhập theo báo giá mua ' + buyCon.code : 'For purchase quote ' + buyCon.code) : (lang === 'vi' ? 'Nhập theo hợp đồng mua ' + buyCon.code : 'For purchase ' + buyCon.code)) : '');
  const setLine = (i, k, v) => setLines(s => s.map((l, j) => j === i ? { ...l, [k]:v } : l));
  const addLine = () => setLines(s => [...s, { item:its[0] ? its[0].code : '', qty:'' }]);
  const rmLine = (i) => setLines(s => s.filter((_, j) => j !== i));
  const create = () => {
    const valid = lines.filter(l => l.item && +l.qty > 0).map(l => { const it = p.products.find(x => x.code === l.item) || {}; return { item:l.item, name:it.name, unit:it.unit, qty:+l.qty }; });
    if (!valid.length) { toast(lang === 'vi' ? 'Thêm ít nhất 1 dòng hợp lệ' : 'Add at least one line', 'error'); return; }
    p.addReceipt({ code, source, buyContract:(preset && preset.buyContract) || null, vendor:(preset && preset.vendor) || null, lines:valid, note:note.trim(), date:TODAY, by:p.me.name });
    toast((lang === 'vi' ? 'Đã nhập kho ' : 'Received ') + code, 'success');
    onClose();
    p.navigate({ name:'module', module:'inventory', sub:'moves' });
  };
  return (
    <Modal title={(lang === 'vi' ? 'Nhập kho · ' : 'Receive · ') + code} width={640} onClose={onClose}
      footer={<><button className="btn" onClick={onClose}>{t('c.cancel')}</button><button className="btn btn-primary" onClick={create}>{t('c.create')}</button></>}>
      <div className="col g16">
        {buyCon && <div className="flex g8 items-center" style={{ fontSize:'var(--fs-base)' }}><span className="subtle">{buyIsQuote ? (lang === 'vi' ? 'Báo giá mua:' : 'Purchase quote:') : (lang === 'vi' ? 'Hợp đồng mua:' : 'Purchase:')}</span><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{buyCon.code}</span></div>}
        <div className="flex g12 wrap">
          <div style={{ flex:'1 1 200px' }}><Field label={lang === 'vi' ? 'Loại phiếu' : 'Source'}><select className="select" value={source} onChange={e => setSource(e.target.value)}>
            <option value="manual">{lang === 'vi' ? 'Nhập thủ công' : 'Manual'}</option>
            <option value="opening">{lang === 'vi' ? 'Tồn đầu kỳ' : 'Opening'}</option>
            <option value="buy">{lang === 'vi' ? 'Từ hợp đồng mua' : 'From purchase'}</option>
          </select></Field></div>
        </div>
        <div className="col g8">
          <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'Mặt hàng' : 'Items'}</div>
          {lines.map((l, i) => (
            <div key={i} className="flex g8 items-center">
              <select className="select" style={{ flex:1 }} value={l.item} onChange={e => setLine(i, 'item', e.target.value)}>
                {its.map(it => <option key={it.code} value={it.code}>{it.code} · {it.name}</option>)}
              </select>
              <input className="input" type="number" min="0" style={{ width:110 }} value={l.qty} onChange={e => setLine(i, 'qty', e.target.value)} placeholder={lang === 'vi' ? 'Số lượng' : 'Qty'} />
              <button className="iconbtn" onClick={() => rmLine(i)} title={t('c.delete')}><i className="bi bi-x-lg"></i></button>
            </div>
          ))}
          <button className="btn btn-subtle btn-sm" style={{ alignSelf:'flex-start' }} onClick={addLine}><i className="bi bi-plus-lg"></i>{lang === 'vi' ? 'Thêm dòng' : 'Add line'}</button>
          {!its.length && <div className="subtle" style={{ fontSize:'var(--fs-sm)', color:'var(--warning)' }}>{lang === 'vi' ? 'Chưa có mặt hàng quản lý tồn — bật "Quản lý tồn kho" cho sản phẩm/vật tư trước.' : 'No stock-tracked items yet.'}</div>}
          {!!its.length && buyCon && unmatched.length > 0 && <div className="subtle" style={{ fontSize:'var(--fs-sm)', color:'var(--warning)' }}>{lang === 'vi'
            ? unmatched.length + ' mặt hàng trong hợp đồng chưa khớp mặt hàng quản lý tồn nên không tự thêm: ' + unmatched.map(u => u.name).join('; ') + '. Kiểm tra tên trong Danh mục & bật "Quản lý tồn kho" cho sản phẩm tương ứng.'
            : unmatched.length + ' contract item(s) not matched to a stock-tracked product: ' + unmatched.map(u => u.name).join('; ') + '.'}</div>}
        </div>
        <Field label={lang === 'vi' ? 'Ghi chú' : 'Note'}><input className="input" value={note} onChange={e => setNote(e.target.value)} placeholder={lang === 'vi' ? 'Số chứng từ, nhà cung cấp…' : 'Ref, vendor…'} /></Field>
      </div>
    </Modal>
  );
}

/* Điều chỉnh tồn 1 mặt hàng (kiểm kê) */
function StockAdjustModal({ itemCode, onClose }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const it = p.products.find(x => x.code === itemCode) || {};
  const cur = stockQtyMap(p.moves)[itemCode] || 0;
  const [target, setTarget] = React.useState(String(cur));
  const [note, setNote] = React.useState('');
  const diff = (+target || 0) - cur;
  const apply = () => {
    if (diff === 0) { onClose(); return; }
    p.addAdjust(itemCode, diff, note.trim() || (lang === 'vi' ? 'Kiểm kê / điều chỉnh' : 'Stock-take'));
    toast(lang === 'vi' ? 'Đã điều chỉnh tồn' : 'Stock adjusted', 'success');
    onClose();
  };
  return (
    <Modal title={(lang === 'vi' ? 'Điều chỉnh tồn · ' : 'Adjust · ') + (it.name || itemCode)} width={460} onClose={onClose}
      footer={<><button className="btn" onClick={onClose}>{t('c.cancel')}</button><button className="btn btn-primary" onClick={apply}>{t('c.save')}</button></>}>
      <div className="col g16">
        <div className="det-row"><span className="dk">{lang === 'vi' ? 'Tồn hiện tại' : 'Current'}</span><span className="dv mono" style={{ fontWeight:600 }}>{cur} {it.unit}</span></div>
        <Field label={lang === 'vi' ? 'Tồn thực tế (sau kiểm kê)' : 'Actual on hand'}><input className="input" type="number" autoFocus value={target} onChange={e => setTarget(e.target.value)} style={{ width:140 }} /></Field>
        {diff !== 0 && <div className="flex g8 items-center" style={{ fontSize:'var(--fs-base)' }}><span className="subtle">{lang === 'vi' ? 'Chênh lệch:' : 'Diff:'}</span><span className="mono" style={{ fontWeight:700, color:diff > 0 ? 'var(--success)' : 'var(--error)' }}>{diff > 0 ? '+' : ''}{diff}</span></div>}
        <Field label={lang === 'vi' ? 'Lý do' : 'Reason'}><input className="input" value={note} onChange={e => setNote(e.target.value)} placeholder={lang === 'vi' ? 'Kiểm kê, hỏng, mất…' : 'Stock-take, damage…'} /></Field>
      </div>
    </Modal>
  );
}

/* Dashboard widget */
function InventoryWidget(){
  const p = usePortal();
  const { t, lang } = useLang();
  const qm = stockQtyMap(p.moves);
  const its = trackedItems(p.products);
  const low = its.filter(it => stockStatus(qm[it.code] || 0, it.minStock) !== 'ok');
  return (
    <WidgetShell wid="inventory" icon="bi-box-seam" iconColor={INV_APP.color} title={lang === 'vi' ? 'Kho' : 'Inventory'}
      onViewAll={() => p.openApp(INV_APP)} onNew={() => p.quick('inventory')} newLabel={lang === 'vi' ? 'Nhập kho' : 'Receive'} newIcon="bi-box-arrow-in-down">
        <div className="flex g16" style={{ marginBottom:8 }}>
          <div><div className="k-val" style={{ fontSize:'var(--fs-2xl)', fontWeight:700 }}>{its.length}</div><div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'mặt hàng' : 'items'}</div></div>
          <div><div className="k-val" style={{ fontSize:'var(--fs-2xl)', fontWeight:700, color:low.length ? 'var(--warning)' : 'var(--success)' }}>{low.length}</div><div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'dưới định mức' : 'low'}</div></div>
        </div>
        {low.slice(0, 3).map(it => { const q = qm[it.code] || 0; return (
          <div key={it.code} className="list-row" style={{ cursor:'pointer' }} onClick={() => p.navigate({ name:'module', module:'inventory', sub:'alerts' })}>
            <span className="grow truncate" style={{ fontSize:'var(--fs-base)' }}>{it.name}</span><span className="mono" style={{ fontWeight:600 }}>{q}/{it.minStock}</span><StockStatusBadge qty={q} min={it.minStock} />
          </div>
        ); })}
    </WidgetShell>
  );
}

/* ===================== PHIẾU XUẤT KHO (P2) ===================== */
function IssueStatusBadge({ status }){ const { lang } = useLang(); const s = ISSUE_STATUS[status] || ISSUE_STATUS.draft; return <span className={'badge ' + s.cls}>{s[lang]}</span>; }
function genIssueCode(list){
  const yr = TODAY.slice(0, 4);
  const max = (list || []).reduce((m, x) => { const mm = (x.code || '').match(new RegExp('PX-' + yr + '-(\\d+)')); return mm ? Math.max(m, +mm[1]) : m; }, 0);
  return 'PX-' + yr + '-' + String(max + 1).padStart(3, '0');
}

function IssueList({ go }){
  const p = usePortal();
  const { t, lang } = useLang();
  const [st, setSt] = React.useState('all');
  const srt = useTableSort();
  const list = srt.sortRows((p.issues || []).filter(i => st === 'all' || i.status === st).sort((a, b) => a.code < b.code ? 1 : -1),
    { code:i => i.code, contract:i => i.contract, cust:i => { const c = (window.CustomerStore && CustomerStore.byCode(i.customer)) || {}; return c.shortName || c.name || i.customer; },
      lines:i => (i.lines || []).length, created:i => i.createdDate, status:i => (ISSUE_STATUS[i.status] || {})[lang] || i.status });
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{lang === 'vi' ? 'Phiếu xuất kho' : 'Stock issues'}</h1><div className="sub">{list.length} {lang === 'vi' ? 'phiếu' : 'issues'}</div></div>
        <button className="btn btn-primary" onClick={() => p.openNewIssue(null)}><i className="bi bi-plus-lg"></i>{lang === 'vi' ? 'Tạo phiếu xuất' : 'New issue'}</button></div>
      <div className="toolbar"><div className="seg">
        {['all', 'draft', 'l1', 'l2', 'approved', 'issued', 'cancelled'].map(s => <button key={s} className={st === s ? 'is-active' : ''} onClick={() => setSt(s)}>{s === 'all' ? t('c.all') : ISSUE_STATUS[s][lang]}</button>)}
      </div></div>
      <div className="card"><div style={{ overflow:'auto' }}>
        <table className="tbl">
          <thead><tr><ThSort k="code" sort={srt}>{lang === 'vi' ? 'Mã phiếu' : 'Code'}</ThSort><ThSort k="contract" sort={srt}>{lang === 'vi' ? 'Hợp đồng' : 'Contract'}</ThSort><ThSort k="cust" sort={srt}>{lang === 'vi' ? 'Khách hàng' : 'Customer'}</ThSort><ThSort k="lines" sort={srt} className="num">{lang === 'vi' ? 'Số dòng' : 'Lines'}</ThSort><ThSort k="created" sort={srt}>{lang === 'vi' ? 'Ngày tạo' : 'Created'}</ThSort><ThSort k="status" sort={srt}>{p.t('c.status')}</ThSort></tr></thead>
          <tbody>
            {list.map(i => { const cust = (window.CustomerStore && CustomerStore.byCode(i.customer)) || {}; return (
              <tr key={i.code} onClick={() => go('issue', { id:i.code })}>
                <td><span className="mono" style={{ fontWeight:600, color:'var(--accent)' }}>{i.code}</span></td>
                <td>{i.contract ? <span className="mono" style={{ color:'var(--primary)', fontWeight:600, fontSize:'var(--fs-base)' }}>{i.contract}</span> : <span className="subtle">—</span>}</td>
                <td className="muted">{cust.shortName || cust.name || i.customer || '—'}</td>
                <td className="num">{(i.lines || []).length}</td>
                <td className="mono" style={{ fontSize:'var(--fs-base)' }}>{fmtDate(i.createdDate)}</td>
                <td><IssueStatusBadge status={i.status} /></td>
              </tr>
            ); })}
          </tbody>
        </table>
        {!list.length && <EmptyState text={lang === 'vi' ? 'Chưa có phiếu xuất. Tạo từ đây hoặc từ chi tiết hợp đồng bán.' : 'No issues yet.'} />}
      </div></div>
    </div>
  );
}

function IssueDetail({ code, go }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const iss = (p.issues || []).find(x => x.code === code);
  if (!iss) return <div className="page"><EmptyState text={lang === 'vi' ? 'Không tìm thấy phiếu' : 'Not found'} /></div>;
  const lv = issueLevels(p);
  const st = iss.status;
  const qm = stockQtyMap(p.moves);
  const cust = (window.CustomerStore && CustomerStore.byCode(iss.customer)) || {};
  const canApprove = canApproveIssue(iss, p);
  const submit = () => { p.patchIssue(iss.code, { ...iss, status:'l1' }); toast(lang === 'vi' ? 'Đã gửi duyệt' : 'Submitted', 'success'); };
  const approve = () => {
    const lvl = st === 'l1' ? 1 : 2;
    const appr = [...(iss.approvals || []), { level:lvl, by:p.me.name, at:TODAY, action:'approve' }];
    const next = (st === 'l1' && lv === 2) ? 'l2' : 'approved';
    p.patchIssue(iss.code, { ...iss, status:next, approvals:appr });
    toast(lang === 'vi' ? 'Đã duyệt' : 'Approved', 'success');
  };
  const reject = () => { p.patchIssue(iss.code, { ...iss, status:'draft', approvals:[...(iss.approvals || []), { level:st === 'l1' ? 1 : 2, by:p.me.name, at:TODAY, action:'reject' }] }); toast(lang === 'vi' ? 'Đã trả về nháp' : 'Rejected', 'info'); };
  const cancel = async () => { if (await confirm({ message:lang === 'vi' ? 'Hủy phiếu xuất này?' : 'Cancel this issue?', confirmLabel:lang === 'vi' ? 'Hủy phiếu' : 'Cancel' })) { p.patchIssue(iss.code, { ...iss, status:'cancelled' }); toast(lang === 'vi' ? 'Đã hủy phiếu' : 'Cancelled'); } };
  const commit = () => {
    const short = (iss.lines || []).filter(l => (qm[l.item] || 0) < +l.qty);
    const doIt = () => { p.commitIssue(iss); toast(lang === 'vi' ? 'Đã xuất kho — tồn đã trừ' : 'Issued — stock deducted', 'success'); };
    if (short.length) { confirm({ message:(lang === 'vi' ? 'Có ' + short.length + ' mặt hàng tồn không đủ — vẫn xuất (tồn sẽ âm)?' : short.length + ' items short — issue anyway?'), confirmLabel:lang === 'vi' ? 'Vẫn xuất' : 'Issue' }).then(ok => { if (ok) doIt(); }); }
    else doIt();
  };
  const actIcon = { approve:'bi-check-circle', reject:'bi-x-circle' };
  const printIss = () => { if (!printDocument(stockIssueHTML(iss, p))) toast(lang === 'vi' ? 'Trình duyệt chặn cửa sổ in — hãy cho phép pop-up' : 'Pop-up blocked', 'error'); };
  return (
    <div className="page pagefade">
      <div className="page-head">
        <div className="flex g12 items-center">
          <button className="btn btn-ghost btn-icon" onClick={() => go('issues')}><i className="bi bi-arrow-left"></i></button>
          <div><h1 style={{ fontSize:'var(--fs-2xl)' }}>{iss.code}</h1>
            <div className="sub flex g8 items-center wrap"><IssueStatusBadge status={iss.status} />{iss.contract && <> · <span className="mono link" style={{ color:'var(--primary)', cursor:'pointer', fontWeight:600 }} onClick={() => p.navigate({ name:'module', module:p.contracts.some(c => c.code === iss.contract) ? 'contracts' : 'quotes', sub:'detail', id:iss.contract })}>{iss.contract}</span></>}{iss.customer && <> · {cust.shortName || cust.name || iss.customer}</>}</div></div>
        </div>
        <div className="flex g8">
          {st === 'draft' && lv >= 1 && <button className="btn btn-primary" onClick={submit}><i className="bi bi-send"></i>{lang === 'vi' ? 'Gửi duyệt' : 'Submit'}</button>}
          {((st === 'draft' && lv === 0) || st === 'approved') && <button className="btn btn-primary" onClick={commit}><i className="bi bi-box-arrow-up"></i>{lang === 'vi' ? 'Xuất kho' : 'Issue stock'}</button>}
          {(st === 'l1' || st === 'l2') && canApprove && <><button className="btn btn-primary" onClick={approve}><i className="bi bi-check-circle"></i>{lang === 'vi' ? 'Duyệt' : 'Approve'}</button><button className="btn" onClick={reject}><i className="bi bi-x-circle"></i>{lang === 'vi' ? 'Từ chối' : 'Reject'}</button></>}
          {(st === 'l1' || st === 'l2') && !canApprove && <span className="subtle" style={{ fontSize:'var(--fs-base)' }}><i className="bi bi-hourglass-split"></i> {lang === 'vi' ? 'Chờ ' + (st === 'l1' ? 'Quản lý' : 'Giám đốc') + ' duyệt' : 'Awaiting approval'}</span>}
          <button className="btn btn-icon" title={lang === 'vi' ? 'In phiếu xuất' : 'Print'} onClick={printIss}><i className="bi bi-printer"></i></button>
          {!['issued', 'cancelled'].includes(st) && <button className="btn btn-icon" title={lang === 'vi' ? 'Hủy phiếu' : 'Cancel'} onClick={cancel}><i className="bi bi-trash"></i></button>}
        </div>
      </div>

      <div className="card" style={{ marginBottom:14 }}>
        <div className="card-head"><h3>{lang === 'vi' ? 'Mặt hàng xuất' : 'Items'}</h3>{st === 'issued' && iss.issuedDate && <span className="badge badge-success">{lang === 'vi' ? 'Xuất ngày ' : 'Issued '}{fmtDate(iss.issuedDate)}</span>}</div>
        <div style={{ overflow:'auto' }}>
          <table className="tbl">
            <thead><tr><th>{lang === 'vi' ? 'Mã' : 'Code'}</th><th>{lang === 'vi' ? 'Mặt hàng' : 'Item'}</th><th>{lang === 'vi' ? 'ĐVT' : 'Unit'}</th><th className="num">{lang === 'vi' ? 'SL xuất' : 'Qty'}</th><th className="num">{lang === 'vi' ? 'Tồn hiện tại' : 'On hand'}</th><th></th></tr></thead>
            <tbody>
              {(iss.lines || []).map((l, i) => { const cur = qm[l.item] || 0; const short = cur < +l.qty && st !== 'issued'; return (
                <tr key={i} style={{ cursor:'default' }}>
                  <td><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{l.item}</span></td>
                  <td style={{ fontWeight:500 }}>{l.name}</td>
                  <td className="muted">{l.unit}</td>
                  <td className="num mono" style={{ fontWeight:600 }}>{l.qty}</td>
                  <td className="num mono" style={{ color:short ? 'var(--error)' : 'var(--text-muted)' }}>{cur}{short && <i className="bi bi-exclamation-triangle" style={{ marginLeft:4 }}></i>}</td>
                  <td>{short && <span className="badge badge-error">{lang === 'vi' ? 'Thiếu' : 'Short'}</span>}</td>
                </tr>
              ); })}
            </tbody>
          </table>
        </div>
        {iss.note && <div className="card-body subtle" style={{ fontSize:'var(--fs-base)' }}><i className="bi bi-sticky" style={{ marginRight:6 }}></i>{iss.note}</div>}
      </div>

      {(iss.approvals || []).length > 0 && (
        <div className="card"><div className="card-head"><h3 style={{ fontSize:'var(--fs-md)' }}>{lang === 'vi' ? 'Lịch sử duyệt' : 'Approval log'}</h3></div><div className="card-body">
          {[...iss.approvals].reverse().map((a, i) => (
            <div key={i} className="list-row">
              <span className={'badge ' + (a.action === 'reject' ? 'badge-error' : 'badge-success')}><i className={'bi ' + (actIcon[a.action] || 'bi-dot')} style={{ marginRight:3 }}></i>{a.action === 'reject' ? (lang === 'vi' ? 'Từ chối' : 'Rejected') : (lang === 'vi' ? 'Duyệt' : 'Approved')} {lang === 'vi' ? 'cấp ' : 'L'}{a.level}</span>
              <span className="grow">{a.by}</span><span className="subtle mono" style={{ fontSize:'var(--fs-sm)' }}>{fmtDate(a.at)}</span>
            </div>
          ))}
        </div></div>
      )}
    </div>
  );
}

function NewIssueModal({ presetContract, onClose }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const its = trackedItems(p.products);
  const code = genIssueCode(p.issues);
  // Preset là HĐ hoặc BÁO GIÁ chốt-không-HĐ (cùng shape items/customer) — phiếu xuất gắn theo mã đó.
  const con = presetContract ? (p.contracts.find(c => c.code === presetContract) || (p.quotes || []).find(x => x.code === presetContract)) : null;
  const conIsQuote = !!(con && !p.contracts.some(c => c.code === con.code));
  // prefill từ hợp đồng: các hạng mục khớp mặt hàng có quản lý tồn.
  const presetLines = con ? (con.items || []).map(it => {
    const m = its.find(pr => (window.sdeburr && sdeburr(pr.name) === sdeburr(it.name)) || (window.productMatches && productMatches(pr, it.name)) || String(pr.name).toLowerCase() === String(it.name).toLowerCase());
    return m ? { item:m.code, qty:it.qty } : null;
  }).filter(Boolean) : [];
  const [lines, setLines] = React.useState(presetLines.length ? presetLines : [{ item:its[0] ? its[0].code : '', qty:'' }]);
  const [note, setNote] = React.useState(con ? (conIsQuote ? (lang === 'vi' ? 'Xuất theo báo giá ' + con.code : 'For quote ' + con.code) : (lang === 'vi' ? 'Xuất theo hợp đồng ' + con.code : 'For contract ' + con.code)) : '');
  const setLine = (i, k, v) => setLines(s => s.map((l, j) => j === i ? { ...l, [k]:v } : l));
  const create = () => {
    const valid = lines.filter(l => l.item && +l.qty > 0).map(l => { const it = p.products.find(x => x.code === l.item) || {}; return { item:l.item, name:it.name, unit:it.unit, qty:+l.qty }; });
    if (!valid.length) { toast(lang === 'vi' ? 'Thêm ít nhất 1 dòng hợp lệ' : 'Add a line', 'error'); return; }
    const rec = { code, contract:con ? con.code : null, customer:con ? con.customer : null, status:'draft', lines:valid, approvals:[], note:note.trim(), createdBy:p.me.name, createdDate:TODAY, issuedDate:null };
    p.addIssue(rec);
    toast((lang === 'vi' ? 'Đã tạo phiếu xuất ' : 'Created ') + code, 'success');
    onClose();
    p.navigate({ name:'module', module:'inventory', sub:'issue', id:code });
  };
  return (
    <Modal title={(lang === 'vi' ? 'Tạo phiếu xuất · ' : 'New issue · ') + code} width={640} onClose={onClose}
      footer={<><button className="btn" onClick={onClose}>{t('c.cancel')}</button><button className="btn btn-primary" onClick={create}>{t('c.create')}</button></>}>
      <div className="col g16">
        {con && <div className="flex g8 items-center" style={{ fontSize:'var(--fs-base)' }}><span className="subtle">{conIsQuote ? (lang === 'vi' ? 'Báo giá:' : 'Quote:') : (lang === 'vi' ? 'Hợp đồng:' : 'Contract:')}</span><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{con.code}</span></div>}
        <div className="col g8">
          <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'Mặt hàng xuất' : 'Items to issue'}</div>
          {lines.map((l, i) => { const cur = stockQtyMap(p.moves)[l.item] || 0; const short = l.item && +l.qty > cur; return (
            <div key={i} className="flex g8 items-center">
              <select className="select" style={{ flex:1 }} value={l.item} onChange={e => setLine(i, 'item', e.target.value)}>
                {its.map(it => <option key={it.code} value={it.code}>{it.code} · {it.name} ({(stockQtyMap(p.moves)[it.code] || 0)})</option>)}
              </select>
              <input className="input" type="number" min="0" style={{ width:100, borderColor:short ? 'var(--error)' : undefined }} value={l.qty} onChange={e => setLine(i, 'qty', e.target.value)} placeholder={lang === 'vi' ? 'SL' : 'Qty'} />
              <span className="subtle mono" style={{ width:60, fontSize:'var(--fs-sm)', color:short ? 'var(--error)' : undefined }}>/{cur}</span>
              <button className="iconbtn" onClick={() => setLines(s => s.filter((_, j) => j !== i))} title={t('c.delete')}><i className="bi bi-x-lg"></i></button>
            </div>
          ); })}
          <button className="btn btn-subtle btn-sm" style={{ alignSelf:'flex-start' }} onClick={() => setLines(s => [...s, { item:its[0] ? its[0].code : '', qty:'' }])}><i className="bi bi-plus-lg"></i>{lang === 'vi' ? 'Thêm dòng' : 'Add line'}</button>
          {!its.length && <div className="subtle" style={{ fontSize:'var(--fs-sm)', color:'var(--warning)' }}>{lang === 'vi' ? 'Chưa có mặt hàng quản lý tồn.' : 'No stock-tracked items.'}</div>}
        </div>
        <Field label={lang === 'vi' ? 'Ghi chú' : 'Note'}><input className="input" value={note} onChange={e => setNote(e.target.value)} /></Field>
      </div>
    </Modal>
  );
}

Object.assign(window, { InventoryModule, InventoryWidget, NewReceiptModal, StockAdjustModal, NewIssueModal, IssueStatusBadge, genIssueCode, stockQtyMap, stockStatus, stockOfName, trackedItems, issueLevels, ITEM_TYPES, STOCK_STATUS, ISSUE_STATUS });
