// cdata.jsx — data + helpers for the Contracts and Finance modules.
// Loaded after data.jsx (uses CUSTOMERS/CONTACTS indirectly via codes).

const TODAY = '2026-06-07';
function vnd(n){ return (Math.round(n || 0)).toLocaleString('vi-VN') + '\u20ab'; }
function vndShort(n){ // compact: 216.000.000 -> 216tr / 1.2 tỷ
  n = Math.round(n || 0);
  if (Math.abs(n) >= 1e9) return (n / 1e9).toLocaleString('vi-VN', { maximumFractionDigits:2 }) + ' tỷ';
  if (Math.abs(n) >= 1e6) return Math.round(n / 1e6).toLocaleString('vi-VN') + ' tr';
  return n.toLocaleString('vi-VN');
}
function daysBetween(a, b){ return Math.round((new Date(b) - new Date(a)) / 86400000); }
function monthKey(d){ return (d || '').slice(0, 7); } // YYYY-MM

// ---- Định dạng ngày HIỂN THỊ (lưu trữ vẫn luôn ISO 'YYYY-MM-DD') ----
// window.__DATEFMT: 'dmy' (mặc định, DD/MM/YYYY) | 'mdy' (MM/DD/YYYY) | 'iso' (YYYY-MM-DD).
// main.jsx cập nhật theo: user pref → mặc định hệ thống (app_settings) → 'dmy'.
try { window.__DATEFMT = window.__DATEFMT || (JSON.parse(localStorage.getItem('swo-portal-datefmt') || 'null')) || 'dmy'; } catch (e) { window.__DATEFMT = 'dmy'; }
function fmtDate(v){
  if (!v) return '';
  const s = String(v); const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(s);
  if (!m) return s;                                   // không phải ISO → trả nguyên
  const y = m[1], mo = m[2], d = m[3], rest = s.slice(10);   // giữ phần giờ nếu có (vd ' 12:30')
  const f = window.__DATEFMT || 'dmy';
  if (f === 'mdy') return mo + '/' + d + '/' + y + rest;
  if (f === 'iso') return y + '-' + mo + '-' + d + rest;
  return d + '/' + mo + '/' + y + rest;
}

/* ---- catalogs ---- */
const CONTRACT_CATEGORIES = [
  { id:'mua-ban', vi:'Hợp đồng mua bán', en:'Sales contract' },
  { id:'dich-vu', vi:'Hợp đồng dịch vụ', en:'Service contract' },
  { id:'bao-tri', vi:'Hợp đồng bảo trì', en:'Maintenance contract' },
  { id:'thue', vi:'Hợp đồng thuê', en:'Lease contract' },
  { id:'hop-tac', vi:'Hợp đồng hợp tác', en:'Partnership contract' },
];
const CONTRACT_STATUS = {
  draft:     { vi:'Nháp',         en:'Draft',     cls:'badge-neutral' },
  active:    { vi:'Hiệu lực',     en:'Active',    cls:'badge-success' },
  expiring:  { vi:'Sắp hết hạn',  en:'Expiring',  cls:'badge-warning' },
  warranty:  { vi:'Bảo hành',     en:'Warranty',  cls:'badge-violet' },
  completed: { vi:'Hoàn thành',   en:'Completed', cls:'badge-info' },
  cancelled: { vi:'Đã hủy',       en:'Cancelled', cls:'badge-error' },
};
const TERM_TYPES = { once:{ vi:'Một lần', en:'One-time' }, term:{ vi:'Kỳ hạn', en:'Term' } };
// Chiều hợp đồng: bán = SWO thu tiền (doanh thu) · mua = SWO trả phí (chi/giá vốn).
const CONTRACT_DIRECTIONS = {
  sell: { vi:'Bán', en:'Sell', cls:'badge-info' },
  buy:  { vi:'Mua', en:'Buy',  cls:'badge-neutral' },
};
function contractDir(c){ return c.direction === 'buy' ? 'buy' : 'sell'; }
// Số hợp đồng tự cấp (HĐ bán) theo template. Token: DD MM YY YYYY · {KH}=tên viết tắt KH · {SEQ}=số thứ tự.
const DEFAULT_CONTRACT_NO_FORMAT = 'DDMM/YYYY/SWO-{KH}';
function formatContractNo(template, opts){
  opts = opts || {};
  const d = new Date(opts.date || TODAY);
  const DD = String(d.getDate()).padStart(2, '0');
  const MM = String(d.getMonth() + 1).padStart(2, '0');
  const YYYY = String(d.getFullYear());
  return (template || DEFAULT_CONTRACT_NO_FORMAT)
    .replace(/YYYY/g, YYYY).replace(/YY/g, YYYY.slice(2))
    .replace(/DD/g, DD).replace(/MM/g, MM)
    .replace(/\{KH\}/gi, (opts.kh || '').trim())
    .replace(/\{SEQ\}/gi, opts.seq != null ? String(opts.seq).padStart(3, '0') : '');
}

