/* ============================================================
   pages_flow.jsx — 测算任务下发 / 成本测算单 / 审批中心 / 审计日志
   对齐：新都《附表1-向车间收集基础数据》 + 科伦《物料价格》《测算成本》
   ============================================================ */
const { Ico, Btn, Tag, Chip, Card, CardHead, Switch, Avatar, Drawer, Modal, Empty, Bar, Ring, StackBar, Spark, RankBars, useApp, FlowStepper, MiniSteps, Timeline, SrcDot, DiffTable, CompletenessPanel } = window;

const { useState, useMemo, useEffect, Fragment } = React;
const yuanF = (v) => (v == null ? '—' : Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }));
const numF = (v) => (v == null ? '—' : Number(v).toLocaleString('zh-CN'));

const STATUS_META = {
  pending: { label: '流转中', kind: 'primary' },
  reviewing: { label: '待审批', kind: 'warn' },
  approved: { label: '已通过 · 已锁定', kind: 'ok' },
  rejected: { label: '已驳回', kind: 'danger' },
};

/* ================================================================
   1. 测算任务下发（财务）
   ================================================================ */
function TasksPage() {
  const app = useApp();
  const { decls, toast, go } = app;
  const [open, setOpen] = useState(false);
  const [draft, setDraft] = useState({
    title: '马来酸阿伐曲波帕片 Q4 成本测算',
    period: '2026 Q4',
    product: 'XN10100029',
    modes: ['M1', 'M2', 'M3', 'M4', 'M5', 'M6'],
    needProd: true, needSupply: true,
    due: '2026-10-08',
    priority: 'normal',
    note: '本期新增扩批生产模式3-6。车间需按附表1口径填报（批理论产量/成品率/抽留样/生产时长/水电气汽/定员明细），供应部需确认物料含税单价与临测价。',
  });

  const submit = () => {
    setOpen(false);
    toast('已下发「' + draft.title + '」至 ' + [draft.needProd && '生产车间', draft.needSupply && '供应部'].filter(Boolean).join(' / '), 'ok');
    go('declarations');
  };

  const active = decls.filter((d) => d.status !== 'approved');

  return (
    <div className="scroll" data-screen-label="04 任务下发">
      <div className="sec-title">
        <h2>测算任务下发</h2>
        <span className="sub">财务发起成本数据收集任务，按「财务发起 → 生产车间 → 供应部 → 财务审批」流转</span>
        <div className="right">
          <Btn size="sm" kind="primary" icon="send" onClick={() => setOpen(true)}>发起测算任务</Btn>
        </div>
      </div>

      <div className="grid g3 mb12">
        {[
          { k: '进行中任务', v: active.length, s: active.length + ' 张测算单流转中', i: 'send', c: 'var(--primary)' },
          { k: '已逾期 / 临近截止', v: 1, s: 'SB-2026-0151 截止 09-30', i: 'clock', c: 'var(--warn)' },
          { k: '本期已锁定归档', v: 1, s: 'Q3 基线已审核锁定 06-27', i: 'checkc', c: 'var(--ok)' },
        ].map((x) => (
          <Card key={x.k}><div className="kpi" style={{ padding: '14px 16px' }}>
            <div className="k-label">{x.k}</div>
            <div className="k-value" style={{ fontSize: 26, color: x.c }}>{x.v}<small>张</small></div>
            <div className="k-foot"><span className="muted">{x.s}</span></div>
          </div></Card>
        ))}
      </div>

      <Card>
        <CardHead title="进行中的测算任务" sub={'共 ' + active.length + ' 张'}
          right={<Btn size="sm" onClick={() => go('declarations')}>全部测算单 →</Btn>} />
        <div className="tbl-wrap">
          <table className="tbl">
            <thead>
              <tr>
                <th style={{ width: 148 }}>单号</th>
                <th>任务名称</th>
                <th style={{ width: 130 }}>覆盖产品</th>
                <th style={{ width: 190 }}>当前节点</th>
                <th style={{ width: 110 }} className="r">截止</th>
                <th style={{ width: 96 }}>优先级</th>
                <th style={{ width: 96 }} className="r">操作</th>
              </tr>
            </thead>
            <tbody>
              {active.map((d) => (
                <tr key={d.id} className="clickable" onClick={() => go('declarations', { open: d.id })}>
                  <td className="num" style={{ fontSize: 12.5 }}>{d.id}</td>
                  <td>
                    <div className="primary-cell">{d.title}</div>
                    <div className="sub-cell">{d.period} · 发起人 {d.creator}</div>
                  </td>
                  <td>
                    <div className="primary-cell tiny">{window.PRODUCT.name}</div>
                    <div className="sub-cell num">{window.PRODUCT.id} · {d.modes.length} 个工况</div>
                  </td>
                  <td>
                    <div className="row" style={{ gap: 8 }}>
                      <MiniSteps decl={d} />
                      <span className="tiny" style={{ color: d.status === 'rejected' ? 'var(--danger)' : 'var(--ink-2)' }}>
                        {d.status === 'rejected' ? '已驳回 · 退回' + window.STEPS[Math.max(d.step, 1)].name : window.STEPS[Math.min(d.step, 3)].name}
                      </span>
                    </div>
                  </td>
                  <td className="r"><span className="num tiny" style={{ color: d.due === '2026-09-30' ? 'var(--warn)' : 'var(--ink-3)' }}>{d.due}</span></td>
                  <td>{d.priority === 'high' ? <Tag kind="warn">加急</Tag> : <Tag>常规</Tag>}</td>
                  <td className="r"><button className="btn sm ghost" onClick={(e) => { e.stopPropagation(); go('declarations', { open: d.id }); }}>查看</button></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </Card>

      {open && (
        <Modal wide title="发起成本测算任务" onClose={() => setOpen(false)}
          footer={<Fragment>
            <span className="tiny muted">下发后按「财务发起 → 生产车间 → 供应部 → 财务审批」自动流转</span>
            <div className="spacer" />
            <Btn size="sm" onClick={() => setOpen(false)}>取消</Btn>
            <Btn size="sm" kind="primary" icon="send" onClick={submit}>确认下发</Btn>
          </Fragment>}>
          <div className="col" style={{ gap: 14 }}>
            <div className="grid g2">
              <div className="field">
                <label>任务名称</label>
                <input className="input" value={draft.title} onChange={(e) => setDraft({ ...draft, title: e.target.value })} />
              </div>
              <div className="field">
                <label>核算期次</label>
                <select className="select" value={draft.period} onChange={(e) => setDraft({ ...draft, period: e.target.value })}>
                  {['2026 Q4', '2026 Q3', '2026 年度'].map((p) => <option key={p}>{p}</option>)}
                </select>
              </div>
            </div>

            <div className="field">
              <label>测算产品</label>
              <div className="row" style={{ gap: 10, border: '1px solid var(--line)', borderRadius: 8, padding: '10px 12px' }}>
                <Tag kind="primary">{window.PRODUCT.id}</Tag>
                <span style={{ fontSize: 12.5, fontWeight: 500 }}>{window.PRODUCT.name} {window.PRODUCT.spec}</span>
                <span className="tiny muted">{window.PRODUCT.cat} · {window.PRODUCT.packSpec} · {window.PRODUCT.wsName} · 最小单位系数 {window.PRODUCT.minUnitFactor}</span>
              </div>
            </div>

            <div className="field">
              <label>测算工况 <span className="hint">对齐附表1生产模式，可多选</span></label>
              <div className="row wrap" style={{ gap: 6 }}>
                {window.MODES.map((m) => (
                  <Chip key={m.key} on={draft.modes.includes(m.key)}
                    onClick={() => setDraft({ ...draft, modes: draft.modes.includes(m.key) ? draft.modes.filter((x) => x !== m.key) : draft.modes.concat([m.key]) })}>
                    {m.name} · {m.desc}
                  </Chip>
                ))}
              </div>
            </div>

            <div className="field">
              <label>参与部门与填报内容</label>
              <div className="grid g2">
                {[
                  { k: 'needProd', n: '生产车间', who: '生产车间', sig: '附表1：批理论产量 / 成品率 / 抽留样 / 生产时长 / 水电气汽 / 定员明细 / 降本措施' },
                  { k: 'needSupply', n: '供应部', who: '供应部', sig: '物料价格：含税采购单价确认 / 临测价填写 / 税率复核' },
                ].map((x) => (
                  <div key={x.k} className="row" style={{ gap: 10, border: '1px solid var(--line)', borderRadius: 8, padding: '10px 12px' }}>
                    <Switch on={draft[x.k]} onChange={(v) => setDraft({ ...draft, [x.k]: v })} />
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontSize: 12.5, fontWeight: 500 }}>{x.n} · {x.who}</div>
                      <div className="tiny muted">{x.sig}</div>
                    </div>
                  </div>
                ))}
              </div>
            </div>

            <div className="grid g2">
              <div className="field">
                <label>填报截止</label>
                <input className="input" type="date" value={draft.due} onChange={(e) => setDraft({ ...draft, due: e.target.value })} />
              </div>
              <div className="field">
                <label>优先级</label>
                <div className="seg" style={{ width: 'fit-content' }}>
                  {[['normal', '常规'], ['high', '加急']].map(([k, l]) => (
                    <button key={k} className={draft.priority === k ? 'on' : ''} onClick={() => setDraft({ ...draft, priority: k })}>{l}</button>
                  ))}
                </div>
              </div>
            </div>

            <div className="field">
              <label>任务说明 <span className="hint">将作为首条留痕写入流程日志</span></label>
              <textarea className="textarea" rows={3} value={draft.note} onChange={(e) => setDraft({ ...draft, note: e.target.value })} />
            </div>

            <div className="notice flat">
              <span className="n-ico"><Ico name="info" size={14} /></span>
              <div>任务下发后每次填报与审批都会自动生成审计记录（操作人 / 时间 / 变更前后值 / 意见），记录不可编辑或删除。财务审批通过后，成本明细将审核锁定并记录审核时间。</div>
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}

/* ================================================================
   2. 成本测算单
   ================================================================ */
function DeclarationsPage() {
  const app = useApp();
  const { decls, role, nav, go } = app;
  const [tab, setTab] = useState('todo');
  const [openId, setOpenId] = useState(nav.params && nav.params.open || null);
  const [q, setQ] = useState('');

  useEffect(() => { if (nav.params && nav.params.open) setOpenId(nav.params.open); }, [nav.params]);

  const buckets = {
    todo: decls.filter((d) => (role === 'finance' ? d.step === 3 && d.status === 'reviewing' : d.step === (role === 'prod' ? 1 : 2) && d.status !== 'approved')),
    doing: decls.filter((d) => d.status !== 'approved' && d.status !== 'rejected' && !(d.step === (role === 'prod' ? 1 : 2))),
    done: decls.filter((d) => d.status === 'approved'),
    rejected: decls.filter((d) => d.status === 'rejected'),
  };
  const list = (buckets[tab] || []).filter((d) => !q || (d.id + d.title).toLowerCase().includes(q.toLowerCase()));

  return (
    <div className="scroll tight" data-screen-label="05 成本测算单">
      <div style={{ padding: '18px 18px 0' }}>
        <div className="sec-title">
          <h2>成本测算单</h2>
          <span className="sub">财务发起 → 生产车间 / 供应部填报 → 财务审批（审核锁定）· 每次提交均留痕</span>
          <div className="right">
            <Btn size="sm" icon="download">导出列表</Btn>
          </div>
        </div>
      </div>
      <div style={{ padding: '0 18px 18px' }}>
        <Card>
          <div className="tbl-toolbar">
            <div className="seg">
              {[['todo', role === 'finance' ? '待我审批' : '待我填报'], ['doing', '流转中'], ['rejected', '已驳回'], ['done', '已通过']].map(([k, l]) => (
                <button key={k} className={tab === k ? 'on' : ''} onClick={() => setTab(k)}>{l} <span className="n">{(buckets[k] || []).length}</span></button>
              ))}
            </div>
            <div className="spacer" />
            <div style={{ position: 'relative', width: 210 }}>
              <Ico name="search" size={14} style={{ position: 'absolute', left: 9, top: 9, color: 'var(--ink-4)' }} />
              <input className="input" style={{ paddingLeft: 28 }} placeholder="搜索单号 / 任务名" value={q} onChange={(e) => setQ(e.target.value)} />
            </div>
          </div>
          <div className="tbl-wrap">
            <table className="tbl">
              <thead>
                <tr>
                  <th style={{ width: 148 }}>单号</th>
                  <th>任务名称</th>
                  <th style={{ width: 128 }}>产品 / 工况</th>
                  <th style={{ width: 176 }}>当前节点</th>
                  <th style={{ width: 108 }}>状态</th>
                  <th style={{ width: 106 }} className="r">提交时间</th>
                  <th style={{ width: 88 }} className="r">操作</th>
                </tr>
              </thead>
              <tbody>
                {list.map((d) => {
                  const meta = STATUS_META[d.status];
                  const last = d.timeline[d.timeline.length - 1];
                  const needMe = (role === 'prod' && d.step === 1 && d.status !== 'approved') || (role === 'supply' && d.step === 2 && d.status !== 'approved') || (role === 'finance' && d.step === 3 && d.status === 'reviewing');
                  const pct = Math.round(d.modes.reduce((s, mk) => s + (d.progress[mk] || 0), 0) / (d.modes.length * 7) * 100);
                  return (
                    <tr key={d.id} className="clickable" onClick={() => setOpenId(d.id)}>
                      <td className="num" style={{ fontSize: 12.5 }}>
                        {d.id}
                        {needMe && <div style={{ marginTop: 3 }}><Tag kind="primary" dot>待我处理</Tag></div>}
                      </td>
                      <td>
                        <div className="primary-cell">{d.title}</div>
                        <div className="sub-cell">{d.period} · 发起人 {d.creator}</div>
                      </td>
                      <td>
                        <div className="primary-cell tiny">{window.PRODUCT.name}</div>
                        <div className="row wrap" style={{ gap: 4, marginTop: 4 }}>
                          {d.modes.map((mk) => <span className="tag" key={mk} style={{ height: 19, fontSize: 10.5 }}>{mk}</span>)}
                        </div>
                      </td>
                      <td>
                        <div className="row" style={{ gap: 8 }}>
                          <MiniSteps decl={d} />
                          <span className="tiny muted nowrap">
                            {d.status === 'rejected' ? <span style={{ color: 'var(--danger)' }}>退回 {window.STEPS[Math.max(d.step, 1)].name}</span> : window.STEPS[d.status === 'approved' ? 3 : d.step].name}
                          </span>
                        </div>
                      </td>
                      <td>
                        <Tag kind={meta.kind} dot>{meta.label}</Tag>
                        {d.lockedAt && <div className="tiny muted" style={{ marginTop: 3 }}>审核时间 {d.lockedAt}</div>}
                      </td>
                      <td className="r num tiny muted">
                        {last ? last.t.slice(5) : '—'}
                        <div className="tiny" style={{ color: pct === 100 ? 'var(--ok)' : 'var(--warn)' }}>数据 {pct}%</div>
                      </td>
                      <td className="r"><button className="btn sm ghost" onClick={(e) => { e.stopPropagation(); setOpenId(d.id); }}>{needMe ? '去处理' : '详情'}</button></td>
                    </tr>
                  );
                })}
                {list.length === 0 && <tr><td colSpan={7}><Empty icon="form" title="该分类下暂无测算单" desc={role === 'prod' ? '生产车间当前没有待填报的任务。' : '切换分类查看其他测算单。'} /></td></tr>}
              </tbody>
            </table>
          </div>
          <div className="tbl-foot">
            <span>共 {list.length} 张</span>
            <div className="spacer" />
            <span className="muted">审计记录总数：{app.audit.length} 条</span>
          </div>
        </Card>
      </div>

      {openId && <DeclarationDrawer id={openId} onClose={() => setOpenId(null)} />}
    </div>
  );
}

/* ---------- 表单初值（默认取工况主数据，已申报值优先） ---------- */
function buildVals(d) {
  const v = {};
  d.modes.forEach((mk) => {
    const m = window.modeById(mk);
    const saved = (d.payload.prod || {})[mk] || {};
    v[mk + '.batches'] = saved.batches != null ? saved.batches : m.batches;
    v[mk + '.hpd'] = saved.hpd != null ? saved.hpd : m.hpd;
    v[mk + '.saving'] = saved.saving != null ? saved.saving : m.saving;
    v[mk + '.savingDue'] = saved.savingDue != null ? saved.savingDue : m.savingDue;
    v[mk + '.equipAmount'] = saved.equipAmount != null ? saved.equipAmount : m.equip.amount;
    v[mk + '.equipName'] = saved.equipName != null ? saved.equipName : m.equip.name;
    ['semi', 'fin'].forEach((sk) => {
      const st = m.stages[sk];
      const ss = saved[sk] || {};
      ['batchQty', 'sample', 'sampleTimes', 'yield', 'hours', 'dailyLabor', 'water', 'elec', 'steam'].forEach((f) => {
        v[mk + '.' + sk + '.' + f] = ss[f] != null ? ss[f] : st[f];
      });
    });
  });
  window.BOM_LINES.forEach((b) => {
    const sp = (d.payload.supply || {})[b.id] || {};
    v['sup.' + b.id + '.price'] = sp.priceTax != null ? sp.priceTax : b.priceTax;
    v['sup.' + b.id + '.tent'] = sp.tentativePriceTax != null ? sp.tentativePriceTax : (b.tentativePriceTax != null ? b.tentativePriceTax : '');
    v['sup.' + b.id + '.ok'] = sp.confirmed != null ? sp.confirmed : !!b.confirmed;
  });
  return v;
}

/* 按当前表单值构造工况副本并试算（车间实时预览）
   单价与物料取财务在页面上编辑后的最新值，保证预览与核算结果一致 */
function trialMode(mk, vals, bomLines, factors) {
  const m = window.modeById(mk);
  const tm = JSON.parse(JSON.stringify(m));
  tm.batches = Number(vals[mk + '.batches']);
  tm.hpd = Number(vals[mk + '.hpd']);
  ['semi', 'fin'].forEach((sk) => {
    ['batchQty', 'sample', 'sampleTimes', 'yield', 'hours', 'dailyLabor', 'water', 'elec', 'steam'].forEach((f) => {
      const raw = vals[mk + '.' + sk + '.' + f];
      if (raw === '' || raw == null) { if (f === 'sample') tm.stages[sk][f] = null; return; }
      const n = Number(raw);
      if (!isNaN(n)) tm.stages[sk][f] = n;
    });
  });
  return window.computeMode(tm, null, bomLines, factors);
}

/* ---------- 变更集 ---------- */
const STAGE_LABELS = {
  batchQty: ['批理论产量', ''], sample: ['标准单批抽留样', '盒'], sampleTimes: ['标准抽样次数', '次'],
  yield: ['成品率', '%'], hours: ['生产时长', 'h'], dailyLabor: ['日总人工', '小时'],
  water: ['单批次水', '吨'], elec: ['单批次电', '度'], steam: ['单批次汽', '吨'],
};

function computeProdChanges(d, vals) {
  const out = [];
  d.modes.forEach((mk) => {
    const m = window.modeById(mk);
    const saved = (d.payload.prod || {})[mk] || {};
    const push = (label, from, to) => out.push({ product: mk, k: label, from: from, to: to });
    [['batches', '连续生产批次', '批'], ['hpd', '每日生产时长', 'h/天']].forEach(([f, l, u]) => {
      const was = saved[f] != null ? saved[f] : m[f];
      const now = vals[mk + '.' + f];
      if (now != null && String(was) !== String(now)) push(l, was + ' ' + u, now + ' ' + u);
    });
    ['semi', 'fin'].forEach((sk) => {
      const st = m.stages[sk];
      const ss = saved[sk] || {};
      Object.keys(STAGE_LABELS).forEach((f) => {
        const was = ss[f] != null ? ss[f] : st[f];
        const now = vals[mk + '.' + sk + '.' + f];
        const disp = (x) => f === 'yield' ? (x == null ? '—' : Math.round(Number(x) * 1000) / 10 + ' %') : (x == null || x === '' ? '—' : STAGE_LABELS[f][1] ? x + ' ' + STAGE_LABELS[f][1] : String(x));
        if (now != null && now !== '' && String(was) !== String(now)) push((sk === 'semi' ? '半成品' : '产成品') + ' · ' + STAGE_LABELS[f][0], disp(was), disp(now));
      });
    });
    [['saving', '生产条件/降本措施', ''], ['savingDue', '预计降本完成时间', ''], ['equipName', '新增设备名称', ''], ['equipAmount', '新增设备金额', '元']].forEach(([f, l, u]) => {
      const base = f === 'equipAmount' ? m.equip.amount : m[f];
      const was = saved[f] != null ? saved[f] : base;
      const now = vals[mk + '.' + f];
      if (now != null && now !== '' && String(was) !== String(now)) push(l, (was == null || was === '' ? '—' : was + (u ? ' ' + u : '')), now + (u ? ' ' + u : ''));
    });
  });
  return out;
}

function computeSupplyChanges(d, vals) {
  const out = [];
  window.BOM_LINES.forEach((b) => {
    const sp = (d.payload.supply || {})[b.id] || {};
    const nowP = vals['sup.' + b.id + '.price'];
    const wasP = sp.priceTax != null ? sp.priceTax : b.priceTax;
    if (nowP != null && String(wasP) !== String(nowP)) out.push({ product: b.id, k: '含税采购单价', from: wasP + ' 元/' + b.unit, to: nowP + ' 元/' + b.unit });
    const nowT = vals['sup.' + b.id + '.tent'];
    const wasT = sp.tentativePriceTax != null ? sp.tentativePriceTax : (b.tentativePriceTax != null ? b.tentativePriceTax : null);
    if (nowT != null && nowT !== '' && String(wasT) !== String(nowT)) out.push({ product: b.id, k: '临测含税采购价', from: wasT == null ? '—' : wasT + ' 元/' + b.unit, to: nowT + ' 元/' + b.unit });
    const wasOk = sp.confirmed != null ? sp.confirmed : !!b.confirmed;
    const nowOk = !!vals['sup.' + b.id + '.ok'];
    if (wasOk !== nowOk) out.push({ product: b.id, k: '含税采购单价确认', from: wasOk ? '已确认' : '待确认', to: nowOk ? '已确认' : '待确认' });
  });
  return out;
}

/* 驳回时受影响字段 */
function computeRejectChanges(d, target) {
  const out = [];
  if (target <= 1) {
    d.modes.forEach((mk) => {
      const saved = (d.payload.prod || {})[mk];
      if (!saved) return;
      const m = window.modeById(mk);
      const defaults = window.modeDefaults(m);
      [['batches', '连续生产批次', '批'], ['hpd', '每日生产时长', 'h/天']].forEach(([f, l, u]) => {
        if (saved[f] != null && String(saved[f]) !== String(defaults[f])) out.push({ product: mk, k: l, from: saved[f] + ' ' + u, to: defaults[f] + ' ' + u });
      });
      ['semi', 'fin'].forEach((sk) => {
        if (!saved[sk]) return;
        Object.keys(STAGE_LABELS).forEach((f) => {
          const was = saved[sk][f];
          const base = defaults[sk][f];
          if (was != null && String(was) !== String(base)) {
            const disp = (x) => f === 'yield' ? Math.round(Number(x) * 1000) / 10 + ' %' : (x == null ? '—' : STAGE_LABELS[f][1] ? x + ' ' + STAGE_LABELS[f][1] : String(x));
            out.push({ product: mk, k: (sk === 'semi' ? '半成品' : '产成品') + ' · ' + STAGE_LABELS[f][0], from: disp(was), to: disp(base) });
          }
        });
      });
    });
  }
  if (target <= 2) {
    Object.keys(d.payload.supply || {}).forEach((mid) => {
      const s = d.payload.supply[mid];
      if (s && s.confirmed) out.push({ product: mid, k: '含税采购单价确认', from: '已确认', to: '待重新确认' });
    });
  }
  return out;
}

/* ================================================================
   申报单详情 / 填报 / 审批
   ================================================================ */
function DeclarationDrawer({ id, onClose }) {
  const app = useApp();
  const { bomLines, factors } = app;
  const { role, toast, go, submitDeclaration, approveDeclaration, rejectDeclaration } = app;
  const d = app.decls.find((x) => x.id === id);
  const [vals, setVals] = useState(() => (d ? buildVals(d) : {}));
  const [selMode, setSelMode] = useState(d && d.modes[0] || 'M1');
  const [note, setNote] = useState('');
  const [rejectOpen, setRejectOpen] = useState(false);
  const [rejectTarget, setRejectTarget] = useState(1);
  const [rejectNote, setRejectNote] = useState('');

  useEffect(() => { if (d) { setVals(buildVals(d)); setSelMode(d.modes[0] || 'M1'); } }, [id, d && d.step, d && d.status]);

  if (!d) return null;

  const myStep = role === 'prod' ? 1 : role === 'supply' ? 2 : 3;
  const canEdit = role !== 'finance' && d.step === myStep && d.status !== 'approved';
  const canApprove = role === 'finance' && d.step === 3 && d.status === 'reviewing';
  const readOnly = !canEdit && !canApprove;

  const changes = useMemo(() => (myStep === 1 ? computeProdChanges(d, vals) : myStep === 2 ? computeSupplyChanges(d, vals) : []), [vals, d, myStep]);

  const doSubmit = () => {
    const data = { changes };
    if (myStep === 1) {
      data.prod = Object.assign({}, d.payload.prod);
      d.modes.forEach((mk) => {
        const s = { semi: {}, fin: {} };
        ['batches', 'hpd'].forEach((f) => { s[f] = Number(vals[mk + '.' + f]); });
        ['saving', 'savingDue', 'equipName'].forEach((f) => { s[f] = vals[mk + '.' + f] === '' ? '' : vals[mk + '.' + f]; });
        s.equipAmount = Number(vals[mk + '.equipAmount']);
        ['semi', 'fin'].forEach((sk) => {
          Object.keys(STAGE_LABELS).forEach((f) => {
            const raw = vals[mk + '.' + sk + '.' + f];
            if (raw === '' || raw == null) { s[sk][f] = null; return; }
            s[sk][f] = Number(raw);
          });
        });
        data.prod[mk] = s;
      });
      const prog = Object.assign({}, d.progress);
      d.modes.forEach((mk) => { prog[mk] = 7; });
      data.progress = prog;
    } else if (myStep === 2) {
      data.supply = Object.assign({}, d.payload.supply);
      window.BOM_LINES.forEach((b) => {
        const tent = vals['sup.' + b.id + '.tent'];
        data.supply[b.id] = {
          priceTax: Number(vals['sup.' + b.id + '.price']),
          tentativePriceTax: tent === '' || tent == null ? null : Number(tent),
          confirmed: !!vals['sup.' + b.id + '.ok'],
        };
      });
      const prog = Object.assign({}, d.progress);
      d.modes.forEach((mk) => { prog[mk] = 7; });
      data.progress = prog;
    }
    submitDeclaration(id, data, note);
    toast(myStep === 1 ? '生产车间填报已提交，已流转至供应部' : '供应部填报已提交，已流转至财务审批', 'ok');
  };

  const doApprove = () => {
    approveDeclaration(id, note);
    toast('审批已通过，成本明细已审核锁定（审核时间 ' + window.NOW + '）', 'ok');
    setNote('');
  };

  const doReject = () => {
    if (!rejectNote.trim()) { toast('请填写驳回原因，驳回必须留下审计说明', 'err'); return; }
    rejectDeclaration(id, rejectTarget, rejectNote, computeRejectChanges(d, rejectTarget));
    setRejectOpen(false);
    toast('已驳回至「' + window.STEPS[rejectTarget].name + '」，填报人将收到通知', 'warn');
  };

  const meta = STATUS_META[d.status];
  const latest = d.timeline[d.timeline.length - 1];

  return (
    <Drawer wide title={d.title}
      meta={<Fragment>
        <span className="num">{d.id}</span>
        <span className="muted">·</span><span>{d.period}</span>
        <span className="muted">·</span><span>发起人 {d.creator}</span>
        <span className="muted">·</span><span>截止 {d.due}</span>
        <Tag kind={meta.kind} dot>{meta.label}</Tag>
      </Fragment>}
      onClose={onClose}
      footer={<Fragment>
        {readOnly && <span className="tiny muted"><Ico name="lock" size={12} style={{ display: 'inline', verticalAlign: -2 }} /> 当前节点由 {window.STEPS[Math.min(d.step, 3)].dept} · {window.STEPS[Math.min(d.step, 3)].owner} 处理，你为只读</span>}
        {canEdit && <span className="tiny muted">变更 {changes.length} 项，提交后将自动生成审计记录</span>}
        <div className="spacer" />
        {canEdit && <Btn size="sm" kind="primary" icon="check" onClick={doSubmit}>提交填报</Btn>}
        {canApprove && <Fragment>
          <Btn size="sm" kind="danger" icon="undo" onClick={() => { setRejectOpen(true); setRejectNote(''); setRejectTarget(1); }}>驳回 / 回退</Btn>
          <Btn size="sm" kind="ok" icon="check" onClick={doApprove}>审批通过 · 审核锁定</Btn>
        </Fragment>}
        {!canEdit && !canApprove && <Btn size="sm" onClick={onClose}>关闭</Btn>}
      </Fragment>}>

      {/* 流程 */}
      <Card className="mb12"><div className="card-body" style={{ padding: '14px 16px' }}>
        <FlowStepper decl={d} />
        <div className="divider" />
        <div className="row tiny muted" style={{ gap: 16, flexWrap: 'wrap' }}>
          <span>当前处理人：<b style={{ color: 'var(--ink-2)' }}>{window.STEPS[Math.min(d.step, 3)].dept} · {window.STEPS[Math.min(d.step, 3)].owner}</b></span>
          <span>最近动作：{latest.act}（{latest.t}）</span>
          <span>产品：<b style={{ color: 'var(--ink-2)' }}>{window.PRODUCT.name}</b> · {d.modes.length} 个工况</span>
        </div>
      </div></Card>

      <div className="grid" style={{ gridTemplateColumns: 'minmax(0,1.15fr) minmax(0,1fr)', alignItems: 'start' }}>
        {/* 左：填报区 */}
        <div className="col" style={{ gap: 12 }}>
          {myStep === 1 && <ProdForm d={d} vals={vals} setVals={setVals} canEdit={canEdit} selMode={selMode} setSelMode={setSelMode} bomLines={bomLines} factors={factors} />}
          {myStep === 2 && <SupplyForm d={d} vals={vals} setVals={setVals} canEdit={canEdit} />}
          {myStep === 3 && <FinanceReview d={d} />}

          <Card>
            <CardHead title="数据完整度" sub="每工况 7 项参数（车间 5 + 供应 1 + 财务 1）" />
            <div className="card-body">
              {d.modes.map((mk, i) => {
                const items = app.COMPLETENESS[mk] || [];
                const ok = items.filter((x) => x.ok).length;
                return (
                  <div key={mk} style={{ padding: i ? '10px 0 0' : 0 }}>
                    <div className="row" style={{ gap: 8 }}>
                      <span className="num" style={{ fontSize: 12.5, fontWeight: 600 }}>{mk}</span>
                      <span className="tiny muted">{window.modeById(mk).desc}</span>
                      <span className="spacer" />
                      <span className="num tiny" style={{ color: ok === 7 ? 'var(--ok)' : 'var(--warn)' }}>{ok}/7</span>
                    </div>
                    <div style={{ marginTop: 6 }}><Bar value={ok / 7 * 100} kind={ok === 7 ? 'ok' : 'warn'} /></div>
                    {ok < 7 && <div className="tiny muted mt8">缺：{items.filter((x) => !x.ok).map((x) => x.k).join('、')}</div>}
                  </div>
                );
              })}
            </div>
          </Card>
        </div>

        {/* 右：审计时间线 */}
        <Card>
          <CardHead title="流程与审计记录" sub={d.timeline.length + ' 条'}
            right={<Btn size="sm" ghost icon="eye" onClick={() => go('audit', { ref: d.id })}>全局日志</Btn>} />
          <div className="card-body" style={{ maxHeight: 560, overflowY: 'auto' }}>
            <Timeline events={d.timeline} />
            {d.lockedAt && (
              <div className="notice ok mt12">
                <span className="n-ico"><Ico name="lock" size={13} /></span>
                <div className="tiny">本单成本明细已审核锁定 · 审核人 {d.lockBy} · 审核时间 {d.lockedAt}，锁定后不可再修改。</div>
              </div>
            )}
            <div className="notice flat mt12">
              <span className="n-ico"><Ico name="lock" size={13} /></span>
              <div className="tiny">记录由系统自动生成，任何角色均无法修改或删除；变更前后值一并留存。</div>
            </div>
          </div>
        </Card>
      </div>

      {canEdit && (
        <Card className="mt12"><div className="card-body">
          <div className="field">
            <label>填报说明 <span className="hint">将写入审计记录，便于审批人判断数据口径</span></label>
            <textarea className="textarea" rows={2} value={note} onChange={(e) => setNote(e.target.value)} placeholder="例：模式4 因扩批验证收率由 96% 上调至 97%；新增泡罩板检测机 45 万元（2025 年预算已体现）。" />
          </div>
        </div></Card>
      )}

      {rejectOpen && (
        <Modal title="驳回 / 回退测算单" onClose={() => setRejectOpen(false)}
          footer={<Fragment>
            <Btn size="sm" onClick={() => setRejectOpen(false)}>取消</Btn>
            <div className="spacer" />
            <Btn size="sm" kind="danger" icon="undo" onClick={doReject}>确认驳回</Btn>
          </Fragment>}>
          <div className="col" style={{ gap: 14 }}>
            <div className="notice warn">
              <span className="n-ico"><Ico name="alert" size={15} /></span>
              <div>驳回会使测算单回退到所选节点，该节点之后的填报数据将标记为「待重新提交」，并通知对应填报人。</div>
            </div>
            <div className="field">
              <label>回退至节点</label>
              <div className="col" style={{ gap: 8 }}>
                {window.STEPS.slice(1, 3).map((s, i) => {
                  const idx = i + 1;
                  return (
                    <label key={s.key} className="row" style={{ gap: 9, padding: '9px 12px', border: '1px solid ' + (rejectTarget === idx ? 'var(--primary-line)' : 'var(--line)'), background: rejectTarget === idx ? 'var(--primary-soft)' : 'transparent', borderRadius: 8, cursor: 'pointer' }}
                      onClick={() => setRejectTarget(idx)}>
                      <input type="radio" checked={rejectTarget === idx} onChange={() => setRejectTarget(idx)} />
                      <span style={{ fontSize: 12.5, fontWeight: 500 }}>{s.name}</span>
                      <span className="tiny muted">处理人 {s.owner} · {s.dept}</span>
                    </label>
                  );
                })}
              </div>
            </div>
            <div className="field">
              <label>驳回原因 <span className="hint">必填</span></label>
              <textarea className="textarea" rows={3} value={rejectNote} onChange={(e) => setRejectNote(e.target.value)}
                placeholder="例：成品率与生产日报不符，请附批次记录说明。" />
            </div>
            <div className="field">
              <label>将回滚的字段</label>
              <div className="tiny muted">
                {computeRejectChanges(d, rejectTarget).length === 0 ? '该节点之后没有可回滚的申报值。' :
                  computeRejectChanges(d, rejectTarget).map((c, i) => (
                    <div key={i} className="row" style={{ gap: 6, padding: '4px 0' }}>
                      <span className="num">{c.product}</span><span>{c.k}</span>
                      <del className="num">{c.from}</del><Ico name="arrowRight" size={11} /><ins className="num">{c.to}</ins>
                    </div>
                  ))}
              </div>
            </div>
          </div>
        </Modal>
      )}
    </Drawer>
  );
}

/* ---------- 生产车间填报表（对齐新都附表1） ---------- */
function ProdForm({ d, vals, setVals, canEdit, selMode, setSelMode, bomLines, factors }) {
  return (
    <Card>
      <CardHead title="生产车间填报（附表1 · 向车间收集基础数据）" sub="批理论产量 / 成品率 / 抽留样 / 生产时长 / 水电气汽 / 定员明细"
        right={canEdit ? <Tag kind="primary" dot>可编辑</Tag> : <Tag><Ico name="lock" size={11} />只读</Tag>} />
      <div className="card-body" style={{ paddingTop: 6 }}>
        <div className="row wrap" style={{ gap: 6 }}>
          {d.modes.map((mk) => {
            const m = window.modeById(mk);
            return <Chip key={mk} on={selMode === mk} onClick={() => setSelMode(mk)}>{m.name} · {m.desc}</Chip>;
          })}
        </div>
        {d.modes.map((mk) => (
          <div key={mk} style={{ display: selMode === mk ? 'block' : 'none' }}>
            <ModeForm mk={mk} vals={vals} setVals={setVals} canEdit={canEdit} bomLines={bomLines} factors={factors} />
          </div>
        ))}
      </div>
    </Card>
  );
}

function StageInput({ mk, sk, f, vals, setVals, canEdit }) {
  const key = mk + '.' + sk + '.' + f.id;
  if (f.id === 'sample' && sk === 'semi') return <span className="tiny muted">—（半成品不扣抽留样）</span>;
  const v = vals[key];
  if (!canEdit) {
    return <span className="num">{f.pct ? (Number(v) * 100).toFixed(1) : (v == null || v === '' ? '—' : v)}</span>;
  }
  return (
    <input className="input mono" style={{ height: 28, textAlign: 'right', padding: '0 8px', width: 104 }}
      value={f.pct ? String(Math.round(Number(v) * 1000) / 10) : (v == null ? '' : v)}
      onChange={(e) => {
        let nv = e.target.value;
        if (f.pct) nv = nv === '' ? '' : Number(nv) / 100;
        setVals(Object.assign({}, vals, { [key]: nv }));
      }} />
  );
}

function ModeForm({ mk, vals, setVals, canEdit, bomLines, factors }) {
  const m = window.modeById(mk);
  const t = trialMode(mk, vals, bomLines, factors);
  const stageFields = [
    { id: 'batchQty', label: '批理论产量', unit: '片/盒' },
    { id: 'sample', label: '标准单批抽留样', unit: '盒' },
    { id: 'sampleTimes', label: '标准抽样次数', unit: '次' },
    { id: 'yield', label: '成品率', unit: '%', pct: true },
    { id: 'hours', label: '生产时长', unit: 'h' },
    { id: 'dailyLabor', label: '日总人工', unit: '小时' },
    { id: 'water', label: '单批次水', unit: '吨' },
    { id: 'elec', label: '单批次电', unit: '度' },
    { id: 'steam', label: '单批次汽', unit: '吨' },
  ];
  return (
    <div className="col" style={{ gap: 12, marginTop: 12 }}>
      <div className="row" style={{ gap: 8 }}>
        <span style={{ fontSize: 13, fontWeight: 600 }}>{m.name}</span>
        <Tag>{m.desc}</Tag>
        <Tag kind={m.kind === '扩批生产' ? 'primary' : ''} dot>{m.kind}</Tag>
        <span className="spacer" />
        <span className="tiny muted">{m.note}</span>
      </div>

      <div className="grid g2">
        {[{ id: 'batches', label: '连续生产批次', unit: '批' }, { id: 'hpd', label: '每日生产时长', unit: 'h/天' }].map((f) => (
          <div className="field" key={f.id}>
            <label>{f.label}（{f.unit}）</label>
            {canEdit
              ? <input className="input mono" style={{ height: 30 }} value={vals[mk + '.' + f.id]} onChange={(e) => setVals(Object.assign({}, vals, { [mk + '.' + f.id]: e.target.value }))} />
              : <span className="num" style={{ lineHeight: '30px' }}>{vals[mk + '.' + f.id]}</span>}
          </div>
        ))}
      </div>

      <div className="tbl-wrap">
        <table className="tbl">
          <thead>
            <tr>
              <th>工艺阶段字段</th>
              <th className="r" style={{ width: 170 }}>半成品（片）</th>
              <th className="r" style={{ width: 170 }}>产成品（盒）</th>
            </tr>
          </thead>
          <tbody>
            {stageFields.map((f) => (
              <tr key={f.id}>
                <td className="tiny">{f.label} <span className="muted">（{f.unit}）</span></td>
                <td className="r"><StageInput mk={mk} sk="semi" f={f} vals={vals} setVals={setVals} canEdit={canEdit} /></td>
                <td className="r"><StageInput mk={mk} sk="fin" f={f} vals={vals} setVals={setVals} canEdit={canEdit} /></td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <div className="preview-panel">
        <div className="row wrap" style={{ gap: 18 }}>
          <div><div className="tiny muted">半成品标准成品量</div><div className="num" style={{ fontSize: 15, fontWeight: 600 }}>{numF(t.std.semi.std)} 片</div></div>
          <div><div className="tiny muted">产成品标准成品量</div><div className="num" style={{ fontSize: 15, fontWeight: 600 }}>{numF(t.std.fin.std)} 盒</div></div>
          <div><div className="tiny muted">单一批次成品量</div><div className="num" style={{ fontSize: 13 }}>{numF(t.std.semi.perBatch)} / {numF(t.std.fin.perBatch)}</div></div>
          <div><div className="tiny muted">生产天数（半 / 产）</div><div className="num" style={{ fontSize: 13 }}>{Number(t.std.semi.days).toFixed(2)} / {Number(t.std.fin.days).toFixed(2)} 天</div></div>
          <div><div className="tiny muted">单批次人工（半 / 产）</div><div className="num" style={{ fontSize: 13 }}>{Number(t.std.semi.laborPerBatch).toFixed(1)} / {Number(t.std.fin.laborPerBatch).toFixed(1)} h</div></div>
          <div><div className="tiny muted">人工总小时（日总人工，已含改线大清场）</div><div className="num" style={{ fontSize: 13 }}>{t.laborTotal} h</div></div>
        </div>
        <div className="tiny muted mt8" style={{ borderTop: '1px dashed var(--line)', paddingTop: 7 }}>
          实时试算（按当前 BOM 与报工单价）：测算生产成本 <b style={{ color: 'var(--ink)' }}>{yuanF(t.prodCost.noTax)}</b> 元（不含税）·
          最小单位成本 <b style={{ color: 'var(--ink)' }}>{yuanF(t.minUnit.piece.noTax)}</b> 元/片 · <b style={{ color: 'var(--ink)' }}>{yuanF(t.minUnit.box.noTax)}</b> 元/盒
        </div>
      </div>

      <div>
        <div className="tiny muted mb8" style={{ fontWeight: 500 }}>日产量定员明细（岗位 × 人数 × 工时，演示为只读）</div>
        <div className="tbl-wrap">
          <table className="tbl">
            <thead>
              <tr><th>岗位</th><th className="r" style={{ width: 90 }}>人数</th><th className="r" style={{ width: 110 }}>工时（h/人·天）</th><th className="r" style={{ width: 110 }}>总工时（h）</th></tr>
            </thead>
            <tbody>
              {m.staff.rows.map((r, i) => (
                <tr key={i}>
                  <td className="tiny">{r.post}</td>
                  <td className="r num">{r.n}</td>
                  <td className="r num">{r.hours}</td>
                  <td className="r num">{r.total}</td>
                </tr>
              ))}
              <tr style={{ background: 'var(--surface-2)' }}>
                <td className="tiny" style={{ fontWeight: 600 }}>合计</td>
                <td className="r num" style={{ fontWeight: 600 }}>{m.staff.rows.reduce((s, r) => s + r.n, 0)}</td>
                <td className="r num" style={{ fontWeight: 600 }}>{Number(m.staff.rows.reduce((s, r) => s + r.hours, 0)).toFixed(2)}</td>
                <td className="r num" style={{ fontWeight: 600 }}>{m.staff.rows.reduce((s, r) => s + r.total, 0)}</td>
              </tr>
              <tr>
                <td className="tiny">改线 + 大清场（含拆洗）</td>
                <td className="r num">{m.staff.changeover.n}</td>
                <td className="r num">{m.staff.changeover.hours}</td>
                <td className="r num">{m.staff.changeover.total}（= {m.staff.changeover.laborH} h）</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>

      <div className="grid g2">
        <div className="field">
          <label>生产条件 / 降本措施</label>
          {canEdit
            ? <input className="input" value={vals[mk + '.saving']} onChange={(e) => setVals(Object.assign({}, vals, { [mk + '.saving']: e.target.value }))} />
            : <span className="tiny" style={{ lineHeight: '28px' }}>{vals[mk + '.saving'] || '—'}</span>}
        </div>
        <div className="field">
          <label>预计降本完成时间</label>
          {canEdit
            ? <input className="input" value={vals[mk + '.savingDue']} onChange={(e) => setVals(Object.assign({}, vals, { [mk + '.savingDue']: e.target.value }))} />
            : <span className="tiny" style={{ lineHeight: '28px' }}>{vals[mk + '.savingDue'] || '—'}</span>}
        </div>
        <div className="field">
          <label>新增设备名称</label>
          {canEdit
            ? <input className="input" value={vals[mk + '.equipName']} onChange={(e) => setVals(Object.assign({}, vals, { [mk + '.equipName']: e.target.value }))} />
            : <span className="tiny" style={{ lineHeight: '28px' }}>{vals[mk + '.equipName'] || '—'}</span>}
        </div>
        <div className="field">
          <label>新增设备金额（元）</label>
          {canEdit
            ? <input className="input mono" value={vals[mk + '.equipAmount']} onChange={(e) => setVals(Object.assign({}, vals, { [mk + '.equipAmount']: e.target.value }))} />
            : <span className="num" style={{ lineHeight: '28px' }}>{numF(vals[mk + '.equipAmount'])}</span>}
        </div>
      </div>
    </div>
  );
}

/* ---------- 供应部填报（对齐科伦物料价格表） ---------- */
function SupplyForm({ d, vals, setVals, canEdit }) {
  return (
    <Card>
      <CardHead title="供应部填报（物料价格表）" sub="含税采购单价确认 + 临测价双轨 + 税率复核"
        right={canEdit ? <Tag kind="primary" dot>可编辑</Tag> : <Tag><Ico name="lock" size={11} />只读</Tag>} />
      <div className="tbl-wrap">
        <table className="tbl">
          <thead>
            <tr>
              <th>物料</th>
              <th style={{ width: 84 }}>投料阶段</th>
              <th className="r" style={{ width: 86 }}>单耗</th>
              <th className="r" style={{ width: 104 }}>含税单价</th>
              <th className="r" style={{ width: 64 }}>税率</th>
              <th className="r" style={{ width: 104 }}>不含税单价</th>
              <th className="r" style={{ width: 104 }}>临测含税价</th>
              <th style={{ width: 78 }} className="c">确认</th>
            </tr>
          </thead>
          <tbody>
            {window.BOM_LINES.map((b) => {
              const price = Number(vals['sup.' + b.id + '.price']) || 0;
              const noTax = price / (1 + b.taxRate);
              return (
                <tr key={b.id}>
                  <td>
                    <div className="primary-cell">{b.name}</div>
                    <div className="sub-cell num">{b.id} · {b.spec} · {b.supplier}</div>
                  </td>
                  <td><span className="tag" style={{ height: 19, fontSize: 10.5 }}>{b.stage === 'semi' ? '半成品（片）' : '产成品（盒）'}</span></td>
                  <td className="r num tiny">{b.qty} /万{b.stage === 'semi' ? '片' : '盒'}（{b.unit}）</td>
                  <td className="r">
                    {canEdit
                      ? <input className="input mono" style={{ height: 28, textAlign: 'right', padding: '0 8px', width: 88 }} value={vals['sup.' + b.id + '.price']} onChange={(e) => setVals(Object.assign({}, vals, { ['sup.' + b.id + '.price']: e.target.value }))} />
                      : <span className="num">{vals['sup.' + b.id + '.price']}</span>}
                    <div className="tiny muted" style={{ marginTop: 2 }}>元/{b.unit}</div>
                  </td>
                  <td className="r num tiny">{Math.round(b.taxRate * 100)}%</td>
                  <td className="r num">{yuanF(noTax)}<div className="tiny muted" style={{ marginTop: 2 }}>元/{b.unit}</div></td>
                  <td className="r">
                    {canEdit
                      ? <input className="input mono" style={{ height: 28, textAlign: 'right', padding: '0 8px', width: 88 }} value={vals['sup.' + b.id + '.tent']} placeholder="同含税价" onChange={(e) => setVals(Object.assign({}, vals, { ['sup.' + b.id + '.tent']: e.target.value }))} />
                      : <span className="num" style={{ color: vals['sup.' + b.id + '.tent'] ? 'var(--ink)' : 'var(--ink-4)' }}>{vals['sup.' + b.id + '.tent'] || '—'}</span>}
                  </td>
                  <td className="c">
                    {canEdit
                      ? <div style={{ display: 'grid', placeItems: 'center' }}><Switch on={!!vals['sup.' + b.id + '.ok']} onChange={(v) => setVals(Object.assign({}, vals, { ['sup.' + b.id + '.ok']: v }))} /></div>
                      : <span className="tag" style={{ background: vals['sup.' + b.id + '.ok'] ? 'var(--ok-soft)' : 'var(--warn-soft)', color: vals['sup.' + b.id + '.ok'] ? 'var(--ok)' : 'var(--warn)' }}>{vals['sup.' + b.id + '.ok'] ? '已确认' : '待确认'}</span>}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
      <div style={{ padding: '10px 14px' }}>
        <div className="notice flat">
          <span className="n-ico"><Ico name="info" size={14} /></span>
          <div className="tiny">含税单价默认取物料价格库同步值；与供应商最新报价不一致的物料请先到「主数据同步 → 差异对比」处理。临测价用于「临测价格成本」双轨测算（科伦口径）。</div>
        </div>
      </div>
    </Card>
  );
}

/* ---------- 财务复核面板（对齐科伦测算成本表） ---------- */
function FinanceReview({ d }) {
  const app = useApp();
  const savedChanges = d.timeline.flatMap((e) => (e.changes || []).map((c) => Object.assign({ t: e.t, who: e.who }, c)));
  return (
    <Card>
      <CardHead title="财务复核" sub="测算生产成本（含税 / 不含税 / 临测）· 通过后成本明细审核锁定"
        right={<Tag kind="warn" dot>待审批</Tag>} />
      <div className="card-body">
        <div className="dsec">
          <h4>申报变更摘要 <span className="line" /></h4>
          <div className="tl-diff" style={{ border: '1px solid var(--line)', borderRadius: 8 }}>
            {savedChanges.length === 0 && <div style={{ padding: '10px 12px' }} className="tiny muted">本次申报无字段变更。</div>}
            {savedChanges.map((c, i) => (
              <div className="row" key={i} style={{ borderTop: i ? '1px solid var(--line)' : 'none' }}>
                <span className="k" style={{ width: 178, flex: '0 0 auto', padding: '6px 10px', background: 'var(--surface-2)', color: 'var(--ink-3)', fontSize: 12 }}>{[c.product, c.k].filter(Boolean).join(' · ')}</span>
                <span style={{ padding: '6px 10px', display: 'flex', gap: 7, alignItems: 'center', flexWrap: 'wrap', fontSize: 12 }}>
                  <del className="num">{c.from}</del><Ico name="arrowRight" size={11} style={{ color: 'var(--ink-4)' }} /><ins className="num">{c.to}</ins>
                  <span className="spacer" />
                  <span className="tiny muted">{c.who} · {c.t.slice(5)}</span>
                </span>
              </div>
            ))}
          </div>
        </div>

        <div className="dsec">
          <h4>按当前数据试算（科伦 · 测算成本） <span className="line" /></h4>
          <div className="tbl-wrap">
            <table className="tbl">
              <thead>
                <tr>
                  <th>工况</th>
                  <th className="r">产成品（盒）</th>
                  <th className="r">物料成本</th>
                  <th className="r">车间付现</th>
                  <th className="r">车间折旧</th>
                  <th className="r">辅助付现</th>
                  <th className="r">辅助折旧</th>
                  <th className="r">测算生产成本<br /><span className="tiny">含税</span></th>
                  <th className="r">测算生产成本<br /><span className="tiny">不含税</span></th>
                  <th className="r">最小单位成本<br /><span className="tiny">元/盒·不含税</span></th>
                  <th className="r">临测生产成本<br /><span className="tiny">含税</span></th>
                </tr>
              </thead>
              <tbody>
                {d.modes.map((mk) => {
                  const c = app.calc[mk];
                  if (!c) return null;
                  return (
                    <tr key={mk}>
                      <td>
                        <div className="primary-cell">{mk} {window.modeById(mk).name}</div>
                        <div className="sub-cell">{window.modeById(mk).desc} · {window.modeById(mk).kind}</div>
                      </td>
                      <td className="r num">{numF(c.std.fin.std)}</td>
                      <td className="r num">{yuanF(c.material.tax.total)}</td>
                      <td className="r num">{yuanF(c.factors.wsCash.tax)}</td>
                      <td className="r num">{yuanF(c.factors.wsDepr.tax)}</td>
                      <td className="r num">{yuanF(c.factors.auxCash.tax)}</td>
                      <td className="r num">{yuanF(c.factors.auxDepr.tax)}</td>
                      <td className="r num" style={{ fontWeight: 600 }}>{yuanF(c.prodCost.tax)}</td>
                      <td className="r num" style={{ fontWeight: 600 }}>{yuanF(c.prodCost.noTax)}</td>
                      <td className="r num" style={{ fontWeight: 600, color: 'var(--primary)' }}>{yuanF(c.minUnit.box.noTax)}</td>
                      <td className="r num" style={{ color: c.prodCost.tentTax > c.prodCost.tax ? 'var(--warn)' : 'var(--ink-2)' }}>{yuanF(c.prodCost.tentTax)}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>

        <div className="dsec">
          <h4>最小单位测算成本（最小单位系数 {window.PRODUCT.minUnitFactor} · 片 → 盒） <span className="line" /></h4>
          <div className="row wrap" style={{ gap: 8 }}>
            {d.modes.map((mk) => {
              const c = app.calc[mk];
              if (!c) return null;
              return (
                <div key={mk} style={{ border: '1px solid var(--line)', borderRadius: 8, padding: '8px 12px', background: 'var(--surface)' }}>
                  <div className="tiny" style={{ fontWeight: 600 }}>{mk}</div>
                  <div className="row" style={{ gap: 12, marginTop: 4 }}>
                    <div><div className="tiny muted">元/片</div><div className="num" style={{ fontSize: 13 }}>{yuanF(c.minUnit.piece.noTax)}</div><div className="tiny muted">含税 {yuanF(c.minUnit.piece.tax)}</div></div>
                    <div><div className="tiny muted">元/盒</div><div className="num" style={{ fontSize: 13, fontWeight: 600, color: 'var(--primary)' }}>{yuanF(c.minUnit.box.noTax)}</div><div className="tiny muted">含税 {yuanF(c.minUnit.box.tax)}</div></div>
                  </div>
                </div>
              );
            })}
          </div>
        </div>

        <div className="notice flat">
          <span className="n-ico"><Ico name="info" size={14} /></span>
          <div className="tiny">试算基于当前主数据（BOM 单耗 / 物料价格 / 报工单价）与车间填报值。审批通过后成本明细将审核锁定并记录审核时间，锁定后不可修改。</div>
        </div>
      </div>
    </Card>
  );
}

/* ================================================================
   3. 审批中心
   ================================================================ */
function ApprovalsPage() {
  const app = useApp();
  const { decls, nav, toast, approveDeclaration, rejectDeclaration, sync } = app;
  const [openId, setOpenId] = useState(nav.params && nav.params.focus || null);
  const [rejectFor, setRejectFor] = useState(null);

  useEffect(() => { if (nav.params && nav.params.focus) setOpenId(nav.params.focus); }, [nav.params]);

  const pending = decls.filter((d) => d.step === 3 && d.status === 'reviewing');
  const rejected = decls.filter((d) => d.status === 'rejected');
  const approved = decls.filter((d) => d.status === 'approved');
  const openDiffs = sync.diffs.filter((d) => d.status === 'open');

  const quality = (d) => {
    const total = d.modes.length * 7;
    const ok = d.modes.reduce((s, mk) => s + (app.COMPLETENESS[mk] || []).filter((x) => x.ok).length, 0);
    return { ok, total, pct: ok / total * 100 };
  };

  return (
    <div className="scroll" data-screen-label="06 审批中心">
      <div className="sec-title">
        <h2>审批中心</h2>
        <span className="sub">财务复核业务部门申报内容 · 可逐节点回退 · 通过后审核锁定</span>
        <div className="right">
          <Btn size="sm" icon="download">导出审批记录</Btn>
        </div>
      </div>

      <div className="grid g4 mb12">
        {[
          { k: '待我审批', v: pending.length, s: '等待复核的测算单', c: 'var(--warn)' },
          { k: '同步差异待确认', v: openDiffs.length, s: '未确认前用本地值核算', c: 'var(--danger)' },
          { k: '本月已通过', v: approved.length, s: '已审核锁定', c: 'var(--ok)' },
          { k: '被驳回', v: rejected.length, s: '需业务部门重新举证', c: 'var(--ink)' },
        ].map((x) => (
          <Card key={x.k}><div className="kpi" style={{ padding: '14px 16px' }}>
            <div className="k-label">{x.k}</div>
            <div className="k-value" style={{ fontSize: 26, color: x.c }}>{x.v}<small>张</small></div>
            <div className="k-foot"><span className="muted">{x.s}</span></div>
          </div></Card>
        ))}
      </div>

      <Card className="mb12">
        <CardHead title="待我审批" sub={pending.length + ' 张'} right={<Btn size="sm" icon="check">批量通过</Btn>} />
        {pending.length === 0
          ? <Empty icon="checkc" title="没有待审批的测算单" desc="生产车间与供应部全部完成填报后，测算单会自动流转到审批中心。" />
          : (
            <div className="tbl-wrap">
              <table className="tbl">
                <thead>
                  <tr>
                    <th style={{ width: 148 }}>单号</th>
                    <th>任务名称</th>
                    <th style={{ width: 120 }}>覆盖工况</th>
                    <th style={{ width: 172 }}>数据完整度</th>
                    <th style={{ width: 140 }}>申报变更</th>
                    <th style={{ width: 112 }}>提交时间</th>
                    <th style={{ width: 210 }} className="r">审批</th>
                  </tr>
                </thead>
                <tbody>
                  {pending.map((d) => {
                    const q = quality(d);
                    const chg = d.timeline.reduce((s, e) => s + (e.changes || []).length, 0);
                    const risk = d.timeline.some((e) => (e.changes || []).some((c) => c.k.includes('成品率') || c.k.includes('单价')));
                    return (
                      <tr key={d.id} className="clickable" onClick={() => setOpenId(d.id)}>
                        <td className="num" style={{ fontSize: 12.5 }}>{d.id}</td>
                        <td>
                          <div className="primary-cell">{d.title}</div>
                          <div className="sub-cell">{d.period} · 生产车间 + 供应部已提交</div>
                        </td>
                        <td>
                          <div className="row wrap" style={{ gap: 4 }}>{d.modes.map((mk) => <span className="tag" key={mk} style={{ height: 19, fontSize: 10.5 }}>{mk}</span>)}</div>
                        </td>
                        <td>
                          <div className="row" style={{ gap: 8 }}>
                            <Bar value={q.pct} kind={q.pct === 100 ? 'ok' : 'warn'} />
                            <span className="num tiny nowrap">{q.ok}/{q.total}</span>
                          </div>
                        </td>
                        <td>
                          <div className="row" style={{ gap: 6 }}>
                            <span className="num" style={{ fontSize: 12.5 }}>{chg} 项</span>
                            {risk && <Tag kind="warn">涉及成品率/单价</Tag>}
                          </div>
                        </td>
                        <td className="num tiny muted">{d.timeline[d.timeline.length - 1].t.slice(5)}</td>
                        <td className="r">
                          <div className="row" style={{ justifyContent: 'flex-end', gap: 6 }}>
                            <button className="btn sm danger" onClick={(e) => { e.stopPropagation(); setRejectFor(d.id); }}>驳回</button>
                            <button className="btn sm ok" onClick={(e) => { e.stopPropagation(); approveDeclaration(d.id); toast('审批通过 · ' + d.id + ' 已审核锁定', 'ok'); }}>通过 · 锁定</button>
                          </div>
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          )}
      </Card>

      <div className="grid g2">
        <Card>
          <CardHead title="已通过 · 已锁定" sub={approved.length + ' 张 · 成本明细已审核锁定'} />
          <div className="tbl-wrap">
            <table className="tbl">
              <thead><tr><th style={{ width: 140 }}>单号</th><th>任务</th><th style={{ width: 130 }} className="r">审核时间</th></tr></thead>
              <tbody>
                {approved.map((d) => (
                  <tr key={d.id} className="clickable" onClick={() => setOpenId(d.id)}>
                    <td className="num" style={{ fontSize: 12.5 }}>{d.id}</td>
                    <td><div className="primary-cell">{d.title}</div><div className="sub-cell">{d.modes.join(' / ')}</div></td>
                    <td className="r num tiny muted">{d.lockedAt || d.timeline[d.timeline.length - 1].t.slice(5)}</td>
                  </tr>
                ))}
                {approved.length === 0 && <tr><td colSpan={3}><Empty icon="checkc" title="暂无记录" /></td></tr>}
              </tbody>
            </table>
          </div>
        </Card>

        <Card>
          <CardHead title="已驳回" sub={rejected.length + ' 张 · 待业务部门重新举证'} />
          <div className="tbl-wrap">
            <table className="tbl">
              <thead><tr><th style={{ width: 140 }}>单号</th><th>任务</th><th style={{ width: 130 }}>回退节点</th><th style={{ width: 88 }} className="r">操作</th></tr></thead>
              <tbody>
                {rejected.map((d) => {
                  const last = d.timeline[d.timeline.length - 1];
                  return (
                    <tr key={d.id}>
                      <td className="num" style={{ fontSize: 12.5 }}>{d.id}</td>
                      <td>
                        <div className="primary-cell">{d.title}</div>
                        <div className="sub-cell clamp2" style={{ maxWidth: 220 }}>{last.note}</div>
                      </td>
                      <td><Tag kind="danger" dot>{last.rejectTo || window.STEPS[Math.max(d.step, 1)].name}</Tag></td>
                      <td className="r"><button className="btn sm ghost" onClick={() => setOpenId(d.id)}>查看</button></td>
                    </tr>
                  );
                })}
                {rejected.length === 0 && <tr><td colSpan={4}><Empty icon="checkc" title="暂无驳回记录" /></td></tr>}
              </tbody>
            </table>
          </div>
        </Card>
      </div>

      {openId && <DeclarationDrawer id={openId} onClose={() => setOpenId(null)} />}

      {rejectFor && <RejectQuick id={rejectFor} onClose={() => setRejectFor(null)} />}
    </div>
  );
}

function RejectQuick({ id, onClose }) {
  const app = useApp();
  const d = app.decls.find((x) => x.id === id);
  const [target, setTarget] = useState(1);
  const [note, setNote] = useState('');
  const changes = d ? computeRejectChanges(d, target) : [];
  return (
    <Modal title={'驳回 ' + id} onClose={onClose}
      footer={<Fragment>
        <Btn size="sm" onClick={onClose}>取消</Btn><div className="spacer" />
        <Btn size="sm" kind="danger" icon="undo" onClick={() => {
          if (!note.trim()) { app.toast('驳回必须填写原因，以确保审计链完整', 'err'); return; }
          app.rejectDeclaration(id, target, note, changes);
          app.toast('已驳回至「' + window.STEPS[target].name + '」', 'warn');
          onClose();
        }}>确认驳回</Btn>
      </Fragment>}>
      <div className="col" style={{ gap: 14 }}>
        <div className="field">
          <label>回退至节点</label>
          <div className="col" style={{ gap: 8 }}>
            {window.STEPS.slice(1, 3).map((s, i) => (
              <label key={s.key} className="row" style={{ gap: 9, padding: '9px 12px', borderRadius: 8, cursor: 'pointer', border: '1px solid ' + (target === i + 1 ? 'var(--primary-line)' : 'var(--line)'), background: target === i + 1 ? 'var(--primary-soft)' : 'transparent' }}>
                <input type="radio" checked={target === i + 1} onChange={() => setTarget(i + 1)} />
                <span style={{ fontSize: 12.5, fontWeight: 500 }}>{s.name}</span>
                <span className="tiny muted">{s.owner} · {s.dept}</span>
              </label>
            ))}
          </div>
        </div>
        <div className="field">
          <label>驳回原因 <span className="hint">必填，写入审计记录</span></label>
          <textarea className="textarea" rows={3} value={note} onChange={(e) => setNote(e.target.value)} placeholder="请说明数据口径问题或需补充的凭据。" />
        </div>
        <div className="field">
          <label>将回滚 {changes.length} 项申报值</label>
          <div className="tiny muted">
            {changes.slice(0, 5).map((c, i) => (
              <div key={i} className="row" style={{ gap: 6, padding: '3px 0' }}>
                <span className="num">{c.product}</span><span>{c.k}</span>
                <del className="num">{c.from}</del><Ico name="arrowRight" size={11} /><ins className="num">{c.to}</ins>
              </div>
            ))}
            {changes.length > 5 && <div>…共 {changes.length} 项</div>}
          </div>
        </div>
      </div>
    </Modal>
  );
}

/* ================================================================
   4. 审计日志
   ================================================================ */
function AuditPage() {
  const app = useApp();
  const { audit, nav } = app;
  const [mod, setMod] = useState('全部');
  const [q, setQ] = useState(nav.params && nav.params.ref || '');
  const [openRow, setOpenRow] = useState(null);

  const mods = ['全部', '成本测算', '数据同步', '差异处理'];
  const rows = audit.filter((a) => (mod === '全部' || a.mod === mod) && (!q || (a.ref + (a.refTitle || '') + a.act + a.who).toLowerCase().includes(q.toLowerCase())));

  const modTag = (m) => m === '成本测算' ? <Tag kind="primary" dot>成本测算</Tag> : m === '数据同步' ? <Tag kind="ok" dot>数据同步</Tag> : <Tag kind="warn" dot>差异处理</Tag>;

  return (
    <div className="scroll tight" data-screen-label="07 审计日志">
      <div style={{ padding: '18px 18px 0' }}>
        <div className="sec-title">
          <h2>审计日志</h2>
          <span className="sub">全流程留痕 · 不可修改或删除 · 支持按单据 / 操作人 / 模块追溯</span>
          <div className="right">
            <Btn size="sm" icon="download">导出 CSV</Btn>
          </div>
        </div>
        <div className="notice info mb12">
          <span className="n-ico"><Ico name="lock" size={15} /></span>
          <div>
            共 <b>{audit.length}</b> 条记录。每次填报、审批、驳回、同步与差异处理都会自动生成一条，包含<b>操作人、部门、时间、变更前后值与意见</b>。
            记录写入后不可编辑或删除，可作为成本口径的追溯依据。
          </div>
        </div>
      </div>

      <div style={{ padding: '0 18px 18px' }}>
        <Card>
          <div className="tbl-toolbar">
            <div className="seg">
              {mods.map((m) => <button key={m} className={mod === m ? 'on' : ''} onClick={() => setMod(m)}>{m} <span className="n">{m === '全部' ? audit.length : audit.filter((a) => a.mod === m).length}</span></button>)}
            </div>
            <div className="spacer" />
            <div style={{ position: 'relative', width: 230 }}>
              <Ico name="search" size={14} style={{ position: 'absolute', left: 9, top: 9, color: 'var(--ink-4)' }} />
              <input className="input" style={{ paddingLeft: 28 }} placeholder="搜索单号 / 操作人 / 动作" value={q} onChange={(e) => setQ(e.target.value)} />
            </div>
            <Chip icon="calendar">2026-09</Chip>
          </div>

          <div className="tbl-wrap">
            <table className="tbl">
              <thead>
                <tr>
                  <th style={{ width: 132 }}>时间</th>
                  <th style={{ width: 104 }}>模块</th>
                  <th style={{ width: 140 }}>关联单据</th>
                  <th>动作</th>
                  <th style={{ width: 148 }}>操作人</th>
                  <th style={{ width: 88 }} className="c">结果</th>
                  <th style={{ width: 72 }} className="r">详情</th>
                </tr>
              </thead>
              <tbody>
                {rows.map((a) => (
                  <Fragment key={a.id + a.t}>
                    <tr className="clickable" onClick={() => setOpenRow(openRow === a.id + a.t ? null : a.id + a.t)}>
                      <td className="num tiny">{a.t}</td>
                      <td>{modTag(a.mod)}</td>
                      <td>
                        <div className="num tiny" style={{ fontWeight: 500 }}>{a.ref}</div>
                        <div className="sub-cell clamp2" style={{ maxWidth: 130 }}>{a.refTitle}</div>
                      </td>
                      <td>
                        <div style={{ fontSize: 12.5, fontWeight: 500, color: a.rejectTo ? 'var(--danger)' : 'var(--ink)' }}>{a.act}</div>
                        {a.note && <div className="sub-cell clamp2" style={{ maxWidth: 320 }}>{a.note}</div>}
                        {(a.changes || []).length > 0 && <div className="provenance" style={{ marginTop: 3 }}><Ico name="swap" size={10} />{a.changes.length} 项字段变更</div>}
                      </td>
                      <td>
                        <div className="row" style={{ gap: 7 }}>
                          <Avatar name={a.who.slice(0, 1)} tint={a.who === '财务部' ? '#2a5bdf' : a.who === '生产车间' ? '#17a2a2' : a.who === '供应部' ? '#c2740a' : '#7e8695'} />
                          <span style={{ fontSize: 12.5 }}>{a.who}</span>
                          <span className="tiny muted">{a.dept}</span>
                        </div>
                      </td>
                      <td className="c">{a.rejectTo ? <Tag kind="danger">驳回</Tag> : a.ok ? <Tag kind="ok">成功</Tag> : <Tag kind="danger">失败</Tag>}</td>
                      <td className="r"><Ico name={openRow === a.id + a.t ? 'up' : 'down'} size={13} style={{ marginLeft: 'auto', color: 'var(--ink-4)' }} /></td>
                    </tr>
                    {openRow === a.id + a.t && (
                      <tr>
                        <td colSpan={7} style={{ background: 'var(--surface-2)', padding: '12px 14px' }}>
                          <div className="grid g2" style={{ gap: 14 }}>
                            <div>
                              <div className="tiny muted mb8">留痕字段</div>
                              <dl className="kv">
                                <dt>记录 ID</dt><dd className="num">{a.id}</dd>
                                <dt>模块</dt><dd>{a.mod}</dd>
                                <dt>动作</dt><dd>{a.act}</dd>
                                <dt>操作人</dt><dd>{a.who}（{a.dept}）</dd>
                                <dt>时间戳</dt><dd className="num">{a.t}:00</dd>
                                <dt>结果</dt><dd>{a.rejectTo ? '已驳回至 ' + a.rejectTo : a.ok ? '操作成功' : '操作失败'}</dd>
                                {a.note && <Fragment><dt>意见 / 说明</dt><dd>{a.note}</dd></Fragment>}
                              </dl>
                            </div>
                            <div>
                              <div className="tiny muted mb8">变更前后值</div>
                              {(a.changes || []).length === 0
                                ? <div className="tiny muted">本次操作无字段级变更。</div>
                                : <div className="tl-diff" style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--surface)' }}>
                                  {a.changes.map((c, i) => (
                                    <div className="row" key={i} style={{ borderTop: i ? '1px solid var(--line)' : 'none' }}>
                                      <span style={{ width: 146, flex: '0 0 auto', padding: '5px 10px', background: 'var(--surface-2)', color: 'var(--ink-3)', fontSize: 12 }}>{[c.product, c.k].filter(Boolean).join(' · ')}</span>
                                      <span style={{ padding: '5px 10px', display: 'flex', gap: 7, alignItems: 'center', fontSize: 12 }}>
                                        {c.from && <del className="num">{c.from}</del>}
                                        {c.from && <Ico name="arrowRight" size={11} style={{ color: 'var(--ink-4)' }} />}
                                        {c.to && <ins className="num">{c.to}</ins>}
                                      </span>
                                    </div>
                                  ))}
                                </div>}
                            </div>
                          </div>
                        </td>
                      </tr>
                    )}
                  </Fragment>
                ))}
                {rows.length === 0 && <tr><td colSpan={7}><Empty icon="search" title="没有匹配的审计记录" /></td></tr>}
              </tbody>
            </table>
          </div>
          <div className="tbl-foot">
            <span>共 {rows.length} 条</span>
            <div className="spacer" />
            <span className="muted">记录不可删改 · 可作为成本口径追溯依据</span>
          </div>
        </Card>
      </div>
    </div>
  );
}

Object.assign(window, {
  TasksPage, DeclarationsPage, DeclarationDrawer, ApprovalsPage, AuditPage, RejectQuick,
  STATUS_META, buildVals, trialMode, computeProdChanges, computeSupplyChanges, computeRejectChanges,
});
