// hrm-payroll.jsx — GIAI ĐOẠN 2: Bảng lương (payroll) trong module HRM.
// Nạp SAU hrm.jsx (dùng useHrm/HRM_* global), TRƯỚC main.jsx.
// Quản lý: tạo/tính lại/chốt bảng lương tháng, xem/sửa/in phiếu. Nhân viên: xem/in phiếu lương của mình.
// Mọi tính toán ở hdata.jsx (hrmComputePayslip) — client-side tức thì; phiếu là snapshot (không đổi khi sửa công sau).

function payMoney(n){ return (Math.round(n || 0)).toLocaleString('vi-VN') + '₫'; }

/* ---------- tab gốc ---------- */
function HrmPayroll(){
  const { canManage } = useHrm();
  return canManage ? <HrmPayrollManager /> : <HrmPayrollSelf />;
}

/* ---------- Quản lý: bảng lương tháng ---------- */
function HrmPayrollManager(){
  const { p, st, data } = useHrm();
  const { lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [ym, setYm] = useTabState('hrm', 'pay.ym', todayVN().slice(0, 7));   // tháng đang xem sống qua chuyển tab
  const [region, setRegion] = React.useState(st.region || 1);
  const [viewSlip, setViewSlip] = React.useState(null);
  const run = data.payruns.find(r => r.period === ym) || null;
  const slips = run ? data.payslips.filter(s => s.payrunId === run.id) : [];
  const shiftMonth = (d) => { const dt = new Date(+ym.slice(0, 4), +ym.slice(5) - 1 + d, 1); setYm(dt.getFullYear() + '-' + String(dt.getMonth() + 1).padStart(2, '0')); };

  // Khoảng ngày của kỳ: kỳ ĐÃ TẠO dùng mốc chụp trong config (ổn định); chưa tạo → theo Thiết lập hiện tại
  const range = hrmPayPeriodRange(ym, run && run.config ? run.config : st);
  // NV thuộc kỳ = đang làm HOẶC nghỉ việc trong/sau kỳ này
  const empsForMonth = () => data.employees.filter(e => e.status !== 'terminated' || (e.endDate && e.endDate >= range.from));
  const generate = () => {
    const cfg = hrmPayConfig(st, region);
    const runId = genPayrunCode(ym);
    p.hrmAdd('payruns', { id:runId, period:ym, region, status:'draft', config:cfg, note:'', createdBy:p.me.id });
    empsForMonth().forEach(e => {
      const detail = hrmComputePayslip(e, ym, data, cfg);
      p.hrmAdd('payslips', { id:genPayslipCode(ym, e.id), payrunId:runId, employeeId:e.id, period:ym, finalized:false, detail });
    });
    toast(L('Đã tạo bảng lương tháng ' + ym.slice(5) + '/' + ym.slice(0, 4), 'Payrun created'), 'success');
  };
  const recompute = () => {
    const cfg = run.config && run.config.minWage ? run.config : hrmPayConfig(st, run.region);
    // NV mới thêm sau khi tạo kỳ → bổ sung phiếu
    const have = new Set(slips.map(s => s.employeeId));
    empsForMonth().forEach(e => {
      const fresh = hrmComputePayslip(e, ym, data, cfg);
      const cur = slips.find(s => s.employeeId === e.id);
      if (cur) p.hrmPatch('payslips', cur.id, { ...cur, detail:{ ...fresh, otherDeductions:cur.detail.otherDeductions || [], note:cur.detail.note || '' } });
      else if (!have.has(e.id)) p.hrmAdd('payslips', { id:genPayslipCode(ym, e.id), payrunId:run.id, employeeId:e.id, period:ym, finalized:false, detail:fresh });
    });
    toast(L('Đã tính lại theo dữ liệu công/OT hiện tại', 'Recomputed'), 'success');
  };
  const finalize = async () => {
    if (!(await confirm({ title:L('Chốt kỳ lương', 'Finalize payrun'), message:L('Chốt bảng lương tháng ' + ym.slice(5) + '/' + ym.slice(0, 4) + '? Sau khi chốt, các phiếu sẽ KHÓA (không sửa/tính lại được) — đây là bản kê trả lương chính thức (Đ95 BLLĐ).', 'Finalize and lock all payslips?'), confirmLabel:L('Chốt', 'Finalize'), danger:false }))) return;
    p.hrmPatch('payruns', run.id, { ...run, status:'finalized' });
    slips.forEach(s => p.hrmPatch('payslips', s.id, { ...s, finalized:true }));
    toast(L('Đã chốt kỳ lương', 'Finalized'), 'success');
  };
  const reopen = async () => {
    if (!(await confirm({ message:L('Mở lại kỳ lương đã chốt để sửa?', 'Reopen finalized payrun?'), confirmLabel:L('Mở lại', 'Reopen') }))) return;
    p.hrmPatch('payruns', run.id, { ...run, status:'draft' });
    slips.forEach(s => p.hrmPatch('payslips', s.id, { ...s, finalized:false }));
    toast(L('Đã mở lại kỳ lương', 'Reopened'), 'success');
  };
  const removeRun = async () => {
    if (!(await confirm({ message:L('Xóa bảng lương tháng này (gồm mọi phiếu)?', 'Delete this payrun and all payslips?'), confirmLabel:L('Xóa', 'Delete') }))) return;
    slips.forEach(s => p.hrmRemove('payslips', s.id));
    p.hrmRemove('payruns', run.id);
    toast(L('Đã xóa bảng lương', 'Deleted'), 'success');
  };
  const exportCsv = () => {
    const H = ['Mã NV', 'Họ tên', 'Kiểu lương', 'Ngày công', 'Lương gốc', 'Phụ cấp', 'Làm thêm giờ', 'Tổng thu nhập', 'BHXH (10.5%)', 'Thuế TNCN', 'Khấu trừ khác', 'Thực lĩnh'];
    const lines = slips.map(s => { const d = s.detail; const other = (d.otherDeductions || []).reduce((a, x) => a + (+x.amount || 0), 0);
      return [s.employeeId, hrmEmpName(data.employees, s.employeeId), (HRM_PAY_TYPES[d.payType] || {}).vi, d.payType === 'byworkday' ? (d.presentDays + d.paidLeaveDays) : d.standardDays - d.unpaidDays, d.baseAmount, d.allowTotal, d.otTotal, d.gross, d.insurance.total, d.pit, other, hrmPayslipNet(d)]; });
    const csv = '﻿' + [H, ...lines].map(r => r.map(v => '"' + String(v == null ? '' : v).replace(/"/g, '""') + '"').join(',')).join('\r\n');
    const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([csv], { type:'text/csv;charset=utf-8' }));
    a.download = 'bang-luong-' + ym + '.csv'; a.click(); URL.revokeObjectURL(a.href);
    toast(L('Đã xuất bảng lương (CSV)', 'Exported'), 'success');
  };

  // employer = phần DN đóng BHXH (21.5% × lương đóng BHXH) → "chi phí công ty" = gross + employer (số đẩy sang Tài chính)
  const tot = slips.reduce((a, s) => { const d = s.detail; a.gross += d.gross; a.ins += d.insurance.total; a.pit += d.pit; a.net += hrmPayslipNet(d);
    a.employer += Math.round((+d.siBase || 0) * HRM_EMPLOYER_INS_RATE); return a; }, { gross:0, ins:0, pit:0, net:0, employer:0 });
  const monthNav = (
    <div className="col g4" style={{ alignItems:'center' }}>
      <div className="flex items-center g8">
        <button className="btn btn-sm" onClick={() => shiftMonth(-1)}><i className="bi bi-chevron-left"></i></button>
        <span className="mono" style={{ fontWeight:700, minWidth:76, textAlign:'center' }}>{ym.slice(5)}/{ym.slice(0, 4)}</span>
        <button className="btn btn-sm" onClick={() => shiftMonth(1)}><i className="bi bi-chevron-right"></i></button>
      </div>
      {range.start > 1 && <span className="subtle mono" style={{ fontSize:'var(--fs-sm)' }}>{fmtDate(range.from)} → {fmtDate(range.to)}</span>}
    </div>
  );

  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{L('Bảng lương', 'Payroll')}</h1>
        <div className="sub">{L('Tính lương từ chấm công + làm thêm giờ theo hệ số luật; khấu trừ BHXH 10.5% + thuế TNCN lũy tiến. Phiếu lương là bản kê trả lương (Đ95 BLLĐ).', 'Payroll from attendance + OT with legal multipliers; SI 10.5% + progressive PIT.')}</div></div>
        {monthNav}</div>

      {!run && (
        <div className="card"><div className="card-body col g14" style={{ alignItems:'flex-start' }}>
          <EmptyState icon="bi-cash-stack" text={L('Chưa tạo bảng lương cho tháng ' + ym.slice(5) + '/' + ym.slice(0, 4), 'No payrun for this month')} />
          <div className="flex g12 items-end wrap" style={{ alignSelf:'stretch' }}>
            <Field label={L('Vùng lương tối thiểu', 'Min-wage region')} hint={L('NĐ 293/2025 — quyết định lương giờ tối thiểu & trần đóng BHXH', 'Decree 293/2025')}>
              <select className="select" style={{ maxWidth:280 }} value={region} onChange={e => setRegion(+e.target.value)}>
                {[1, 2, 3, 4].map(r => <option key={r} value={r}>{L('Vùng ', 'Region ') + r + ' · ' + payMoney(HRM_REGION_MIN_WAGE[r].month) + '/' + L('tháng', 'mo')}</option>)}
              </select>
            </Field>
            <button className="btn btn-primary" onClick={generate}><i className="bi bi-magic"></i>{L('Tạo bảng lương tháng này', 'Generate payrun')}</button>
          </div>
        </div></div>
      )}

      {run && (<>
        <div className="kpi-strip">
          <Kpi label={L('Số nhân viên', 'Employees')} value={slips.length} icon="bi-people" />
          <Kpi label={L('Tổng thu nhập', 'Gross total')} value={vndShort(tot.gross)} icon="bi-cash-stack" />
          <Kpi label={L('Tổng thực lĩnh', 'Net total')} value={vndShort(tot.net)} icon="bi-wallet2" />
          <Kpi label={L('Chi phí công ty', 'Company cost')} value={vndShort(tot.gross + tot.employer)} icon="bi-building"
            delta={L('gồm BHXH DN ' + vndShort(tot.employer), 'incl. employer SI ' + vndShort(tot.employer))} deltaDir="up" />
        </div>
        <div className="toolbar" style={{ justifyContent:'space-between' }}>
          <div className="flex g8 items-center">
            <span className={'badge ' + (run.status === 'finalized' ? 'badge-success' : 'badge-warning')}><i className="dot"></i>{run.status === 'finalized' ? L('Đã chốt', 'Finalized') : L('Nháp', 'Draft')}</span>
            <span className="subtle" style={{ fontSize:'var(--fs-base)' }}>{L('Vùng ' + run.region + ' · lương tối thiểu ' + payMoney((run.config.minWage || {}).month || 0) + '/tháng', 'Region ' + run.region)}</span>
          </div>
          <div className="flex g8 wrap">
            <button className="btn btn-sm" onClick={() => printPayslips(slips.slice().sort((a, b) => a.employeeId < b.employeeId ? -1 : 1), data.employees, p.branding, data.bankAccounts)} disabled={!slips.length}><i className="bi bi-printer"></i>{L('In tất cả', 'Print all')}</button>
            <button className="btn btn-sm" onClick={exportCsv}><i className="bi bi-download"></i>{L('Xuất CSV', 'Export')}</button>
            {run.status !== 'finalized' && <button className="btn btn-sm" onClick={recompute}><i className="bi bi-arrow-repeat"></i>{L('Tính lại', 'Recompute')}</button>}
            {run.status !== 'finalized' && <button className="btn btn-sm btn-danger" onClick={removeRun}><i className="bi bi-trash"></i>{L('Xóa', 'Delete')}</button>}
            {run.status !== 'finalized'
              ? <button className="btn btn-sm btn-primary" onClick={finalize}><i className="bi bi-lock"></i>{L('Chốt kỳ lương', 'Finalize')}</button>
              : <button className="btn btn-sm" onClick={reopen}><i className="bi bi-unlock"></i>{L('Mở lại', 'Reopen')}</button>}
          </div>
        </div>
        <div className="card"><div style={{ overflow:'auto' }}>
          <table className="tbl">
            <thead><tr><th>{L('Nhân viên', 'Employee')}</th><th className="num">{L('Công', 'Days')}</th><th className="num">{L('Lương gốc', 'Base')}</th><th className="num">{L('Phụ cấp', 'Allow.')}</th><th className="num">{L('Làm thêm', 'OT')}</th><th className="num">{L('Tổng TN', 'Gross')}</th><th className="num">{L('BHXH', 'SI')}</th><th className="num">{L('Thuế', 'PIT')}</th><th className="num">{L('Thực lĩnh', 'Net')}</th><th style={{ width:56 }}></th></tr></thead>
            <tbody>
              {slips.length === 0 && <tr><td colSpan="10"><EmptyState icon="bi-inbox" text={L('Không có nhân viên nào', 'No employees')} /></td></tr>}
              {slips.slice().sort((a, b) => a.employeeId < b.employeeId ? -1 : 1).map(s => { const d = s.detail; const days = d.payType === 'byworkday' ? (d.presentDays + d.paidLeaveDays) : (d.standardDays - d.unpaidDays); return (
                <tr key={s.id} style={{ cursor:'pointer' }} onClick={() => setViewSlip(s)}>
                  <td style={{ fontWeight:500 }}>{hrmEmpName(data.employees, s.employeeId)}<div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{(HRM_PAY_TYPES[d.payType] || {})[lang]}</div></td>
                  <td className="num mono">{days}/{d.standardDays}</td>
                  <td className="num mono">{payMoney(d.baseAmount)}</td>
                  <td className="num mono">{d.allowTotal ? payMoney(d.allowTotal) : '—'}</td>
                  <td className="num mono" style={{ color:d.otTotal ? 'var(--primary)' : undefined }}>{d.otTotal ? payMoney(d.otTotal) : '—'}</td>
                  <td className="num mono" style={{ fontWeight:600 }}>{payMoney(d.gross)}</td>
                  <td className="num mono" style={{ color:'var(--error)' }}>{payMoney(d.insurance.total)}</td>
                  <td className="num mono" style={{ color:'var(--error)' }}>{d.pit ? payMoney(d.pit) : '—'}</td>
                  <td className="num mono" style={{ fontWeight:700, color:'var(--success)' }}>{payMoney(hrmPayslipNet(d))}</td>
                  <td style={{ textAlign:'right' }} onClick={e => e.stopPropagation()}><button className="iconbtn" title={L('Xem phiếu', 'View')} onClick={() => setViewSlip(s)}><i className="bi bi-eye" style={{ fontSize:13 }}></i></button></td>
                </tr>); })}
            </tbody>
            {slips.length > 0 && <tfoot><tr style={{ cursor:'default', background:'var(--bg-subtle)' }}>
              <td style={{ fontWeight:700 }}>{L('Tổng', 'Total')}</td><td></td><td></td><td></td><td></td>
              <td className="num mono" style={{ fontWeight:700 }}>{payMoney(tot.gross)}</td>
              <td className="num mono" style={{ fontWeight:700, color:'var(--error)' }}>{payMoney(tot.ins)}</td>
              <td className="num mono" style={{ fontWeight:700, color:'var(--error)' }}>{payMoney(tot.pit)}</td>
              <td className="num mono" style={{ fontWeight:700, color:'var(--success)' }}>{payMoney(tot.net)}</td><td></td>
            </tr></tfoot>}
          </table>
        </div></div>
      </>)}
      {viewSlip && <PayslipModal slip={viewSlip} editable={run && run.status !== 'finalized'} onClose={() => setViewSlip(null)} />}
    </div>
  );
}

/* ---------- Nhân viên: phiếu lương của tôi ---------- */
function HrmPayrollSelf(){
  const { p, myEmp, data } = useHrm();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [viewSlip, setViewSlip] = React.useState(null);
  if (!myEmp) return (
    <div className="page pagefade"><div className="page-head"><h1>{L('Phiếu lương của tôi', 'My payslips')}</h1></div>
      <EmptyState icon="bi-person-x" text={L('Tài khoản chưa liên kết hồ sơ nhân sự.', 'Account not linked to an employee record.')} /></div>
  );
  const mine = data.payslips.filter(s => s.employeeId === myEmp.id && s.finalized).sort((a, b) => a.period < b.period ? 1 : -1);
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{L('Phiếu lương của tôi', 'My payslips')}</h1>
        <div className="sub">{L('Bảng kê lương từng tháng đã được chốt. Bấm để xem chi tiết hoặc in.', 'Finalized monthly payslips.')}</div></div></div>
      <div className="card"><div style={{ overflow:'auto' }}>
        <table className="tbl">
          <thead><tr><th>{L('Kỳ lương', 'Period')}</th><th className="num">{L('Tổng thu nhập', 'Gross')}</th><th className="num">{L('Khấu trừ', 'Deductions')}</th><th className="num">{L('Thực lĩnh', 'Net')}</th><th style={{ width:80 }}></th></tr></thead>
          <tbody>
            {mine.length === 0 && <tr><td colSpan="5"><EmptyState icon="bi-cash-stack" text={L('Chưa có phiếu lương nào được chốt', 'No finalized payslips yet')} /></td></tr>}
            {mine.map(s => { const d = s.detail; return (
              <tr key={s.id} style={{ cursor:'pointer' }} onClick={() => setViewSlip(s)}>
                <td style={{ fontWeight:600 }}>{L('Tháng ', 'Month ') + s.period.slice(5) + '/' + s.period.slice(0, 4)}</td>
                <td className="num mono">{payMoney(d.gross)}</td>
                <td className="num mono" style={{ color:'var(--error)' }}>{payMoney(d.insurance.total + d.pit + (d.otherDeductions || []).reduce((a, x) => a + (+x.amount || 0), 0))}</td>
                <td className="num mono" style={{ fontWeight:700, color:'var(--success)' }}>{payMoney(hrmPayslipNet(d))}</td>
                <td style={{ textAlign:'right' }} onClick={e => e.stopPropagation()}><button className="btn btn-sm" onClick={() => setViewSlip(s)}><i className="bi bi-eye"></i>{L('Xem', 'View')}</button></td>
              </tr>); })}
          </tbody>
        </table>
      </div></div>
      {viewSlip && <PayslipModal slip={viewSlip} editable={false} onClose={() => setViewSlip(null)} />}
    </div>
  );
}

/* ---------- Modal xem/sửa 1 phiếu lương ---------- */
function PayslipModal({ slip, editable, onClose }){
  const { p, data } = useHrm();
  const { lang } = useLang();
  const toast = useToast();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const emp = data.employees.find(e => e.id === slip.employeeId) || { fullName:slip.employeeId };
  const [d, setD] = React.useState(slip.detail);
  const [addDraft, setAddDraft] = React.useState({ name:'', amount:'' });
  const net = hrmPayslipNet(d);
  const otherTotal = (d.otherDeductions || []).reduce((a, x) => a + (+x.amount || 0), 0);
  const save = () => { p.hrmPatch('payslips', slip.id, { ...slip, detail:d }); toast(L('Đã lưu phiếu lương', 'Saved'), 'success'); onClose(); };
  const addDeduction = () => {
    if (!addDraft.name.trim() || !(+addDraft.amount > 0)) { toast(L('Nhập tên và số tiền khấu trừ', 'Enter name & amount'), 'error'); return; }
    setD(s => ({ ...s, otherDeductions:[...(s.otherDeductions || []), { name:addDraft.name.trim(), amount:+addDraft.amount }] }));
    setAddDraft({ name:'', amount:'' });
  };
  const rmDeduction = (i) => setD(s => ({ ...s, otherDeductions:(s.otherDeductions || []).filter((_, j) => j !== i) }));
  const Line = ({ label, value, strong, negative, hint }) => (
    <div className="flex justify-between items-baseline" style={{ padding:'3px 0' }}>
      <span style={{ color:'var(--text-muted)', fontSize:'var(--fs-base)' }}>{label}{hint && <span className="subtle" style={{ fontSize:'var(--fs-sm)', marginLeft:6 }}>{hint}</span>}</span>
      <span className={'mono' + (strong ? '' : '')} style={{ fontWeight:strong ? 700 : 500, color:negative ? 'var(--error)' : (strong ? 'var(--text)' : 'inherit') }}>{negative && value ? '−' : ''}{payMoney(value)}</span>
    </div>
  );
  return (
    <Modal title={L('Phiếu lương ', 'Payslip ') + slip.period.slice(5) + '/' + slip.period.slice(0, 4)
        + (d.periodFrom && !d.periodFrom.endsWith('-01') ? ' (' + fmtDate(d.periodFrom) + ' – ' + fmtDate(d.periodTo) + ')' : '')
        + ' · ' + emp.fullName} width={620} onClose={onClose} staticBackdrop
      footer={<>
        <button className="btn" style={{ marginRight:'auto' }} onClick={() => printPayslip(slip, emp, p.branding, data.bankAccounts)}><i className="bi bi-printer"></i>{L('In phiếu', 'Print')}</button>
        <button className="btn" onClick={onClose}>{L('Đóng', 'Close')}</button>
        {editable && <button className="btn btn-primary" onClick={save}>{L('Lưu', 'Save')}</button>}</>}>
      <div className="col g16">
        <div className="card" style={{ boxShadow:'none', border:'1px solid var(--border-light)' }}><div className="card-body col g2">
          <div style={{ fontWeight:700, color:'var(--success)', marginBottom:4 }}>{L('THU NHẬP', 'INCOME')}</div>
          <Line label={L('Lương theo công', 'Base by attendance')} value={d.baseAmount}
            hint={(HRM_PAY_TYPES[d.payType] || {})[lang] + ' · ' + (d.payType === 'byworkday' ? (d.presentDays + d.paidLeaveDays) : (d.standardDays - d.unpaidDays)) + '/' + d.standardDays + L(' công', ' days')} />
          {(d.allowances || []).map((a, i) => <Line key={i} label={L('Phụ cấp: ', 'Allowance: ') + a.name} value={+a.amount || 0}
            hint={a.taxFree ? (a.taxFreeCap ? L('miễn thuế ≤ ' + payMoney(a.taxFreeCap), 'tax-free ≤ ' + payMoney(a.taxFreeCap)) : L('miễn thuế', 'tax-free')) : null} />)}
          {(d.otLines || []).length > 0 && <Line label={L('Làm thêm giờ', 'Overtime')} value={d.otTotal} hint={d.totalOtHours + 'h'} />}
          {(d.otLines || []).map((o, i) => (
            <div key={i} className="flex justify-between" style={{ padding:'1px 0 1px 16px', fontSize:'var(--fs-sm)', color:'var(--text-muted)' }}>
              <span>{fmtDate(o.date)} · {(HRM_OT_TYPE_LABEL[o.type] || {})[lang]} · {o.dayHours ? o.dayHours + 'h×' + Math.round(o.dayMult * 100) + '%' : ''}{o.nightHours ? (o.dayHours ? ' + ' : '') + o.nightHours + 'h×' + Math.round(o.nightMult * 100) + '% ' + L('đêm', 'night') : ''}</span>
              <span className="mono">{payMoney(o.amount)}</span>
            </div>
          ))}
          <div style={{ borderTop:'1px solid var(--border-light)', marginTop:4, paddingTop:4 }}><Line label={L('Tổng thu nhập', 'Gross income')} value={d.gross} strong /></div>
        </div></div>

        <div className="card" style={{ boxShadow:'none', border:'1px solid var(--border-light)' }}><div className="card-body col g2">
          <div style={{ fontWeight:700, color:'var(--error)', marginBottom:4 }}>{L('KHẤU TRỪ', 'DEDUCTIONS')}</div>
          <Line label={L('BHXH (8%)', 'Social ins. (8%)')} value={d.insurance.bhxh} negative />
          <Line label={L('BHYT (1.5%)', 'Health ins. (1.5%)')} value={d.insurance.bhyt} negative />
          <Line label={L('BHTN (1%)', 'Unemp. ins. (1%)')} value={d.insurance.bhtn} negative />
          <Line label={L('Thuế TNCN', 'Personal income tax')} value={d.pit} negative
            hint={L('TN tính thuế ' + payMoney(d.taxedIncome) + ' · ' + d.taxDependents + ' người phụ thuộc'
              + (d.allowExempt ? ' · phụ cấp miễn thuế ' + payMoney(d.allowExempt) : ''), 'taxed ' + payMoney(d.taxedIncome))} />
          {(d.otherDeductions || []).map((x, i) => (
            <div key={i} className="flex justify-between items-center" style={{ padding:'2px 0' }}>
              <span style={{ color:'var(--text-muted)', fontSize:'var(--fs-base)' }}>{x.name}{editable && <button className="iconbtn" style={{ marginLeft:4 }} onClick={() => rmDeduction(i)}><i className="bi bi-x" style={{ fontSize:12 }}></i></button>}</span>
              <span className="mono" style={{ color:'var(--error)' }}>−{payMoney(x.amount)}</span>
            </div>
          ))}
          {editable && (
            <div className="flex g8 wrap" style={{ marginTop:4, paddingTop:6, borderTop:'1px dashed var(--border-light)' }}>
              <input className="input" style={{ flex:'1 1 140px' }} placeholder={L('Khấu trừ khác (tạm ứng…)', 'Other deduction')} value={addDraft.name} onChange={e => setAddDraft(s => ({ ...s, name:e.target.value }))} />
              <div style={{ flex:'0 0 130px' }}><MoneyInput value={addDraft.amount} onChange={v => setAddDraft(s => ({ ...s, amount:v }))} /></div>
              <button className="btn btn-sm" onClick={addDeduction}><i className="bi bi-plus-lg"></i></button>
            </div>
          )}
          <div style={{ borderTop:'1px solid var(--border-light)', marginTop:4, paddingTop:4 }}><Line label={L('Tổng khấu trừ', 'Total deductions')} value={d.insurance.total + d.pit + otherTotal} strong negative /></div>
        </div></div>

        <div className="flex justify-between items-center" style={{ padding:'10px 14px', borderRadius:8, background:'color-mix(in srgb, var(--success) 10%, transparent)' }}>
          <span style={{ fontWeight:700, fontSize:'var(--fs-lg)' }}>{L('THỰC LĨNH', 'NET PAY')}</span>
          <span className="mono" style={{ fontWeight:800, fontSize:'var(--fs-xl)', color:'var(--success)' }}>{payMoney(net)}</span>
        </div>
        {editable && <Field label={L('Ghi chú phiếu', 'Payslip note')}><input className="input" value={d.note || ''} onChange={e => setD(s => ({ ...s, note:e.target.value }))} /></Field>}
      </div>
    </Modal>
  );
}

/* ---------- In phiếu lương A4 (bản kê trả lương — Đ95.3 BLLĐ) ---------- */
const PAYSLIP_CSS =
    '@page{size:A4;margin:16mm}html{background:#fff;color-scheme:light}'
  + 'body{margin:0;font-family:"Be Vietnam Pro",-apple-system,"Segoe UI",Roboto,sans-serif;color:#0A1730;font-size:13px;line-height:1.5}'
  + '.doc{max-width:800px;margin:0 auto}.doc+.doc{page-break-before:always}@media print{.doc{max-width:none;margin:0;padding:0}}'
  + '.head{display:flex;justify-content:space-between;align-items:flex-start;border-bottom:2px solid #0A1730;padding-bottom:8px}'
  + '.co{font-size:11px;line-height:1.35}.co b{font-size:13px}'
  + 'h1{font-size:1.5em;text-align:center;margin:18px 0 2px}.sub{text-align:center;color:#5b6b85;margin:0 0 16px;font-size:.95em}'
  + '.info{display:grid;grid-template-columns:1fr 1fr;gap:2px 24px;margin-bottom:14px}'
  + '.info div{padding:2px 0;border-bottom:1px dashed #e3e8f0}.info .k{color:#5b6b85}'
  + 'table{width:100%;border-collapse:collapse;margin:6px 0 14px}th,td{text-align:left;padding:5px 8px;border-bottom:1px solid #e3e8f0}'
  + 'th{background:#f2f5fa;font-size:.9em}td.n,th.n{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}'
  + '.sect{font-weight:700;margin:10px 0 2px}.tot td{font-weight:700;border-top:2px solid #0A1730;border-bottom:none}'
  + '.net{display:flex;justify-content:space-between;padding:12px 14px;background:#eef6f0;border-radius:6px;margin:6px 0 20px}'
  + '.net b{font-size:1.25em;color:#1F9D57}.sign{display:flex;justify-content:space-around;margin-top:32px;text-align:center;font-size:.95em}'
  + '.sign div{width:45%}.sign .role{font-weight:600}.sign .hint{color:#5b6b85;font-size:.85em;font-style:italic}.note{font-size:.9em;color:#5b6b85;margin-top:8px}';

// Thân 1 phiếu (không có <head>) — dùng chung cho in đơn lẻ và in hàng loạt.
// bank = TK nhận lương (hrmPrimaryBank) — không có thì bỏ dòng, phiếu vẫn hợp lệ.
function payslipBodyHtml(slip, emp, co, bank){
  const d = slip.detail;
  const m = payMoney;
  const net = hrmPayslipNet(d);
  const otherTotal = (d.otherDeductions || []).reduce((a, x) => a + (+x.amount || 0), 0);
  const days = d.payType === 'byworkday' ? (d.presentDays + d.paidLeaveDays) : (d.standardDays - d.unpaidDays);
  const incomeRows = [
    ['Lương theo công (' + (HRM_PAY_TYPES[d.payType] || {}).vi + ' · ' + days + '/' + d.standardDays + ' công)', d.baseAmount],
    ...(d.allowances || []).map(a => ['Phụ cấp: ' + a.name + (a.taxFree ? (a.taxFreeCap ? ' (miễn thuế ≤ ' + m(a.taxFreeCap) + ')' : ' (miễn thuế)') : ''), +a.amount || 0]),
    ...((d.otLines || []).length ? [['Làm thêm giờ (' + d.totalOtHours + 'h)', d.otTotal]] : []),
  ];
  const deductRows = [
    ['BHXH (8%)', d.insurance.bhxh], ['BHYT (1.5%)', d.insurance.bhyt], ['BHTN (1%)', d.insurance.bhtn],
    ['Thuế TNCN (' + d.taxDependents + ' người phụ thuộc)', d.pit],
    ...(d.otherDeductions || []).map(x => [x.name, +x.amount || 0]),
  ];
  const row = (r) => '<tr><td>' + r[0] + '</td><td class="n">' + m(r[1]) + '</td></tr>';
  return '<div class="doc">'
    + '<div class="head"><div class="co"><b>' + co + '</b><br>Bảng kê trả lương (Điều 95 Bộ luật Lao động)</div>'
    + '<div class="co" style="text-align:right">Mã NV: <b>' + emp.id + '</b></div></div>'
    + '<h1>PHIẾU LƯƠNG</h1><div class="sub">Kỳ lương tháng ' + slip.period.slice(5) + '/' + slip.period.slice(0, 4)
    + (d.periodFrom && !d.periodFrom.endsWith('-01') ? ' (từ ' + fmtDate(d.periodFrom) + ' đến ' + fmtDate(d.periodTo) + ')' : '') + '</div>'
    + '<div class="info"><div><span class="k">Họ và tên:</span> <b>' + emp.fullName + '</b></div><div><span class="k">Chức vụ:</span> ' + (emp.position || '—') + '</div>'
    + '<div><span class="k">Phòng ban:</span> ' + (emp.dept || '—') + '</div><div><span class="k">Ngày công:</span> ' + days + '/' + d.standardDays + '</div>'
    + (bank ? '<div style="grid-column:1/-1"><span class="k">Tài khoản nhận lương:</span> <b>' + bank.accountNo + '</b> · ' + bank.bankName + (bank.branch ? ' — ' + bank.branch : '') + '</div>' : '')
    + '</div>'
    + '<div class="sect">A. Thu nhập</div><table><thead><tr><th>Khoản mục</th><th class="n">Số tiền</th></tr></thead><tbody>'
    + incomeRows.map(row).join('') + '<tr class="tot"><td>Tổng thu nhập</td><td class="n">' + m(d.gross) + '</td></tr></tbody></table>'
    + '<div class="sect">B. Các khoản khấu trừ</div><table><thead><tr><th>Khoản mục</th><th class="n">Số tiền</th></tr></thead><tbody>'
    + deductRows.map(row).join('') + '<tr class="tot"><td>Tổng khấu trừ</td><td class="n">' + m(d.insurance.total + d.pit + otherTotal) + '</td></tr></tbody></table>'
    + '<div class="net"><span style="font-weight:700;font-size:1.1em">THỰC LĨNH</span><b>' + m(net) + '</b></div>'
    + (d.note ? '<div class="note">Ghi chú: ' + d.note + '</div>' : '')
    + '<div class="sign"><div><div class="role">Người lao động</div><div class="hint">(Ký, ghi rõ họ tên)</div></div>'
    + '<div><div class="role">Người lập bảng lương</div><div class="hint">(Ký, ghi rõ họ tên)</div></div></div></div>';
}
function openPrintWindow(title, bodyHtml){
  const html = '<!doctype html><html lang="vi"><head><meta charset="utf-8"><title>' + title + '</title><style>' + PAYSLIP_CSS
    + '</style></head><body>' + bodyHtml + '<script>window.onload=function(){window.print()}<\/script></body></html>';
  const w = window.open('', '_blank');
  if (!w) return;
  w.document.write(html); w.document.close();
}
function printPayslip(slip, emp, branding, banks){
  const co = (branding && branding.name) || 'S.W.O Technology';
  openPrintWindow('Phiếu lương ' + slip.period + ' — ' + emp.fullName, payslipBodyHtml(slip, emp, co, hrmPrimaryBank(banks, emp.id)));
}
// In HÀNG LOẠT: mỗi phiếu 1 trang A4 (page-break-before giữa các .doc).
function printPayslips(slips, employees, branding, banks){
  const co = (branding && branding.name) || 'S.W.O Technology';
  const byId = {}; (employees || []).forEach(e => { byId[e.id] = e; });
  const body = slips.map(s => payslipBodyHtml(s, byId[s.employeeId] || { id:s.employeeId, fullName:s.employeeId }, co, hrmPrimaryBank(banks, s.employeeId))).join('');
  openPrintWindow('Phiếu lương ' + (slips[0] ? slips[0].period : '') + ' (' + slips.length + ' NV)', body);
}

Object.assign(window, { HrmPayroll, PayslipModal, printPayslip, printPayslips });