// customer addresses (master-data extension keyed by customer code)
const CUSTOMER_ADDR = {
  KIZUNA:'Lô A, KCN Tân Kim, H. Cần Giuộc, Long An',
  KYANON:'KMS Building, 234/1 Đ. 3/2, Q.10, TP.HCM',
  TSD:'72 Lê Thánh Tôn, Q.1, TP.HCM',
  SEMITEC:'KCN Long Hậu, H. Cần Giuộc, Long An',
  NGOCMINH:'15 Trường Chinh, Q. Tân Bình, TP.HCM',
  VTIDC:'Tòa nhà Viettel, 285 CMT8, Q.10, TP.HCM',
  AIPOWER:'8 Nguyễn Huệ, Q.1, TP.HCM',
  ONECAD:'27 Lê Lợi, Q.1, TP.HCM',
  DASSVN:'KCN Sóng Thần, Dĩ An, Bình Dương',
  NGTSGROUP:'Aeon Mall, Tân Phú, TP.HCM',
  HQGTECH:'123 Nguyễn Văn Linh, Q.7, TP.HCM',
  BAUMANN:'Industriestraße 5, Stuttgart, Germany',
};

function it(name, unit, qty, price, vat){ return { name, unit, qty, price, vat }; }
function pay(no, amount, dueDate, condition, paid, paidDate){ return { no, amount, dueDate, condition, paid:!!paid, paidDate:paidDate || null }; }

/* ---- contracts ---- */
const CONTRACTS = [
  { code:'HD-2026-001', category:'mua-ban', signDate:'2026-01-15', customer:'KIZUNA', rep:'LH-0001', termType:'once', startDate:'2026-01-15', endDate:null, status:'active', remindBefore:30,
    items:[ it('AppSheet Enterprise — License triển khai', 'gói', 1, 120000000, 8), it('Triển khai & đào tạo vận hành', 'gói', 1, 80000000, 8) ],
    schedule:[ pay(1, 108000000, '2026-01-20', 'Tạm ứng 50% khi ký', true, '2026-01-21'), pay(2, 108000000, '2026-03-15', 'Sau nghiệm thu', true, '2026-03-18') ] },

  { code:'HD-2026-002', category:'dich-vu', signDate:'2026-01-02', customer:'SEMITEC', rep:'LH-0002', termType:'term', startDate:'2026-01-01', endDate:'2026-12-31', status:'active', remindBefore:30,
    items:[ it('Dịch vụ vận hành hệ thống quản lý linh kiện', 'tháng', 12, 25000000, 8) ],
    schedule:[ pay(1, 81000000, '2026-01-05', 'Quý I — tạm ứng', true, '2026-01-06'), pay(2, 81000000, '2026-04-05', 'Quý II', true, '2026-04-07'), pay(3, 81000000, '2026-07-05', 'Quý III', false), pay(4, 81000000, '2026-10-05', 'Quý IV', false) ] },

  { code:'HD-2026-003', category:'dich-vu', signDate:'2026-02-10', customer:'KYANON', rep:'LH-0006', termType:'term', startDate:'2026-02-15', endDate:'2026-08-15', status:'active', remindBefore:30,
    items:[ it('Tổng đài Cloud VoIP — 50 máy nhánh', 'tháng', 6, 9000000, 8), it('Phí khởi tạo & cấu hình', 'lần', 1, 12000000, 8) ],
    schedule:[ pay(1, 38880000, '2026-02-18', 'Tạm ứng khi ký', true, '2026-02-19'), pay(2, 32400000, '2026-05-15', 'Giữa kỳ', true, '2026-05-16'), pay(3, 12000000, '2026-08-10', 'Cuối kỳ', false) ] },

  { code:'HD-2026-004', category:'bao-tri', signDate:'2025-06-20', customer:'NGOCMINH', rep:'LH-0008', termType:'term', startDate:'2025-06-25', endDate:'2026-06-24', status:'expiring', remindBefore:30,
    items:[ it('PC Health Check Annual — 80 thiết bị', 'năm', 1, 96000000, 8) ],
    schedule:[ pay(1, 51840000, '2025-06-25', 'Tạm ứng 50%', true, '2025-06-26'), pay(2, 51840000, '2025-12-25', 'Đợt 2', true, '2025-12-28') ] },

  { code:'HD-2025-018', category:'dich-vu', signDate:'2025-07-01', customer:'VTIDC', rep:'LH-0013', termType:'term', startDate:'2025-07-01', endDate:'2026-06-30', status:'expiring', remindBefore:30,
    items:[ it('Thuê hạ tầng Cloud & vận hành', 'tháng', 12, 18000000, 8) ],
    schedule:[ pay(1, 116640000, '2025-07-03', 'Nửa năm đầu', true, '2025-07-05'), pay(2, 116640000, '2026-01-03', 'Nửa năm sau', true, '2026-01-04') ] },

  { code:'HD-2026-005', category:'mua-ban', signDate:'2026-05-28', customer:'TSD', rep:'LH-0004', termType:'once', startDate:'2026-05-28', endDate:null, status:'draft', remindBefore:30,
    items:[ it('Giải pháp số hóa quy trình AppSheet', 'gói', 1, 150000000, 8), it('Tích hợp API & dữ liệu', 'gói', 1, 45000000, 8) ],
    schedule:[ pay(1, 105300000, '2026-06-15', 'Tạm ứng 50% khi ký', false), pay(2, 105300000, '2026-08-15', 'Sau nghiệm thu', false) ] },

  { code:'HD-2026-006', category:'thue', signDate:'2026-03-05', customer:'HQGTECH', rep:'LH-0011', termType:'term', startDate:'2026-03-10', endDate:'2027-03-09', status:'active', remindBefore:45,
    items:[ it('Thuê phần mềm quản lý vận hành (SaaS)', 'tháng', 12, 14000000, 8) ],
    schedule:[ pay(1, 90720000, '2026-03-12', 'Nửa năm đầu', true, '2026-03-13'), pay(2, 90720000, '2026-09-12', 'Nửa năm sau', false) ] },

  { code:'HD-2026-007', category:'mua-ban', signDate:'2026-04-18', customer:'DASSVN', rep:'LH-0003', termType:'once', startDate:'2026-04-18', endDate:null, status:'active', remindBefore:30,
    items:[ it('Hệ thống quản lý logistics — License', 'gói', 1, 220000000, 8), it('Phần cứng đầu đọc & thiết bị', 'bộ', 10, 6500000, 10), it('Triển khai tại hiện trường', 'gói', 1, 60000000, 8) ],
    schedule:[ pay(1, 178200000, '2026-04-22', 'Tạm ứng 50%', true, '2026-04-23'), pay(2, 106920000, '2026-06-30', 'Sau lắp đặt 30%', false), pay(3, 71280000, '2026-08-30', 'Nghiệm thu 20%', false) ] },

  { code:'HD-2026-008', category:'hop-tac', signDate:'2026-02-01', customer:'AIPOWER', rep:'LH-0010', termType:'term', startDate:'2026-02-01', endDate:'2026-07-31', status:'active', remindBefore:30,
    items:[ it('Hợp tác phát triển module AI', 'giai đoạn', 2, 70000000, 8) ],
    schedule:[ pay(1, 75600000, '2026-02-05', 'Giai đoạn 1', true, '2026-02-06'), pay(2, 75600000, '2026-06-20', 'Giai đoạn 2', false) ] },

  { code:'HD-2025-009', category:'dich-vu', signDate:'2025-09-10', customer:'ONECAD', rep:null, termType:'once', startDate:'2025-09-10', endDate:null, status:'completed', remindBefore:30,
    items:[ it('Tư vấn chuyển đổi số', 'gói', 1, 40000000, 8) ],
    schedule:[ pay(1, 43200000, '2025-09-15', 'Thanh toán 1 lần', true, '2025-09-16') ] },

  /* ---- HĐ MUA (SWO mua hàng/dịch vụ → trả phí → Chi). Đối tác = Nhà cung cấp (NCC-*). ---- */
  { code:'HD-2026-101', category:'mua-ban', direction:'buy', signDate:'2026-05-02', customer:'NCC-001', rep:null, termType:'once', startDate:'2026-05-02', endDate:null, status:'active', remindBefore:30,
    items:[ it('License nền tảng (mua từ NCC để triển khai)', 'gói', 1, 90000000, 8) ],
    schedule:[ pay(1, 97200000, '2026-05-08', 'Thanh toán khi nhận license', true, '2026-05-09') ] },
  { code:'HD-2026-102', category:'dich-vu', direction:'buy', signDate:'2026-04-10', customer:'NCC-002', rep:null, termType:'term', startDate:'2026-04-10', endDate:'2026-10-09', status:'active', remindBefore:30,
    items:[ it('Thuê hạ tầng máy chủ (đầu vào)', 'tháng', 6, 12000000, 8) ],
    schedule:[ pay(1, 38880000, '2026-04-12', 'Nửa kỳ đầu', true, '2026-04-13'), pay(2, 38880000, '2026-07-12', 'Nửa kỳ sau', false) ] },

  /* ---- HĐ BÁN LẠI (mua-đi-bán-lại): bán cho KH, giá vốn = HĐ mua HD-2026-101 ---- */
  { code:'HD-2026-103', category:'mua-ban', direction:'sell', signDate:'2026-05-15', customer:'KIZUNA', rep:'LH-0001', termType:'once', startDate:'2026-05-15', endDate:null, status:'active', remindBefore:30, costContracts:['HD-2026-101'],
    items:[ it('License nền tảng (bán lại) + triển khai', 'gói', 1, 150000000, 8) ],
    schedule:[ pay(1, 81000000, '2026-05-20', 'Tạm ứng 50% khi ký', true, '2026-05-21'), pay(2, 81000000, '2026-07-15', 'Sau nghiệm thu', false) ] },
];

function contractTotals(c){
  let sub = 0, vat = 0;
  (c.items || []).forEach(i => { const line = i.qty * i.price; sub += line; vat += line * (i.vat / 100); });
  return { sub, vat, total:sub + vat };
}
function contractStatus(c){
  if (c.status === 'draft' || c.status === 'cancelled') return c.status;
  // Đã kết thúc → chuyển bảo hành: còn hạn = 'warranty' (tím), hết hạn = 'completed'.
  if (c.warranty && c.warranty.end) return daysBetween(TODAY, c.warranty.end) >= 0 ? 'warranty' : 'completed';
  if (c.status === 'completed') return 'completed';
  if (c.termType === 'term' && c.endDate){
    const d = daysBetween(TODAY, c.endDate);
    if (d < 0) return 'completed';
    if (d <= (c.remindBefore || 30)) return 'expiring';
  }
  return 'active';
}
function warrantyInfo(c){
  if (!c.warranty || !c.warranty.end) return null;
  const daysLeft = daysBetween(TODAY, c.warranty.end);
  return { months:c.warranty.months, start:c.warranty.start, end:c.warranty.end, daysLeft, active:daysLeft >= 0 };
}
// Lợi nhuận HĐ bán = doanh thu (HĐ bán) − giá vốn (các HĐ mua liên kết qua costContracts).
function dealMargin(sell, all){
  const revenue = contractTotals(sell).total;
  const costList = (sell.costContracts || []).map(code => (all || []).find(c => c.code === code)).filter(Boolean);
  const cost = costList.reduce((s, c) => s + contractTotals(c).total, 0);
  const profit = revenue - cost;
  return { revenue, cost, profit, marginPct: revenue ? Math.round(profit / revenue * 100) : 0, costList };
}

/* ---- expenses (Chi) ---- */
const EXPENSE_CATEGORIES = [
  { id:'luong', vi:'Lương & phúc lợi', en:'Payroll' },
  { id:'thiet-bi', vi:'Thiết bị & phần cứng', en:'Hardware' },
  { id:'thue-vp', vi:'Thuê văn phòng', en:'Office rent' },
  { id:'ha-tang', vi:'Hạ tầng & Cloud', en:'Infrastructure' },
  { id:'marketing', vi:'Marketing', en:'Marketing' },
  { id:'doi-tac', vi:'Chi trả đối tác', en:'Vendor payment' },
  { id:'khac', vi:'Chi phí khác', en:'Other' },
];
function exp(id, date, cat, desc, amount, status){ return { id, date, cat, desc, amount, status }; }
const EXPENSES = [
  exp('C001','2026-01-05','thue-vp','Thuê văn phòng Q1', 90000000,'paid'),
  exp('C002','2026-01-31','luong','Lương tháng 1', 220000000,'paid'),
  exp('C003','2026-02-12','thiet-bi','Mua 10 màn hình Dell + 5 laptop', 145000000,'paid'),
  exp('C004','2026-02-28','luong','Lương tháng 2', 220000000,'paid'),
  exp('C005','2026-03-08','ha-tang','Gia hạn Google Cloud + AppSheet', 38000000,'paid'),
  exp('C006','2026-03-31','luong','Lương tháng 3', 235000000,'paid'),
  exp('C007','2026-04-05','thue-vp','Thuê văn phòng Q2', 90000000,'paid'),
  exp('C008','2026-04-20','marketing','Chiến dịch quảng bá Q2', 55000000,'paid'),
  exp('C009','2026-04-30','luong','Lương tháng 4', 235000000,'paid'),
  exp('C010','2026-05-15','doi-tac','Chi trả đối tác Viettel IDC', 60000000,'paid'),
  exp('C011','2026-05-31','luong','Lương tháng 5', 240000000,'paid'),
  exp('C012','2026-06-06','ha-tang','Hạ tầng Cloud tháng 6', 18000000,'pending'),
  exp('C013','2026-06-15','luong','Lương tháng 6 (dự kiến)', 240000000,'pending'),
  exp('C014','2026-06-20','thiet-bi','Bổ sung thiết bị đầu đọc', 32000000,'pending'),
];

/* =====================================================================
   SUPPORT (Hỗ trợ sau bán) — ticket 4 bước + QR định danh theo hợp đồng.
   Adapt từ design v4: model hiện tại dùng `c.customer` (không có `c.supplier`)
   và KHÔNG có `c.warranty` → hết hạn QR suy ra từ endDate / signDate+12 tháng.
   ===================================================================== */
function addDaysStr(dateStr, n){ const d = new Date(dateStr); d.setDate(d.getDate() + n); return d.toISOString().slice(0, 10); }
function addMonthsStr(dateStr, n){ const d = new Date(dateStr); d.setMonth(d.getMonth() + n); return d.toISOString().slice(0, 10); }

const SUPPORT_STATUS = {
  open:    { vi:'Tiếp nhận',  en:'Open',     cls:'badge-info',    icon:'bi-inbox' },
  process: { vi:'Đang xử lý', en:'Process',  cls:'badge-warning', icon:'bi-gear' },
  result:  { vi:'Có kết quả', en:'Result',   cls:'badge-violet',  icon:'bi-clipboard2-check' },
  report:  { vi:'Đã báo cáo', en:'Report',   cls:'badge-success', icon:'bi-file-earmark-bar-graph' },
};
const SUPPORT_FLOW = ['open', 'process', 'result', 'report'];
const SUPPORT_PRIORITIES = {
  low:    { vi:'Thấp',       en:'Low',    cls:'badge-neutral', slaH:48 },
  med:    { vi:'Trung bình', en:'Medium', cls:'badge-info',    slaH:24 },
  high:   { vi:'Cao',        en:'High',   cls:'badge-warning', slaH:8 },
  urgent: { vi:'Khẩn cấp',   en:'Urgent', cls:'badge-error',   slaH:4 },
};
const SUPPORT_CHANNELS = {
  qr:     { vi:'QR định danh', en:'QR form', icon:'bi-qr-code' },
  phone:  { vi:'Điện thoại',   en:'Phone',   icon:'bi-telephone' },
  email:  { vi:'Email',        en:'Email',   icon:'bi-envelope' },
  onsite: { vi:'Tại chỗ',      en:'On-site', icon:'bi-geo-alt' },
  chat:   { vi:'Chat / Zalo',  en:'Chat',    icon:'bi-chat-dots' },
};
const SUPPORT_CATEGORIES = [
  { id:'su-co',     vi:'Sự cố',             en:'Incident' },
  { id:'bao-tri',   vi:'Bảo trì',           en:'Maintenance' },
  { id:'huong-dan', vi:'Hướng dẫn sử dụng', en:'How-to' },
  { id:'yeu-cau',   vi:'Yêu cầu thay đổi',  en:'Change request' },
  { id:'khac',      vi:'Khác',              en:'Other' },
];
function supportCatLabel(id, lang){ const c = SUPPORT_CATEGORIES.find(x => x.id === id); return c ? c[lang] : id; }
function ev(date, by, note, type){ return { date, by, note, type:type || 'note' }; }

// Mock fallback (chỉ dùng khi DB chưa migrate; sau migrate → DB.loadTickets trả mảng thật, kể cả rỗng).
const TICKETS = [
  { code:'SP-2026-001', customer:'KIZUNA', contract:'HD-2026-001', contact:'LH-0001', channel:'qr', category:'su-co', priority:'high', status:'process',
    title:'Lỗi đồng bộ dữ liệu AppSheet sau cập nhật', desc:'Một số bản ghi không đồng bộ lên Google Sheet sau khi cập nhật phiên bản mới.', assignee:'Phạm Quốc Bảo', createdDate:'2026-06-03', updatedDate:'2026-06-05',
    interactions:[ ev('2026-06-03', 'KH (QR)', 'Khách gửi ticket qua QR định danh trên hợp đồng.', 'create'), ev('2026-06-03', 'Phạm Quốc Bảo', 'Tiếp nhận, xác nhận tái hiện được lỗi trên môi trường test.', 'assign'), ev('2026-06-05', 'Phạm Quốc Bảo', 'Đang phối hợp Google để rà soát quota API.', 'note') ] },
  { code:'SP-2026-002', customer:'SEMITEC', contract:'HD-2026-002', contact:'LH-0002', channel:'phone', category:'huong-dan', priority:'med', status:'report',
    title:'Hướng dẫn cấu hình phân quyền cho phòng QC', desc:'Khách cần thêm 3 tài khoản phòng QC với quyền chỉ xem.', assignee:'Lê Thu Hà', createdDate:'2026-05-20', updatedDate:'2026-05-23',
    interactions:[ ev('2026-05-20', 'KH', 'Gọi điện yêu cầu hỗ trợ phân quyền.', 'create'), ev('2026-05-21', 'Lê Thu Hà', 'Đã tạo 3 tài khoản, gửi hướng dẫn qua email.', 'note') ],
    result:{ date:'2026-05-22', by:'Lê Thu Hà', summary:'Hoàn tất cấu hình 3 tài khoản QC quyền chỉ xem, khách xác nhận hoạt động tốt.' },
    report:{ date:'2026-05-23', to:'SEMITEC', summary:'Gửi biên bản hỗ trợ & nghiệm thu cho khách. Đóng ticket.' } },
  { code:'SP-2026-003', customer:'KYANON', contract:'HD-2026-003', contact:'LH-0006', channel:'qr', category:'su-co', priority:'urgent', status:'open',
    title:'Tổng đài VoIP rớt cuộc gọi giờ cao điểm', desc:'Từ 9–11h sáng cuộc gọi bị ngắt, ảnh hưởng chăm sóc khách hàng.', assignee:'Phạm Quốc Bảo', createdDate:'2026-06-06', updatedDate:'2026-06-06',
    interactions:[ ev('2026-06-06', 'KH (QR)', 'Gửi ticket khẩn qua QR, đính kèm log cuộc gọi.', 'create') ] },
];

function ticketSla(t){
  const pr = SUPPORT_PRIORITIES[t.priority] || SUPPORT_PRIORITIES.med;
  const days = Math.max(1, Math.ceil(pr.slaH / 24));
  const due = addDaysStr(t.createdDate, days);
  const openStage = t.status === 'open' || t.status === 'process';
  const overdue = openStage && daysBetween(due, TODAY) > 0;
  return { due, slaH:pr.slaH, overdue, daysLeft:daysBetween(TODAY, due) };
}

/* ---- QR định danh theo hợp đồng ---- */
function qrToken(code){ let h = 2166136261; for (let i = 0; i < code.length; i++){ h ^= code.charCodeAt(i); h = Math.imul(h, 16777619); } return ('0000000' + (h >>> 0).toString(36).toUpperCase()).slice(-7); }
function qrIntakeUrl(c){ return 'https://swo.vn/s/' + c.code + '?k=' + qrToken(c.code); }
function qrExpiryOf(c){ return c.endDate || (c.warranty && c.warranty.end) || addMonthsStr(c.signDate, 12); }
function qrInfoOf(c){
  const expiry = qrExpiryOf(c);
  const daysLeft = daysBetween(TODAY, expiry);
  const cancelled = contractStatus(c) === 'cancelled';
  return { token:qrToken(c.code), url:qrIntakeUrl(c), expiry, daysLeft, active:!cancelled && daysLeft >= 0, expired:daysLeft < 0 };
}
// Bên đối tác của hợp đồng (HĐ bán → khách hàng; HĐ mua → nhà cung cấp).
function contractParty(c){
  const store = contractDir(c) === 'buy' ? window.VendorStore : window.CustomerStore;
  return (store && store.byCode && store.byCode(c.customer)) || { name:c.customer, shortName:c.customer };
}
// HĐ bán đủ điều kiện có QR hỗ trợ (không tính HĐ mua / nháp / hủy).
function supportableContracts(list){ return (list || CONTRACTS).filter(c => contractDir(c) === 'sell' && c.status !== 'draft' && contractStatus(c) !== 'cancelled'); }

/* ---- Chu kỳ thanh toán HĐ kỳ hạn (thuê dịch vụ theo năm, thu theo tháng/quý/6T/12T) ----
   Lịch thanh toán (schedule) vẫn là nguồn chân lý — chu kỳ dùng để SINH lịch + gia hạn nối tiếp. */
const CONTRACT_BILLING_CYCLES = {
  once:    { vi:'Một lần',    en:'One-off',     months:0 },
  month:   { vi:'Theo tháng', en:'Monthly',     months:1 },
  quarter: { vi:'Theo quý',   en:'Quarterly',   months:3 },
  half:    { vi:'6 tháng',    en:'Semi-annual', months:6 },
  year:    { vi:'12 tháng',   en:'Annual',      months:12 },
};
// Sinh các đợt theo chu kỳ trong [start, end): thu ĐẦU mỗi kỳ (trả trước), chia đều từ tổng
// (làm tròn nghìn, đợt CUỐI nhận phần lẻ — luôn khớp tổng). startNo: nối tiếp số đợt khi gia hạn.
function genCycleInstallments(cycle, startDate, endDate, total, startNo){
  const m = (CONTRACT_BILLING_CYCLES[cycle] || {}).months;
  if (!m || !startDate || !endDate || endDate <= startDate) return [];
  const dates = [];
  for (let d = startDate; d < endDate; d = addMonthsStr(d, m)) dates.push(d);
  const n = dates.length;
  const base = Math.floor(total / n / 1000) * 1000;
  const lbl = (d) => { const [y, mo] = d.split('-'); return cycle === 'quarter' ? 'Quý ' + (Math.floor((+mo - 1) / 3) + 1) + '/' + y : 'Kỳ ' + mo + '/' + y; };
  // Đợt cuối nhận phần lẻ — LÀM TRÒN đồng (tổng HĐ có thể lẻ thập phân do VAT).
  return dates.map((d, i) => ({ no:(startNo || 1) + i, amount:i === n - 1 ? Math.round(total - base * (n - 1)) : base, dueDate:d, condition:lbl(d), paid:false, paidDate:null }));
}

Object.assign(window, {
  TODAY, vnd, vndShort, daysBetween, monthKey, addDaysStr, addMonthsStr, fmtDate,
  CONTRACT_BILLING_CYCLES, genCycleInstallments,
  CONTRACT_CATEGORIES, CONTRACT_STATUS, TERM_TYPES, CONTRACT_DIRECTIONS, CUSTOMER_ADDR,
  CONTRACTS, contractTotals, contractStatus, contractDir, dealMargin, formatContractNo, DEFAULT_CONTRACT_NO_FORMAT, warrantyInfo,
  EXPENSE_CATEGORIES, EXPENSES,
  SUPPORT_STATUS, SUPPORT_FLOW, SUPPORT_PRIORITIES, SUPPORT_CHANNELS, SUPPORT_CATEGORIES, supportCatLabel, ev,
  TICKETS, ticketSla, qrToken, qrIntakeUrl, qrExpiryOf, qrInfoOf, contractParty, supportableContracts,
});
