403Webshell
Server IP : 172.67.189.169  /  Your IP : 216.73.216.59
Web Server : Apache/2
System : Linux cat167-197.static.lnwhostname.com 5.10.0-20-amd64 #1 SMP Debian 5.10.158-2 (2022-12-13) x86_64
User : paphayomho ( 1042)
PHP Version : 5.6.40
Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
MySQL : ON  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /home/paphayomho/domains/paphayomhospital.go.th/private_html/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/paphayomho/domains/paphayomhospital.go.th/private_html//Account.html
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ระบบบริหารลูกหนี้ โรงพยาบาลป่าพะยอม</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"></script>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Sarabun',Arial,sans-serif;background:#f0f4f8;color:#2d3748;font-size:14px}
.header{background:linear-gradient(135deg,#1a5276,#2980b9);color:#fff;padding:14px 24px;display:flex;align-items:center;gap:14px;box-shadow:0 2px 8px rgba(0,0,0,.25)}
.header-logo{width:52px;height:52px;background:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0}
.header-title{line-height:1.3}
.header-title h1{font-size:18px;font-weight:700}
.header-title p{font-size:12px;opacity:.85}
.nav{background:#fff;border-bottom:2px solid #e2e8f0;display:flex;padding:0 16px;overflow-x:auto}
.nav-tab{padding:12px 18px;cursor:pointer;font-size:13px;font-weight:600;color:#718096;border-bottom:3px solid transparent;white-space:nowrap;transition:all .2s}
.nav-tab.active{color:#1a5276;border-bottom-color:#2980b9}
.nav-tab:hover:not(.active){color:#2d3748;background:#f7fafc}
.content{padding:20px;max-width:1400px;margin:0 auto}
.panel{display:none}.panel.active{display:block}

/* Cards */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;margin-bottom:20px}
.card{background:#fff;border-radius:10px;padding:16px 18px;box-shadow:0 1px 4px rgba(0,0,0,.08);border-left:4px solid #2980b9}
.card.green{border-left-color:#27ae60}.card.orange{border-left-color:#e67e22}.card.red{border-left-color:#e74c3c}.card.purple{border-left-color:#8e44ad}
.card-label{font-size:11px;color:#718096;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}
.card-value{font-size:24px;font-weight:700;color:#2d3748}
.card-sub{font-size:11px;color:#a0aec0;margin-top:4px}

/* Tables */
.table-wrap{overflow-x:auto;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08)}
table{width:100%;border-collapse:collapse;background:#fff}
thead th{background:#1a5276;color:#fff;padding:9px 12px;text-align:left;font-size:12px;white-space:nowrap}
thead th.num{text-align:right}
tbody tr:nth-child(even){background:#f7fafc}
tbody tr:hover{background:#ebf5fb}
tbody td{padding:7px 12px;font-size:12.5px;border-bottom:1px solid #e2e8f0;white-space:nowrap}
tbody td.num{text-align:right;font-variant-numeric:tabular-nums}
.tfoot td{background:#dbeafe;font-weight:700;padding:8px 12px;font-size:12.5px;border-top:2px solid #93c5fd}
.tfoot td.num{text-align:right}

/* Section */
.section{background:#fff;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,.08);margin-bottom:20px;overflow:hidden}
.section-header{background:#1a5276;color:#fff;padding:10px 16px;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:space-between}
.section-body{padding:16px}
.section-header .badge{background:rgba(255,255,255,.25);border-radius:12px;padding:2px 10px;font-size:11px}

/* Upload area */
.upload-area{border:2px dashed #90cdf4;border-radius:10px;padding:28px;text-align:center;cursor:pointer;transition:all .2s;background:#ebf8ff}
.upload-area:hover{border-color:#2980b9;background:#dbeafe}
.upload-area input{display:none}
.upload-icon{font-size:36px;margin-bottom:8px}
.upload-title{font-weight:700;color:#1a5276;margin-bottom:4px}
.upload-sub{font-size:12px;color:#718096}

/* Status badges */
.badge-matched{background:#d1fae5;color:#065f46;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600}
.badge-partial{background:#fef3c7;color:#92400e;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600}
.badge-none{background:#fee2e2;color:#991b1b;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600}
.badge-deny{background:#ede9fe;color:#5b21b6;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600}

/* Charts row */
.charts-row{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px}
@media(max-width:768px){.charts-row{grid-template-columns:1fr}}
.chart-box{background:#fff;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,.08);padding:16px}
.chart-box h3{font-size:13px;font-weight:700;color:#2d3748;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid #e2e8f0}

/* Filters */
.filters{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px;align-items:flex-end}
.filters label{font-size:12px;color:#4a5568;font-weight:600;display:block;margin-bottom:3px}
.filters select,.filters input{padding:6px 10px;border:1px solid #cbd5e0;border-radius:6px;font-size:13px;background:#fff;min-width:140px}
.btn{padding:7px 16px;border:none;border-radius:6px;cursor:pointer;font-size:13px;font-weight:600;transition:all .2s}
.btn-primary{background:#2980b9;color:#fff}.btn-primary:hover{background:#1a5276}
.btn-success{background:#27ae60;color:#fff}.btn-success:hover{background:#1e8449}
.btn-warning{background:#e67e22;color:#fff}.btn-warning:hover{background:#d35400}
.btn-sm{padding:4px 10px;font-size:12px}
.btn-outline{background:#fff;border:1px solid #cbd5e0;color:#4a5568}.btn-outline:hover{background:#f7fafc}
.btn-danger{background:#e74c3c;color:#fff;border:none}.btn-danger:hover{background:#c0392b}
.storage-tag{display:inline-block;font-size:11px;padding:3px 10px;border-radius:10px;background:rgba(255,255,255,.15);color:rgba(255,255,255,.8);white-space:nowrap}
.storage-tag.saved{background:rgba(39,174,96,.35);color:#a7f3d0}
.storage-tag.warn{background:rgba(231,76,60,.35);color:#fca5a5}

/* Import log */
.import-log{max-height:200px;overflow-y:auto;background:#1a202c;border-radius:8px;padding:12px;font-family:monospace;font-size:12px;color:#a0aec0}
.log-ok{color:#68d391}.log-warn{color:#f6ad55}.log-err{color:#fc8181}

/* Accounting report */
.report-page{background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:24px;max-width:900px;margin:0 auto;font-size:13px}
.report-title{text-align:center;font-weight:700;font-size:16px;margin-bottom:4px}
.report-sub{text-align:center;font-size:13px;color:#4a5568;margin-bottom:16px}
.report-line{display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px dotted #e2e8f0}
.report-section-title{background:#1a5276;color:#fff;padding:5px 10px;font-weight:700;margin:10px 0 6px;font-size:12px}
.report-total{display:flex;justify-content:space-between;padding:6px 0;font-weight:700;border-top:2px solid #2d3748}

/* Responsive */
@media(max-width:600px){.content{padding:10px}.cards{grid-template-columns:1fr 1fr}}
</style>
</head>
<body>

<div class="header">
  <div class="header-logo">🏥</div>
  <div class="header-title">
    <h1>ระบบบริหารลูกหนี้ค่ารักษาพยาบาล</h1>
    <p>โรงพยาบาลป่าพะยอม &nbsp;|&nbsp; รหัส 11422 &nbsp;|&nbsp; จังหวัดพัทลุง</p>
  </div>
  <div style="margin-left:auto;display:flex;gap:8px;align-items:center">
    <span id="storage-tag" class="storage-tag">💾 ยังไม่มีข้อมูล</span>
    <button class="btn btn-outline btn-sm" onclick="exportData()">💾 Export JSON</button>
    <button class="btn btn-outline btn-sm" onclick="document.getElementById('importJsonFile').click()">📂 Import JSON</button>
    <button class="btn btn-sm" style="background:rgba(231,76,60,.7);color:#fff;border:none;cursor:pointer" onclick="if(confirm('ล้างข้อมูลทั้งหมดออกจากเครื่อง (localStorage) ด้วย?')){store.impRecords=[];store.eclaimRecords=[];store.importLog=[];store.impFileCount=0;store.eclaimFileCount=0;localStorage.removeItem('hospital_ar_data_v2');refreshAll();log('🗑️ ล้างข้อมูลทั้งหมดแล้ว','warn');document.getElementById('storage-tag').textContent='💾 ยังไม่มีข้อมูล';document.getElementById('storage-tag').className='storage-tag';}">🗑️ ล้างทั้งหมด</button>
    <input type="file" id="importJsonFile" accept=".json" style="display:none" onchange="importData(this)">
  </div>
</div>

<nav class="nav">
  <div class="nav-tab active" onclick="showTab('dashboard')">📊 แดชบอร์ด</div>
  <div class="nav-tab" onclick="showTab('import')">📥 นำเข้าข้อมูล</div>
  <div class="nav-tab" onclick="showTab('monthly')">📋 ตั้งลูกหนี้รายเดือน</div>
  <div class="nav-tab" onclick="showTab('matching')">🔗 จับคู่ REP</div>
  <div class="nav-tab" onclick="showTab('accounting')">📑 รายงานบัญชี</div>
</nav>

<div class="content">

<!-- ===== DASHBOARD ===== -->
<div id="panel-dashboard" class="panel active">
  <div class="cards" id="dash-cards">
    <div class="card"><div class="card-label">ลูกหนี้ทั้งหมด (ราย)</div><div class="card-value" id="d-total-count">0</div><div class="card-sub" id="d-total-amt">฿ 0</div></div>
    <div class="card green"><div class="card-label">จับคู่ REP ครบแล้ว</div><div class="card-value" id="d-matched-count">0</div><div class="card-sub" id="d-matched-amt">฿ 0</div></div>
    <div class="card orange"><div class="card-label">ค้างชำระ (มี REP บางส่วน)</div><div class="card-value" id="d-partial-count">0</div><div class="card-sub" id="d-partial-amt">฿ 0</div></div>
    <div class="card red"><div class="card-label">ยังไม่มี REP</div><div class="card-value" id="d-none-count">0</div><div class="card-sub" id="d-none-amt">฿ 0</div></div>
    <div class="card purple"><div class="card-label">ไฟล์ IMP ที่นำเข้า</div><div class="card-value" id="d-imp-files">0</div><div class="card-sub" id="d-eclaim-files">REP 0 ฉบับ</div></div>
  </div>

  <div class="charts-row">
    <div class="chart-box">
      <h3>📊 ยอดลูกหนี้แยกตามสิทธิ (บาท)</h3>
      <canvas id="chart-by-right" height="200"></canvas>
    </div>
    <div class="chart-box">
      <h3>📈 สถานะการจับคู่ REP</h3>
      <canvas id="chart-status" height="200"></canvas>
    </div>
  </div>

  <div class="section">
    <div class="section-header">
      📅 สรุปรายเดือน แยกตามสิทธิ
      <span class="badge" id="dash-month-badge">0 เดือน</span>
    </div>
    <div class="section-body" style="padding:0">
      <div class="table-wrap">
        <table id="dash-monthly-table">
          <thead><tr>
            <th>เดือน</th><th>สิทธิ</th>
            <th class="num">จำนวนราย</th>
            <th class="num">ยอดตั้งหนี้ (บาท)</th>
            <th class="num">ผู้ป่วยชำระ</th>
            <th class="num">สิทธิชำระ</th>
            <th class="num">ยอด REP รับได้</th>
            <th class="num">ส่วนต่าง</th>
            <th>สถานะ</th>
          </tr></thead>
          <tbody id="dash-monthly-body"><tr><td colspan="9" style="text-align:center;padding:24px;color:#a0aec0">ยังไม่มีข้อมูล — กรุณานำเข้าไฟล์ IMP ก่อน</td></tr></tbody>
        </table>
      </div>
    </div>
  </div>
</div>

<!-- ===== IMPORT ===== -->
<div id="panel-import" class="panel">
  <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:20px">
    <div class="section">
      <div class="section-header">📄 นำเข้าไฟล์ IMP (ตัวตั้งลูกหนี้)</div>
      <div class="section-body">
        <div class="upload-area" onclick="document.getElementById('impFiles').click()">
          <input type="file" id="impFiles" accept=".xls,.xlsx" multiple onchange="handleImpUpload(this)">
          <div class="upload-icon">📊</div>
          <div class="upload-title">คลิกเพื่อเลือกไฟล์ IMP</div>
          <div class="upload-sub">รองรับ .xls / .xlsx (หลายไฟล์พร้อมกัน)<br>เช่น IMP_66_IPD_04_รายงานผู้ป่วยใน.xls</div>
        </div>
        <div style="margin-top:12px;font-size:12px;color:#4a5568">
          <b>คอลัมน์ที่ใช้:</b> HN, AN, หมายเลขบัตร, วันที่Admit/จำหน่าย, สิทธิการรักษา, ผู้ป่วยชำระ, สิทธิชำระ, ค่าใช้จ่ายทั้งหมด
        </div>
        <div id="imp-file-list" style="margin-top:10px"></div>
      </div>
    </div>
    <div class="section">
      <div class="section-header">📋 นำเข้าไฟล์ eclaim REP</div>
      <div class="section-body">
        <div class="upload-area" onclick="document.getElementById('eclaimFiles').click()">
          <input type="file" id="eclaimFiles" accept=".xls,.xlsx" multiple onchange="handleEclaimUpload(this)">
          <div class="upload-icon">🗂️</div>
          <div class="upload-title">คลิกเพื่อเลือกไฟล์ eclaim REP</div>
          <div class="upload-sub">รองรับ .xls / .xlsx (หลายไฟล์/งวด)<br>เช่น eclaim_11422_OP_25681231_xxx.xls</div>
        </div>
        <div style="margin-top:12px;font-size:12px;color:#4a5568">
          <b>คอลัมน์ที่ใช้:</b> REP No., HN, AN, PID, ชดเชยสุทธิ, HC, AE, INST, IP, DMIS, DRUG
        </div>
        <div id="eclaim-file-list" style="margin-top:10px"></div>
      </div>
    </div>
  </div>

  <div class="section">
    <div class="section-header">
      🖥️ ประวัติการนำเข้า
      <button class="btn btn-sm" style="background:rgba(255,255,255,.2);color:#fff;border:none;cursor:pointer" onclick="clearLog()">ล้าง log</button>
    </div>
    <div class="section-body" style="padding:10px">
      <div class="import-log" id="import-log"><span class="log-ok">พร้อมรับข้อมูล...</span></div>
    </div>
  </div>

  <div class="section">
    <div class="section-header">📊 สรุปข้อมูลที่นำเข้าแล้ว</div>
    <div class="section-body">
      <div class="cards" style="margin-bottom:0">
        <div class="card"><div class="card-label">IMP Records</div><div class="card-value" id="imp-record-count">0</div></div>
        <div class="card"><div class="card-label">eclaim Records</div><div class="card-value" id="eclaim-record-count">0</div></div>
        <div class="card green"><div class="card-label">จำนวน REP No.</div><div class="card-value" id="rep-count">0</div></div>
        <div class="card orange"><div class="card-label">เดือนข้อมูล</div><div class="card-value" id="month-count">0</div></div>
      </div>
      <div style="margin-top:12px;display:flex;gap:8px">
        <button class="btn btn-warning" onclick="if(confirm('ล้างข้อมูล IMP ทั้งหมด?\n(ลบทุกรายการและประวัติไฟล์ทั้งหมด)')){store.impRecords=[];store.importLog=[];store.impFiles=[];store.impFileCount=0;refreshAll();log('ล้างข้อมูล IMP ทั้งหมดแล้ว','warn')}">🗑️ ล้างทั้งหมด IMP</button>
        <button class="btn btn-warning" onclick="if(confirm('ล้างข้อมูล eclaim ทั้งหมด?\n(ลบทุกรายการและประวัติไฟล์ทั้งหมด)')){store.eclaimRecords=[];store.eclaimFiles=[];store.eclaimFileCount=0;refreshAll();log('ล้างข้อมูล eclaim ทั้งหมดแล้ว','warn')}">🗑️ ล้างทั้งหมด eclaim</button>
      </div>
    </div>
  </div>

  <!-- eclaim records table -->
  <div class="section" style="margin-top:20px">
    <div class="section-header">
      📋 รายการ eclaim / REP ที่นำเข้าแล้ว
      <span class="badge" id="eclaim-records-badge">0 รายการ</span>
    </div>
    <div class="section-body">
      <div class="filters" style="margin-bottom:10px">
        <div>
          <label>ค้นหา HN / REP No / AN</label>
          <input type="text" id="eclaim-search" placeholder="กรอก HN หรือ REP No..." oninput="renderEclaimTable()" style="min-width:200px">
        </div>
        <div>
          <label>กองทุนหลัก</label>
          <input type="text" id="eclaim-fund-filter" placeholder="เช่น HC16, AE04..." oninput="renderEclaimTable()" style="min-width:140px">
        </div>
      </div>
      <div class="table-wrap">
        <table>
          <thead><tr>
            <th>#</th>
            <th>REP No.</th>
            <th>HN</th><th>AN</th>
            <th>วันที่บริการ</th>
            <th>กองทุนหลัก</th>
            <th>กองทุนย่อย</th>
            <th class="num">ยอดชดเชย</th>
            <th>ไฟล์ต้นทาง</th>
            <th></th>
          </tr></thead>
          <tbody id="eclaim-records-body">
            <tr><td colspan="10" style="text-align:center;padding:16px;color:#a0aec0">ยังไม่มีข้อมูล</td></tr>
          </tbody>
        </table>
      </div>
      <div id="eclaim-records-more" style="text-align:center;padding:8px;font-size:12px;color:#718096"></div>
    </div>
  </div>
</div>

<!-- ===== MONTHLY ===== -->
<div id="panel-monthly" class="panel">
  <div class="filters">
    <div>
      <label>เดือน/ปี</label>
      <select id="filter-month" onchange="renderMonthly()">
        <option value="">-- ทุกเดือน --</option>
      </select>
    </div>
    <div>
      <label>สิทธิการรักษา</label>
      <select id="filter-right" onchange="renderMonthly()">
        <option value="">-- ทุกสิทธิ --</option>
      </select>
    </div>
    <div>
      <label>ประเภท</label>
      <select id="filter-ptype" onchange="renderMonthly()">
        <option value="">-- ทุกประเภท --</option>
        <option value="IPD">IPD (ผู้ป่วยใน)</option>
        <option value="OPD">OPD (ผู้ป่วยนอก)</option>
      </select>
    </div>
    <div style="margin-top:18px">
      <button class="btn btn-success" onclick="exportMonthlyExcel()">📥 Export Excel</button>
    </div>
  </div>

  <div class="section">
    <div class="section-header">
      📋 สรุปรายเดือน แยกตามสิทธิ
      <span class="badge" id="monthly-count-badge">0 รายการ</span>
    </div>
    <div class="section-body" style="padding:0">
      <div class="table-wrap">
        <table id="monthly-summary-table">
          <thead><tr>
            <th title="IPD=เดือนจำหน่าย  OPD=เดือนรับบริการ">เดือน ℹ️</th>
            <th>สิทธิการรักษา</th>
            <th class="num">จำนวนราย</th>
            <th class="num">ค่าใช้จ่ายทั้งหมด</th>
            <th class="num">ผู้ป่วยชำระ</th>
            <th class="num">สิทธิชำระ (ตั้งหนี้)</th>
            <th class="num">ยอด REP รับ</th>
            <th class="num">ส่วนต่าง</th>
            <th class="num">% รับได้</th>
          </tr></thead>
          <tbody id="monthly-summary-body">
            <tr><td colspan="9" style="text-align:center;padding:24px;color:#a0aec0">ยังไม่มีข้อมูล</td></tr>
          </tbody>
          <tfoot id="monthly-summary-foot"></tfoot>
        </table>
      </div>
    </div>
  </div>

  <div class="section">
    <div class="section-header">
      👤 รายชื่อผู้ป่วย
      <span class="badge" id="patient-list-badge">0 ราย</span>
    </div>
    <div class="section-body" style="padding:0">
      <div class="table-wrap">
        <table id="patient-list-table">
          <thead><tr>
            <th>HN</th><th>AN</th><th>ชื่อ-สกุล</th>
            <th>วันจำหน่าย</th><th>สิทธิการรักษา</th>
            <th class="num">ผู้ป่วยชำระ</th>
            <th class="num">สิทธิชำระ</th>
            <th class="num">ค่าใช้จ่ายรวม</th>
            <th class="num">ยอด REP</th>
            <th>สถานะ</th>
            <th></th>
          </tr></thead>
          <tbody id="patient-list-body">
            <tr><td colspan="11" style="text-align:center;padding:24px;color:#a0aec0">ยังไม่มีข้อมูล</td></tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</div>

<!-- ===== MATCHING ===== -->
<div id="panel-matching" class="panel">
  <div class="filters">
    <div>
      <label>เดือน</label>
      <select id="match-filter-month" onchange="renderMatching()">
        <option value="">-- ทุกเดือน --</option>
      </select>
    </div>
    <div>
      <label>สถานะ</label>
      <select id="match-filter-status" onchange="renderMatching()">
        <option value="">-- ทุกสถานะ --</option>
        <option value="matched">✅ จับคู่ได้ครบ</option>
        <option value="partial">⚠️ บางส่วน</option>
        <option value="none">❌ ไม่มี REP</option>
        <option value="deny">🚫 DENY</option>
      </select>
    </div>
    <div>
      <label>สิทธิ</label>
      <select id="match-filter-right" onchange="renderMatching()">
        <option value="">-- ทุกสิทธิ --</option>
      </select>
    </div>
    <div style="margin-top:18px">
      <button class="btn btn-success" onclick="exportMatchingExcel()">📥 Export Excel</button>
    </div>
  </div>

  <div class="cards" style="margin-bottom:16px">
    <div class="card green"><div class="card-label">จับคู่ครบ</div><div class="card-value" id="m-matched">0</div></div>
    <div class="card orange"><div class="card-label">บางส่วน</div><div class="card-value" id="m-partial">0</div></div>
    <div class="card red"><div class="card-label">ไม่มี REP</div><div class="card-value" id="m-none">0</div></div>
    <div class="card purple"><div class="card-label">DENY</div><div class="card-value" id="m-deny">0</div></div>
  </div>

  <div class="section">
    <div class="section-header">🔗 รายละเอียดการจับคู่ REP พร้อมแยกกองทุน</div>
    <div class="section-body" style="padding:0">
      <div class="table-wrap">
        <table id="matching-table">
          <thead><tr>
            <th>HN</th><th>ชื่อ-สกุล</th><th>วันจำหน่าย</th>
            <th>สิทธิ</th><th>REP No.</th>
            <th class="num">สิทธิชำระ</th>
            <th class="num">ชดเชยสุทธิ</th>
            <th class="num">HC</th>
            <th class="num">AE</th>
            <th class="num">INST</th>
            <th class="num">IP/OP</th>
            <th class="num">DMIS</th>
            <th class="num">DRUG</th>
            <th class="num">ส่วนต่าง</th>
            <th>สถานะ</th>
          </tr></thead>
          <tbody id="matching-body">
            <tr><td colspan="15" style="text-align:center;padding:24px;color:#a0aec0">ยังไม่มีข้อมูล</td></tr>
          </tbody>
          <tfoot id="matching-foot"></tfoot>
        </table>
      </div>
    </div>
  </div>

  <div class="section">
    <div class="section-header">📊 สรุปกองทุน แยกตามสิทธิ</div>
    <div class="section-body" style="padding:0">
      <div class="table-wrap">
        <table id="fund-summary-table">
          <thead><tr>
            <th>สิทธิ</th>
            <th class="num">ราย</th>
            <th class="num">ตั้งหนี้ (สิทธิชำระ)</th>
            <th class="num">ชดเชยสุทธิ</th>
            <th class="num">HC ค่าใช้จ่ายสูง</th>
            <th class="num">AE ฉุกเฉิน</th>
            <th class="num">INST อวัยวะ</th>
            <th class="num">IP ผู้ป่วยใน</th>
            <th class="num">DMIS โรคเฉพาะ</th>
            <th class="num">DRUG ยา</th>
            <th class="num">ส่วนต่าง</th>
          </tr></thead>
          <tbody id="fund-summary-body"></tbody>
        </table>
      </div>
    </div>
  </div>

  <div class="section">
    <div class="section-header">🗂️ สรุปกองทุนหลัก (รหัสกองทุน) — แยกตามสิทธิ</div>
    <div class="section-body" style="padding:0">
      <div class="table-wrap">
        <table id="fund-code-table">
          <thead><tr>
            <th>สิทธิ</th>
            <th>รหัสกองทุนหลัก</th>
            <th>หมวดกองทุน</th>
            <th>กองทุนย่อย (Sub Fund)</th>
            <th class="num">ราย</th>
            <th class="num">ยอดชดเชย (บาท)</th>
          </tr></thead>
          <tbody id="fund-code-body"></tbody>
          <tfoot id="fund-code-foot"></tfoot>
        </table>
      </div>
    </div>
  </div>
</div>

<!-- ===== ACCOUNTING ===== -->
<div id="panel-accounting" class="panel">
  <div class="filters">
    <div>
      <label>เดือน/ปี</label>
      <select id="acc-month" onchange="renderAccounting()">
        <option value="">-- เลือกเดือน --</option>
      </select>
    </div>
    <div>
      <label>สิทธิ</label>
      <select id="acc-right" onchange="renderAccounting()">
        <option value="">-- ทุกสิทธิ --</option>
      </select>
    </div>
    <div style="margin-top:18px;display:flex;gap:8px">
      <button class="btn btn-primary" onclick="renderAccounting()">🔄 แสดงรายงาน</button>
      <button class="btn btn-success" onclick="printAccounting()">🖨️ พิมพ์/PDF</button>
    </div>
  </div>

  <div id="accounting-report"></div>
</div>

</div><!-- end content -->

<script>
// =============================================
// DATA STORE
// =============================================
const store = {
  impRecords: [],
  eclaimRecords: [],
  importLog: [],
  impFileCount: 0,
  eclaimFileCount: 0,
  impFiles: [],    // [{name, added, total, date}] ไฟล์ IMP ที่นำเข้าแล้ว
  eclaimFiles: [], // [{name, added, total, date}] ไฟล์ eclaim ที่นำเข้าแล้ว
};

// =============================================
// UTILITIES
// =============================================
function fmt(n) {
  if (n === null || n === undefined || n === '' || n === '-') return '-';
  const num = parseFloat(n);
  if (isNaN(num)) return '-';
  return num.toLocaleString('th-TH', {minimumFractionDigits:2, maximumFractionDigits:2});
}

function fmtInt(n) {
  if (!n) return '0';
  return parseInt(n).toLocaleString('th-TH');
}

function log(msg, type='ok') {
  const el = document.getElementById('import-log');
  const cls = type === 'ok' ? 'log-ok' : type === 'warn' ? 'log-warn' : 'log-err';
  const ts = new Date().toLocaleTimeString('th-TH');
  el.innerHTML += `\n<span class="${cls}">[${ts}] ${msg}</span>`;
  el.scrollTop = el.scrollHeight;
}

function clearLog() {
  document.getElementById('import-log').innerHTML = '<span class="log-ok">Log ถูกล้างแล้ว</span>';
}

function parseDateStr(s) {
  if (!s) return null;
  s = String(s).trim();
  // Thai Buddhist Era: DD/MM/YYYY where YYYY might be BE (2568 = 2025 CE)
  let m = s.match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/);
  if (m) {
    let y = parseInt(m[3]);
    if (y > 2400) y -= 543; // Convert BE to CE
    return new Date(y, parseInt(m[2]) - 1, parseInt(m[1]));
  }
  // ISO date or Excel serial
  let d = new Date(s);
  if (!isNaN(d)) return d;
  return null;
}

function monthKey(d) {
  if (!d) return 'ไม่ระบุ';
  return `${d.getFullYear() + 543}-${String(d.getMonth() + 1).padStart(2,'0')}`;
}

function monthLabel(key) {
  if (key === 'ไม่ระบุ') return 'ไม่ระบุ';
  const [y, m] = key.split('-');
  const months = ['','ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.','ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.'];
  return `${months[parseInt(m)]} ${y}`;
}

function normalizeRight(s) {
  if (!s) return 'อื่นๆ';
  s = String(s).trim();
  if (s.includes('ถ้วนหน้า') || s.includes('หลักประกัน') || s.includes('UC') || s.includes('30 บาท')) return 'UC (บัตรทอง)';
  if (s.includes('OFC')) return 'OFC';
  if (s.includes('ต้นสังกัด') || s.includes('ราชการ') || s.includes('ข้าราชการ')) return 'ข้าราชการ/รัฐวิสาหกิจ';
  if (s.includes('ประกันสังคม') || s.includes('SSS')) return 'ประกันสังคม';
  if (s.includes('อุบัติเหตุ') || s.includes('ฉุกเฉิน') || s.includes('AE')) return 'อุบัติเหตุ/ฉุกเฉิน';
  if (s.includes('เอกชน') || s.includes('ชีวิต')) return 'เอกชน/ประกันชีวิต';
  if (s.includes('ต่างด้าว') || s.includes('แรงงาน')) return 'แรงงานต่างด้าว';
  if (s.includes('สถานะ') || s.includes('UNK') || s.includes('ไม่มีสิทธิ') || s.includes('ปัญหาสถานะ')) return 'บุคคลมีปัญหาสถานะ';
  if (s.includes('พรบ') || s.includes('ประกันรถ') || s.includes('รถยนต์')) return 'พรบ.รถ';
  if (s.includes('อปท') || s.includes('ท้องถิ่น')) return 'อปท.';
  if (s.includes('ชำระเงิน') || s.includes('เงินสด') || s.includes('จ่ายเอง')) return 'ชำระเงินเอง';
  return s.length > 40 ? s.substring(0,40) + '...' : s;
}

// =============================================
// ACCOUNT CODE MAPPING — ผังบัญชีภาคสุขภาพ สำนักงานปลัดกระทรวงสาธารณสุข พ.ศ. 2560
// โครงสร้าง: { code, name } ต่อ rightNorm → ptype → subType
// =============================================
const ACCOUNT_MAP = {
  // ─── UC / บัตรทอง (สปสช.) ──────────────────────────────
  'UC (บัตรทอง)': {
    OPD: {
      default:  { code:'1102050101.201', name:'ลูกหนี้ค่ารักษา UC-OP ใน CUP' },
      nout_in:  { code:'1102050101.203', name:'ลูกหนี้ค่ารักษา UC-OP นอก CUP (ในจังหวัดสังกัด สธ.)' },
      nout_out: { code:'1102050101.204', name:'ลูกหนี้ค่ารักษา UC-OP นอก CUP (ต่างจังหวัดสังกัด สธ.)' },
      pp:       { code:'1102050101.209', name:'ลูกหนี้ค่ารักษาด้านการสร้างเสริมสุขภาพและป้องกันโรค (P&P)' },
      cr:       { code:'1102050101.216', name:'ลูกหนี้ค่ารักษา UC-OP บริการเฉพาะ (CR)' },
      refer:    { code:'1102050101.222', name:'ลูกหนี้ค่ารักษา OP-Refer' },
      // HC/AE/DMI ใน REP map → CR (บริการเฉพาะ)
      hc:   { code:'1102050101.216', name:'ลูกหนี้ค่ารักษา UC-OP บริการเฉพาะ (CR) - HC' },
      ae:   { code:'1102050101.216', name:'ลูกหนี้ค่ารักษา UC-OP บริการเฉพาะ (CR) - AE' },
      dmis: { code:'1102050101.216', name:'ลูกหนี้ค่ารักษา UC-OP บริการเฉพาะ (CR) - DMI' },
      drug: { code:'1102050101.201', name:'ลูกหนี้ค่ายา UC-OP' },
      inst: { code:'1102050101.201', name:'ลูกหนี้ค่าอวัยวะเทียม UC-OP' },
    },
    IPD: {
      default:  { code:'1102050101.202', name:'ลูกหนี้ค่ารักษา UC-IP' },
      cr:       { code:'1102050101.217', name:'ลูกหนี้ค่ารักษา UC-IP บริการเฉพาะ (CR)' },
      hc:   { code:'1102050101.217', name:'ลูกหนี้ค่ารักษา UC-IP บริการเฉพาะ (CR) - HC' },
      ae:   { code:'1102050101.217', name:'ลูกหนี้ค่ารักษา UC-IP บริการเฉพาะ (CR) - AE' },
      dmis: { code:'1102050101.217', name:'ลูกหนี้ค่ารักษา UC-IP บริการเฉพาะ (CR) - DMI' },
      drug: { code:'1102050101.202', name:'ลูกหนี้ค่ายา UC-IP' },
      inst: { code:'1102050101.202', name:'ลูกหนี้ค่าอวัยวะเทียม UC-IP' },
    },
  },
  // ─── อุบัติเหตุ/ฉุกเฉิน (ระบบปฏิบัติการ) ─────────────
  'อุบัติเหตุ/ฉุกเฉิน': {
    OPD: { default: { code:'1102050101.109', name:'ลูกหนี้-ระบบปฏิบัติการฉุกเฉิน' } },
    IPD: { default: { code:'1102050101.109', name:'ลูกหนี้-ระบบปฏิบัติการฉุกเฉิน' } },
  },
  // ─── ประกันสังคม (SSO) ──────────────────────────────────
  'ประกันสังคม': {
    OPD: {
      default:   { code:'1102050101.301', name:'ลูกหนี้ค่ารักษาประกันสังคม OP-เครือข่าย' },
      nout_sdhz: { code:'1102050101.303', name:'ลูกหนี้ค่ารักษาประกันสังคม OP-นอกเครือข่าย สังกัด สป.สธ.' },
      replace:   { code:'1102050101.307', name:'ลูกหนี้ค่ารักษาประกันสังคม-กองทุนทดแทน' },
      e72:       { code:'1102050101.308', name:'ลูกหนี้ค่ารักษาประกันสังคม 72 ชั่วโมงแรก' },
      hc_ae:     { code:'1102050101.309', name:'ลูกหนี้ค่ารักษาประกันสังคม-ค่าใช้จ่ายสูง/AE OP' },
      hc:        { code:'1102050101.309', name:'ลูกหนี้ค่ารักษาประกันสังคม-ค่าใช้จ่ายสูง/AE OP' },
      ae:        { code:'1102050101.309', name:'ลูกหนี้ค่ารักษาประกันสังคม-ค่าใช้จ่ายสูง/AE OP' },
      nout_diff: { code:'1102050102.301', name:'ลูกหนี้ค่ารักษาประกันสังคม OP-นอกเครือข่าย ต่างสังกัด สป.สธ.' },
    },
    IPD: {
      default:   { code:'1102050101.302', name:'ลูกหนี้ค่ารักษาประกันสังคม IP-เครือข่าย' },
      nout_sdhz: { code:'1102050101.304', name:'ลูกหนี้ค่ารักษาประกันสังคม IP-นอกเครือข่าย สังกัด สป.สธ.' },
      hc:        { code:'1102050101.310', name:'ลูกหนี้ค่ารักษาประกันสังคม-ค่าใช้จ่ายสูง IP' },
      ae:        { code:'1102050101.310', name:'ลูกหนี้ค่ารักษาประกันสังคม-ค่าใช้จ่ายสูง IP' },
      nout_diff: { code:'1102050102.302', name:'ลูกหนี้ค่ารักษาประกันสังคม IP-นอกเครือข่าย ต่างสังกัด สป.สธ.' },
    },
  },
  // ─── OFC / ข้าราชการ-เบิกตรงกรมบัญชีกลาง ───────────────
  'OFC': {
    OPD: { default: { code:'1102050101.401', name:'ลูกหนี้ค่ารักษา-เบิกจ่ายตรงกรมบัญชีกลาง OP' } },
    IPD: { default: { code:'1102050101.402', name:'ลูกหนี้ค่ารักษา-เบิกจ่ายตรงกรมบัญชีกลาง IP' } },
  },
  'ข้าราชการ/รัฐวิสาหกิจ': {
    OPD: {
      default: { code:'1102050101.401', name:'ลูกหนี้ค่ารักษา-เบิกจ่ายตรงกรมบัญชีกลาง OP' },
      tonsangkad: { code:'1102050102.108', name:'ลูกหนี้ค่ารักษา-เบิกต้นสังกัด OP' },
      other:   { code:'1102050102.110', name:'ลูกหนี้ค่ารักษา-เบิกจ่ายตรงหน่วยงานอื่น OP' },
    },
    IPD: {
      default: { code:'1102050101.402', name:'ลูกหนี้ค่ารักษา-เบิกจ่ายตรงกรมบัญชีกลาง IP' },
      tonsangkad: { code:'1102050102.109', name:'ลูกหนี้ค่ารักษา-เบิกต้นสังกัด IP' },
      other:   { code:'1102050102.111', name:'ลูกหนี้ค่ารักษา-เบิกจ่ายตรงหน่วยงานอื่น IP' },
    },
  },
  // ─── คนต่างด้าว / แรงงานต่างด้าว ────────────────────────
  'แรงงานต่างด้าว': {
    OPD: {
      default:  { code:'1102050101.501', name:'ลูกหนี้ค่ารักษา-คนต่างด้าวและแรงงานต่างด้าว OP' },
      nout_cup: { code:'1102050101.503', name:'ลูกหนี้ค่ารักษา-คนต่างด้าวและแรงงานต่างด้าว OP นอก CUP' },
      central:  { code:'1102050101.505', name:'ลูกหนี้ค่ารักษา-แรงงานต่างด้าว เบิกจากส่วนกลาง OP' },
    },
    IPD: {
      default:  { code:'1102050101.502', name:'ลูกหนี้ค่ารักษา-คนต่างด้าวและแรงงานต่างด้าว IP' },
      nout_cup: { code:'1102050101.504', name:'ลูกหนี้ค่ารักษา-คนต่างด้าวและแรงงานต่างด้าว IP นอก CUP' },
      central:  { code:'1102050101.506', name:'ลูกหนี้ค่ารักษา-แรงงานต่างด้าว เบิกจากส่วนกลาง IP' },
    },
  },
  // ─── บุคคลมีปัญหาสถานะและสิทธิ ─────────────────────────
  'บุคคลมีปัญหาสถานะ': {
    OPD: {
      default:  { code:'1102050101.701', name:'ลูกหนี้ค่ารักษา-บุคคลมีปัญหาสถานะและสิทธิ OP ใน CUP' },
      nout_cup: { code:'1102050101.702', name:'ลูกหนี้ค่ารักษา-บุคคลมีปัญหาสถานะและสิทธิ OP นอก CUP' },
      central:  { code:'1102050101.703', name:'ลูกหนี้ค่ารักษาบุคคลมีปัญหาสถานะ-เบิกจากส่วนกลาง OP' },
    },
    IPD: {
      default:  { code:'1102050101.704', name:'ลูกหนี้ค่ารักษาบุคคลมีปัญหาสถานะ-เบิกจากส่วนกลาง IP' },
    },
  },
  // ─── พรบ.รถ ─────────────────────────────────────────────
  'พรบ.รถ': {
    OPD: { default: { code:'1102050102.602', name:'ลูกหนี้ค่ารักษา-พรบ.รถ OP' } },
    IPD: { default: { code:'1102050102.603', name:'ลูกหนี้ค่ารักษา-พรบ.รถ IP' } },
  },
  // ─── อปท. ────────────────────────────────────────────────
  'อปท.': {
    OPD: {
      default: { code:'1102050102.801', name:'ลูกหนี้ค่ารักษา-เบิกจ่ายตรง อปท. OP' },
      special: { code:'1102050102.803', name:'ลูกหนี้ค่ารักษา-เบิกจ่ายตรง อปท.รูปแบบพิเศษ OP' },
    },
    IPD: {
      default: { code:'1102050102.802', name:'ลูกหนี้ค่ารักษา-เบิกจ่ายตรง อปท. IP' },
      special: { code:'1102050102.804', name:'ลูกหนี้ค่ารักษา-เบิกจ่ายตรงอปท.รูปแบบพิเศษ IP' },
    },
  },
  // ─── ชำระเงินเอง / บุคคลภายนอก ──────────────────────────
  'ชำระเงินเอง': {
    OPD: { default: { code:'1102050102.106', name:'ลูกหนี้ค่ารักษา-ชำระเงิน OP' } },
    IPD: { default: { code:'1102050102.107', name:'ลูกหนี้ค่ารักษา-ชำระเงิน IP' } },
  },
  // ─── เอกชน / ประกันชีวิต ─────────────────────────────────
  'เอกชน/ประกันชีวิต': {
    OPD: { default: { code:'1102060000.000', name:'ลูกหนี้ค่ารักษา-เอกชน/ประกันชีวิต OP' } },
    IPD: { default: { code:'1102060000.000', name:'ลูกหนี้ค่ารักษา-เอกชน/ประกันชีวิต IP' } },
  },
};

function getAccountCode(rightNorm, ptype, subType) {
  const pt = (ptype === 'IPD' || ptype === 'IP') ? 'IPD' : 'OPD';
  const rm = ACCOUNT_MAP[rightNorm];
  if (!rm) return { code:'1102050000.000', name:'ลูกหนี้ค่ารักษาพยาบาล ' + (rightNorm||'อื่นๆ') };
  const pm = rm[pt];
  if (!pm) return { code:'1102050000.000', name:'ลูกหนี้ค่ารักษาพยาบาล ' + rightNorm };
  if (subType && pm[subType]) return pm[subType];
  return pm.default || { code:'1102050000.000', name:'ลูกหนี้ค่ารักษาพยาบาล ' + rightNorm };
}

function toNum(v) {
  if (v === null || v === undefined || v === '' || v === '-') return 0;
  const n = parseFloat(String(v).replace(/,/g,''));
  return isNaN(n) ? 0 : n;
}

// =============================================
// IMP PARSER
// =============================================
function parseImpFile(workbook, filename) {
  const sheetName = workbook.SheetNames[0];
  const ws = workbook.Sheets[sheetName];
  const rows = XLSX.utils.sheet_to_json(ws, {header:1, defval:''});

  // Find header row (contains 'HN')
  let headerRow = 0;
  for (let i = 0; i < Math.min(10, rows.length); i++) {
    if (rows[i].some(c => String(c).trim() === 'HN')) { headerRow = i; break; }
  }

  const header = rows[headerRow].map(c => String(c).trim());
  // Try column names in priority order
  const colFirst = (...names) => {
    for (const name of names) {
      const i = header.findIndex(h => h.includes(name));
      if (i >= 0) return i;
    }
    return -1;
  };

  // Map columns by name patterns (with OPD/IPD column name variants)
  const cols = {
    admitDate:    colFirst('วันที่Admit','วันรับบริการ','วันที่รับ','วันที่บริการ','วันเข้า','วันที่เข้า','Admit'),
    dischargeDate:colFirst('วันที่จำหน่าย','วันจำหน่าย','วันออก','จำหน่าย'),
    hn:           colFirst('HN'),
    an:           colFirst('AN'),
    pid:          colFirst('หมายเลขบัตร','บัตรประชาชน','PID','บัตร'),
    name:         colFirst('ชื่อ-สกุล','ชื่อสกุล','ชื่อ'),
    age:          colFirst('อายุ'),
    ward:         colFirst('ตึก','Ward','หอผู้ป่วย'),
    icd10:        colFirst('ICD','รหัสโรค'),
    rightType:    colFirst('สิทธิการรักษา','สิทธิ์การรักษา','สิทธิ์','สิทธิ'),
    patPay:       colFirst('ผู้ป่วยชำระ','ผู้ป่วยจ่าย','ส่วนผู้ป่วย'),
    rightPay:     colFirst('สิทธิชำระ','สิทธิ์ชำระ','สิทธิจ่าย','ส่วนสิทธิ','ส่วนเบิก'),
    totalCost:    colFirst('ค่าใช้จ่ายทั้งหมด','ค่าใช้จ่ายรวม','ค่าใช้จ่าย','รวมค่าใช้จ่าย','ยอดรวม','ค่ารักษา'),
  };

  // Diagnostic log for column positions
  log(`  📊 ${filename.replace(/.*[\\/]/,'')} header row ${headerRow}: HN[${cols.hn}] AN[${cols.an}] Admit[${cols.admitDate}] Dis[${cols.dischargeDate}] Right[${cols.rightType}] Cost[${cols.totalCost}]`, 'ok');
  if (cols.hn < 0) log(`  ⚠️ ไม่พบคอลัมน์ HN ใน ${filename}`, 'warn');
  if (cols.rightType < 0) log(`  ⚠️ ไม่พบคอลัมน์ สิทธิการรักษา — ตรวจสอบหัวตาราง`, 'warn');
  if (cols.admitDate < 0) log(`  ⚠️ ไม่พบคอลัมน์ วันที่ Admit/รับบริการ — วันที่จะว่าง`, 'warn');
  if (cols.totalCost < 0) log(`  ⚠️ ไม่พบคอลัมน์ ค่าใช้จ่าย — ยอดจะเป็น 0`, 'warn');

  let count = 0;
  const records = [];
  for (let i = headerRow + 1; i < rows.length; i++) {
    const row = rows[i];
    if (!row || row.every(c => !c)) continue;
    const hn = String(row[cols.hn] || '').trim();
    if (!hn) continue;

    const disDate = parseDateStr(row[cols.dischargeDate]);
    const admDate = parseDateStr(row[cols.admitDate]);

    // คำนวณ ptype ก่อน เพื่อใช้กำหนด disMonth ให้ถูกต้อง
    const ptype = filename.toLowerCase().includes('ipd') ? 'IPD'
                : filename.toLowerCase().includes('opd') ? 'OPD' : 'IPD';
    // OPD: ใช้วันที่รับบริการ (admitDate) เป็นฐานเดือน เพราะ OPD มักเข้า-ออกวันเดียวกัน
    // IPD: ใช้วันที่จำหน่าย (dischargeDate) ตามปกติ
    const disMonth = ptype === 'OPD' ? monthKey(admDate) : monthKey(disDate);
    records.push({
      hn,
      an:          String(row[cols.an] || '').trim(),
      pid:         String(row[cols.pid] || '').trim(),
      name:        String(row[cols.name] || '').trim(),
      admitDate:   admDate,
      dischargeDate: disDate,
      ward:        String(row[cols.ward] || '').trim(),
      icd10:       String(row[cols.icd10] || '').trim(),
      rightType:   String(row[cols.rightType] || '').trim(),
      rightNorm:   normalizeRight(row[cols.rightType]),
      patPay:      toNum(row[cols.patPay]),
      rightPay:    toNum(row[cols.rightPay]),
      totalCost:   toNum(row[cols.totalCost]),
      disMonth,
      ptype,
      sourceFile:  filename,
      repRecords:  [],
    });
    count++;
  }
  return { records, count };
}

// =============================================
// ECLAIM PARSER
// =============================================
function parseEclaimFile(workbook, filename) {
  // Look for 'Detail' sheet first
  let sheetName = workbook.SheetNames.find(n => n.toLowerCase().includes('detail')) || workbook.SheetNames[0];
  const ws = workbook.Sheets[sheetName];
  const rows = XLSX.utils.sheet_to_json(ws, {header:1, defval:''});

  // Find header row containing 'REP No.' or 'HN'
  let headerRow = -1;
  for (let i = 0; i < Math.min(20, rows.length); i++) {
    const joined = rows[i].map(c => String(c)).join(' ');
    if (joined.includes('REP No.') || (joined.includes('HN') && joined.includes('PID'))) {
      headerRow = i; break;
    }
  }
  if (headerRow < 0) { log(`ไม่พบ header ใน ${filename}`, 'err'); return {records:[], count:0}; }

  const header = rows[headerRow].map(c => String(c).trim());
  const findCol = (names) => {
    for (const name of names) {
      const idx = header.findIndex(h => h.includes(name));
      if (idx >= 0) return idx;
    }
    return -1;
  };

  // Fund columns are multi-row headers - scan rows headerRow to headerRow+3
  // Build a merged header
  const mergedHeader = [...header];
  for (let r = headerRow + 1; r <= Math.min(headerRow + 3, rows.length - 1); r++) {
    rows[r].forEach((c, i) => {
      if (c && !mergedHeader[i]) mergedHeader[i] = String(c).trim();
      else if (c) mergedHeader[i] += ' ' + String(c).trim();
    });
  }

  const cols = {
    repNo:    findCol(['REP No.']),
    hn:       findCol(['HN']),
    an:       findCol(['AN']),
    pid:      findCol(['PID']),
    name:     findCol(['ชื่อ-สกุล','ชื่อ']),
    admDate:  findCol(['วันเข้ารักษา']),
    disDate:  findCol(['วันจำหน่าย']),
    netComp:  findCol(['ชดเชยสุทธิ']),
    compFrom: findCol(['ชดเชยจาก']),
    fundMain: findCol(['กองทุนหลัก']),
    fundSub:  findCol(['กองทุนย่อย']),
    errorCode:findCol(['Error Code']),
    deny:     findCol(['Deny']),
  };

  // Find fund breakdown columns by scanning full merged headers
  const fundCols = {};
  mergedHeader.forEach((h, i) => {
    if (h.includes('ค่าใช้จ่ายสูง') || h.includes('HC') && h.length < 10) fundCols.hc = fundCols.hc || i;
    if (h.includes('อุบัติเหตุฉุกเฉิน') || h === 'AE') fundCols.ae = fundCols.ae || i;
    if (h.includes('อวัยวะเทียม') || h.includes('INST') && h.length < 10) fundCols.inst = fundCols.inst || i;
    if (h.includes('ผู้ป่วยใน') || h === 'IP') fundCols.ip = fundCols.ip || i;
    if (h.includes('โรคเฉพาะ') || h === 'DMIS') fundCols.dmis = fundCols.dmis || i;
    if (h === 'DRUG') fundCols.drug = fundCols.drug || i;
  });
  // Fallback fixed indices based on known format
  if (!fundCols.hc)   fundCols.hc   = 54;
  if (!fundCols.ae)   fundCols.ae   = 56;
  if (!fundCols.inst) fundCols.inst = 64;
  if (!fundCols.ip)   fundCols.ip   = 66;
  if (!fundCols.dmis) fundCols.dmis = 75;
  if (!fundCols.drug) fundCols.drug = 96;

  const dataStart = headerRow + 3; // Skip sub-headers (rows 6,7 in eclaim = 2 sub-header rows)
  let count = 0;
  const records = [];

  for (let i = dataStart; i < rows.length; i++) {
    const row = rows[i];
    if (!row || row.every(c => !c)) continue;
    const hn = String(row[cols.hn] || '').trim();
    const repNo = String(row[cols.repNo] || '').trim();
    if (!hn && !repNo) continue;
    if (hn === 'HN') continue; // repeated header

    records.push({
      repNo,
      hn,
      an:       String(row[cols.an] || '').trim(),
      pid:      String(row[cols.pid] || '').trim(),
      name:     String(row[cols.name] || '').trim(),
      admDate:  parseDateStr(row[cols.admDate]),
      disDate:  parseDateStr(row[cols.disDate]),
      netComp:  toNum(row[cols.netComp]),
      compFrom: String(row[cols.compFrom] || '').trim(),
      fundMain: String(row[cols.fundMain] || '').trim(),
      fundSub:  String(row[cols.fundSub] || '').trim(),
      errorCode:String(row[cols.errorCode] || '').trim(),
      isDeny:   String(row[cols.deny] || '').trim().toUpperCase() === 'DENY',
      hc:       toNum(row[fundCols.hc]),
      ae:       toNum(row[fundCols.ae]),
      inst:     toNum(row[fundCols.inst]),
      ip:       toNum(row[fundCols.ip]),
      dmis:     toNum(row[fundCols.dmis]),
      drug:     toNum(row[fundCols.drug]),
      sourceFile: filename,
    });
    count++;
  }
  return { records, count };
}

// =============================================
// FILE UPLOAD HANDLERS
// =============================================
async function handleImpUpload(input) {
  const files = Array.from(input.files);
  for (const file of files) {
    log(`📄 กำลังอ่าน IMP: ${file.name} ...`);
    try {
      const buf = await file.arrayBuffer();
      const wb = XLSX.read(buf, {type:'array', cellDates:false});
      const {records, count} = parseImpFile(wb, file.name);
      // Avoid exact duplicates by HN+AN+admitDate+totalCost
      let added = 0, dupCount = 0, errCount = 0;
      for (const rec of records) {
        // Dedup key: HN + AN + admitDate (exact date) + totalCost
        // ใช้ admitDate แทน disMonth เพื่อรองรับ OPD ที่มีหลาย visit/เดือน และ AN ว่าง
        const admDt = rec.admitDate ? rec.admitDate.toISOString().split('T')[0] : '';
        const dup = store.impRecords.find(r => {
          const rDt = r.admitDate ? r.admitDate.toISOString().split('T')[0] : '';
          return r.hn === rec.hn && r.an === rec.an && rDt === admDt && r.totalCost === rec.totalCost;
        });
        if (!dup) { store.impRecords.push(rec); added++; }
        else dupCount++;
      }
      store.impFileCount++;
      // บันทึก file-level tracking (ตรวจสอบไม่ให้ซ้ำ)
      if (!store.impFiles.find(f => f.name === file.name)) {
        store.impFiles.push({name: file.name, added, total: count, date: new Date().toLocaleDateString('th-TH')});
      } else {
        const existing = store.impFiles.find(f => f.name === file.name);
        existing.added += added; existing.total += count; // เพิ่มยอด
      }
      log(`✅ IMP: ${file.name} — พบ ${count} รายการ, เพิ่มใหม่ ${added} ราย${dupCount>0?' | ซ้ำ '+dupCount+' ราย (ข้าม)':''}`, 'ok');
      renderImpFileList();
    } catch(e) {
      log(`❌ Error: ${file.name} — ${e.message}`, 'err');
    }
  }
  input.value = '';
  refreshAll();
}

async function handleEclaimUpload(input) {
  const files = Array.from(input.files);
  for (const file of files) {
    log(`📋 กำลังอ่าน REP/eclaim: ${file.name} ...`);
    try {
      const buf = await file.arrayBuffer();
      const wb = XLSX.read(buf, {type:'array', cellDates:false});

      // Auto-detect format by sheet names or column content
      const sheetNames = wb.SheetNames; // keep original case for Thai check
      const sheetNamesLC = sheetNames.map(s => s.toLowerCase());
      let result;
      if (sheetNamesLC.includes('individual')) {
        // New REP format: รายงานสรุปการขอเบิกชดเชย (6902_OP_xx)
        log(`  📂 ตรวจพบ: REP format ใหม่ (Individual sheet)`, 'ok');
        result = parseRepSimpleFile(wb, file.name);
        if (result.meta && result.meta.batch) {
          log(`  🗂️ งวด: ${result.meta.batch} | ${result.meta.hospName || ''}`, 'ok');
        }
      } else if (sheetNames.some(s => s.includes('รายละเอียด') && s.toUpperCase().includes('OP') && !s.includes('อุทธรณ์'))) {
        // STM format: Statement รายละเอียด(ข้อมูลปกติ) 1 OP
        log(`  📂 ตรวจพบ: STM format (รายละเอียด OP sheet)`, 'ok');
        result = parseStmFile(wb, file.name);
        if (result.meta && result.meta.hospName) {
          log(`  🗂️ หน่วยบริการ: ${result.meta.hospName}`, 'ok');
        }
      } else if (sheetNamesLC.some(s => s.includes('detail'))) {
        // Old eclaim format
        log(`  📂 ตรวจพบ: eclaim format เดิม (Detail sheet)`, 'ok');
        result = parseEclaimFile(wb, file.name);
      } else {
        // Try rep_simple first (simpler heuristic)
        const ws0 = wb.Sheets[wb.SheetNames[0]];
        const testRows = XLSX.utils.sheet_to_json(ws0, {header:1, defval:''}).slice(0, 15);
        const hasRepNo = testRows.some(r => r.some(c => String(c).includes('REP_NO')));
        const hasTranId = testRows.some(r => r.some(c => String(c).includes('TRAN_ID')));
        if (hasTranId) {
          log(`  📂 ตรวจพบ: STM format (TRAN_ID detected)`, 'ok');
          result = parseStmFile(wb, file.name);
        } else {
          result = hasRepNo ? parseRepSimpleFile(wb, file.name) : parseEclaimFile(wb, file.name);
          log(`  📂 ตรวจพบ: ${hasRepNo ? 'REP format ใหม่' : 'eclaim format เดิม'} (auto-detect)`, 'ok');
        }
      }

      let added = 0, dupCount = 0;
      for (const rec of result.records) {
        // Dedup key per format:
        //  - STM: TRAN_ID unique per transaction row
        //  - REP Simple (rep_simple): repNo + rowSeq (unique per row, 1 row per fund per patient)
        //  - eclaim format: repNo + hn + seqNo (old format มี 1 row ต่อ patient)
        const getKey = (r) => {
          if (r.fileType === 'stm')
            return `stm|${r.tranId||r.hn}|${r.seqNo||r.netComp}`;
          if (r.fileType === 'rep_simple')
            return `${r.repNo}|ROW|${r.rowSeq}|${r.fundMain}|${r.fundSub}|${r.netComp}`;
          return `${r.repNo}|${r.hn}|${r.seqNo || r.netComp}`;
        };
        const key = getKey(rec);
        if (!store.eclaimRecords.some(r => getKey(r) === key)) {
          store.eclaimRecords.push(rec);
          added++;
        } else dupCount++;
      }
      store.eclaimFileCount++;
      // บันทึก file-level tracking
      if (!store.eclaimFiles.find(f => f.name === file.name)) {
        store.eclaimFiles.push({name: file.name, added, total: result.count, date: new Date().toLocaleDateString('th-TH')});
      } else {
        const existing = store.eclaimFiles.find(f => f.name === file.name);
        existing.added += added; existing.total += result.count;
      }
      log(`✅ ${file.name} — พบ ${result.count} รายการ, เพิ่มใหม่ ${added} ราย${dupCount>0?' | ซ้ำ '+dupCount+' รายการ (ข้าม)':''}`, 'ok');
      renderEclaimFileList();
    } catch(e) {
      log(`❌ Error: ${file.name} — ${e.message}`, 'err');
    }
  }
  input.value = '';
  refreshAll();
}

function renderImpFileList() {
  const el = document.getElementById('imp-file-list');
  if (!el) return;
  el.innerHTML = store.impFiles.length === 0
    ? '<div style="font-size:12px;color:#a0aec0;padding:4px 0">ยังไม่ได้นำเข้าไฟล์ IMP</div>'
    : store.impFiles.map((f, idx) => `
        <div style="font-size:12px;padding:4px 8px;background:#d1fae5;border-radius:4px;margin-top:4px;display:flex;align-items:center;gap:6px">
          <span style="flex:1">✅ <b>${(f.name||'').replace(/.*[\\/]/,'')}</b>
            <span style="color:#065f46"> (${fmtInt(f.added)}/${fmtInt(f.total)} ราย)</span>
            <span style="color:#a0aec0;font-size:11px"> ${f.date||''}</span>
          </span>
          <button class="btn btn-danger btn-sm" style="padding:2px 8px;font-size:11px"
            onclick="deleteImpByIndex(${idx})">🗑️ ลบ</button>
        </div>`).join('');
}

function renderEclaimFileList() {
  const el = document.getElementById('eclaim-file-list');
  if (!el) return;
  el.innerHTML = store.eclaimFiles.length === 0
    ? '<div style="font-size:12px;color:#a0aec0;padding:4px 0">ยังไม่ได้นำเข้าไฟล์ eclaim</div>'
    : store.eclaimFiles.map((f, idx) => `
        <div style="font-size:12px;padding:4px 8px;background:#dbeafe;border-radius:4px;margin-top:4px;display:flex;align-items:center;gap:6px">
          <span style="flex:1">✅ <b>${(f.name||'').replace(/.*[\\/]/,'')}</b>
            <span style="color:#1e40af"> (${fmtInt(f.added)}/${fmtInt(f.total)} ราย)</span>
            <span style="color:#a0aec0;font-size:11px"> ${f.date||''}</span>
          </span>
          <button class="btn btn-danger btn-sm" style="padding:2px 8px;font-size:11px"
            onclick="deleteEclaimByIndex(${idx})">🗑️ ลบ</button>
        </div>`).join('');
}

// ใช้ index แทนชื่อไฟล์ใน onclick เพื่อหลีกเลี่ยง quote ใน HTML attribute
function deleteImpByIndex(idx) {
  const f = store.impFiles[idx];
  if (!f) return;
  const shortName = (f.name||'').replace(/.*[\\/]/,'');
  if (!confirm(`ลบข้อมูล IMP ทั้งหมดจากไฟล์นี้?\n📄 ${shortName}\n(จะลบ ${fmtInt(f.added)} รายการออกจากระบบ)`)) return;
  const filename = f.name;
  const before = store.impRecords.length;
  store.impRecords = store.impRecords.filter(r => r.sourceFile !== filename);
  const deleted = before - store.impRecords.length;
  store.impFiles = store.impFiles.filter(x => x.name !== filename);
  store.impFileCount = store.impFiles.length;
  log(`🗑️ ลบ IMP จาก "${shortName}": ${deleted} ราย`, 'warn');
  refreshAll();
}

function deleteEclaimByIndex(idx) {
  const f = store.eclaimFiles[idx];
  if (!f) return;
  const shortName = (f.name||'').replace(/.*[\\/]/,'');
  if (!confirm(`ลบข้อมูล eclaim ทั้งหมดจากไฟล์นี้?\n📋 ${shortName}\n(จะลบ ${fmtInt(f.added)} รายการออกจากระบบ)`)) return;
  const filename = f.name;
  const before = store.eclaimRecords.length;
  store.eclaimRecords = store.eclaimRecords.filter(r => r.sourceFile !== filename);
  const deleted = before - store.eclaimRecords.length;
  store.eclaimFiles = store.eclaimFiles.filter(x => x.name !== filename);
  store.eclaimFileCount = store.eclaimFiles.length;
  log(`🗑️ ลบ eclaim จาก "${shortName}": ${deleted} รายการ`, 'warn');
  refreshAll();
}

// =============================================
// REP SIMPLE PARSER (format: 6902_OP_xx / Individual sheet)
// =============================================
function parseRepSimpleFile(workbook, filename) {
  const sheetName = workbook.SheetNames.find(n => n === 'Individual') || workbook.SheetNames[0];
  const ws = workbook.Sheets[sheetName];
  const rows = XLSX.utils.sheet_to_json(ws, {header:1, defval:''});

  // Find header row: contains 'HN' and 'REP_NO'
  let headerRow = -1;
  for (let i = 0; i < Math.min(15, rows.length); i++) {
    const joined = rows[i].map(c => String(c)).join('|');
    if (joined.includes('HN') && joined.includes('REP_NO')) { headerRow = i; break; }
    if (joined.includes('HN') && joined.includes('กองทุน')) { headerRow = i; break; }
  }
  if (headerRow < 0) { log(`ไม่พบ header ใน ${filename}`, 'err'); return {records:[], count:0, meta:{}}; }

  const header = rows[headerRow].map(c => String(c).trim());
  const ci = (name) => header.findIndex(h => h.includes(name));

  const cols = {
    seq:         ci('ลำดับ'),
    transferDate:ci('วันที่โอน'),
    hn:          ci('HN'),
    an:          ci('AN'),
    ptype:       ci('ประเภท'),
    pid:         ci('เลขบัตรประชาชน') >= 0 ? ci('เลขบัตรประชาชน') : ci('PID'),
    name:        ci('ชื่อ'),
    serviceDate: ci('วันที่เข้ารับบริการ') >= 0 ? ci('วันที่เข้ารับบริการ') : ci('วันที่'),
    netComp:     ci('จ่ายชดเชยสุทธิ') >= 0 ? ci('จ่ายชดเชยสุทธิ') : ci('ชดเชย'),
    repNo:       ci('REP_NO') >= 0 ? ci('REP_NO') : ci('REP No'),
    fundMain:    ci('กองทุนหลัก'),
    fundSub:     ci('กองทุนย่อย'),
    detail:      ci('รายละเอียด'),
    seqNo:       ci('seq_no'),
    invoiceNo:   ci('invoice_no'),
  };

  // Read metadata from top rows (งวด, ชื่อหน่วยบริการ, etc.)
  const meta = {};
  for (let i = 0; i < headerRow; i++) {
    const r = rows[i];
    const label = String(r[1] || '').trim();
    const val   = String(r[2] || '').trim();
    if (label.includes('งวด')) meta.batch = val;
    if (label.includes('ชื่อหน่วย')) meta.hospName = val;
    if (label.includes('รหัสหน่วย')) meta.hospCode = val;
    if (label.includes('วันที่จัดพิมพ์')) meta.printDate = val;
  }

  let count = 0;
  const records = [];
  for (let i = headerRow + 1; i < rows.length; i++) {
    const row = rows[i];
    if (!row || row.every(c => !c)) continue;
    const hn = String(row[cols.hn] || '').trim();
    // Skip summary row (has "รวม" or no HN that looks valid)
    if (!hn || hn === 'nan' || String(row[cols.serviceDate] || '').includes('รวม')) continue;
    const net = toNum(row[cols.netComp]);
    if (net === 0 && !hn) continue;

    // rowSeq = ลำดับที่ในไฟล์ (unique per row) ใช้เป็น dedup key ที่แม่นยำ
    const rowSeq  = String(row[cols.seq] || (headerRow + count + 1)).trim();
    const seqNoVal = String(row[cols.seqNo] || '').trim();
    records.push({
      repNo:       String(row[cols.repNo] || '').trim(),
      hn,
      an:          String(row[cols.an] || '').trim(),
      pid:         String(row[cols.pid] || '').trim(),
      name:        String(row[cols.name] || '').trim(),
      ptype:       String(row[cols.ptype] || '').trim(),
      serviceDate: parseDateStr(row[cols.serviceDate]),
      transferDate:parseDateStr(row[cols.transferDate]),
      netComp:     net,
      fundMain:    String(row[cols.fundMain] || '').trim(),
      fundSub:     String(row[cols.fundSub] || '').trim(),
      detail:      String(row[cols.detail] || '').trim(),
      seqNo:       seqNoVal,   // visit-level seq (shared across fund rows of same visit)
      rowSeq:      rowSeq,     // row-level seq = ลำดับที่ (unique per row in file)
      invoiceNo:   String(row[cols.invoiceNo] || '').trim(),
      // Derived fund category
      fundCat:     getFundCategory(String(row[cols.fundMain] || ''), String(row[cols.fundSub] || '')),
      isDeny:      false,
      // Empty eclaim-format fields for compatibility
      admDate:     null, disDate: null,
      compFrom:    '', errorCode: '',
      hc:0, ae:0, inst:0, ip:0, dmis:0, drug:0, // will be set by fundCat below
      fileType:    'rep_simple',
      sourceFile:  filename,
    });
    // Set fund bucket based on category
    const last = records[records.length - 1];
    if (last.fundCat === 'hc')   last.hc   = net;
    if (last.fundCat === 'ae')   last.ae   = net;
    if (last.fundCat === 'ae_drug') { last.ae = net; last.drug = net; }
    if (last.fundCat === 'inst') last.inst = net;
    if (last.fundCat === 'drug') last.drug = net;
    if (last.fundCat === 'dmis') last.dmis = net;
    if (last.fundCat === 'ip')   last.ip   = net;
    count++;
  }
  return { records, count, meta };
}

// =============================================
// STM PARSER (format: STM_11422_OPUCS_xx — Statement รายละเอียด)
// Sheet: รายละเอียด(ข้อมูลปกติ) 1 OP
// Columns: REP | TRAN_ID | HN | AN | PID | ชื่อ-สกุล | วันเข้ารักษา | วันจำหน่าย | MAININSCL | PROJCODE | ... | ยอดชดเชยทั้งสิ้น | DRUG | VA | SEQ NO
// =============================================
function parseStmFile(workbook, filename) {
  // Find the target sheet: ชื่อมี 'รายละเอียด' และ 'OP' (ไม่ใช่ 'อุทธรณ์')
  const sheetName = workbook.SheetNames.find(n =>
    n.includes('รายละเอียด') && n.toUpperCase().includes('OP') && !n.includes('อุทธรณ์')
  ) || workbook.SheetNames[0];
  const ws = workbook.Sheets[sheetName];
  const rows = XLSX.utils.sheet_to_json(ws, {header:1, defval:''});

  // Find header row: must contain 'HN' and ('TRAN_ID' or 'SEQ NO')
  let headerRow = -1;
  for (let i = 0; i < Math.min(20, rows.length); i++) {
    const cells = rows[i].map(c => String(c).trim());
    const joined = cells.join('|');
    if (joined.includes('HN') && (joined.includes('TRAN_ID') || joined.includes('SEQ NO') || joined.includes('SEQ_NO'))) {
      headerRow = i;
      break;
    }
  }
  if (headerRow < 0) {
    log(`ไม่พบ header ใน STM ${filename}`, 'err');
    return {records:[], count:0, meta:{}};
  }

  const header = rows[headerRow].map(c => String(c).trim());
  const ci = (...names) => {
    for (const name of names) {
      const i = header.findIndex(h => h === name || h.includes(name));
      if (i >= 0) return i;
    }
    return -1;
  };

  const cols = {
    repNo:    ci('REP'),
    tranId:   ci('TRAN_ID'),
    hn:       ci('HN'),
    an:       ci('AN'),
    pid:      ci('PID'),
    name:     ci('ชื่อ-สกุล','ชื่อสกุล','ชื่อ'),
    admDate:  ci('วันเข้ารักษา','วันรับบริการ','วันที่เข้า','วันที่รับ'),
    disDate:  ci('วันจำหน่าย','วันออก','วันจำหน่าย'),
    inscl:    ci('MAININSCL'),
    projcode: ci('PROJCODE'),
    netComp:  ci('ยอดชดเชยทั้งสิ้น','ยอดชดเชย'),
    drug:     ci('DRUG'),
    va:       ci('VA'),
    seqNo:    ci('SEQ NO','SEQ_NO','seqno'),
  };

  // Extract metadata from rows above header
  const meta = {};
  for (let i = 0; i < headerRow; i++) {
    const r = rows[i];
    const line = r.map(c => String(c)).join(' ');
    if (line.includes('เลขที่เอกสาร') || line.includes('REP')) {
      for (const c of r) {
        const v = String(c).trim();
        if (v.match(/^\d{10,}$/) || v.match(/^REP\d/i)) { meta.docRepNo = v; break; }
      }
    }
    if (line.includes('โรงพยาบาล') || line.includes('หน่วยบริการ')) {
      for (let j = 0; j < r.length - 1; j++) {
        if (String(r[j]).includes('โรงพยาบาล') || String(r[j]).includes('หน่วยบริการ')) {
          meta.hospName = String(r[j+1] || r[j]).trim(); break;
        }
      }
    }
    if (line.includes('ออกรายงาน') || line.includes('วันที่พิมพ์')) {
      for (let j = 0; j < r.length - 1; j++) {
        if (String(r[j]).includes('ออกรายงาน') || String(r[j]).includes('พิมพ์')) {
          meta.printDate = String(r[j+1] || '').trim(); break;
        }
      }
    }
  }

  const records = [];
  let count = 0;
  for (let i = headerRow + 1; i < rows.length; i++) {
    const row = rows[i];
    if (!row || row.every(c => !c && c !== 0)) continue;

    const rawHn = String(row[cols.hn] ?? '').trim();
    // Skip summary/total rows (no valid HN, or HN is text like 'รวม')
    if (!rawHn || rawHn === 'nan' || /[ก-ฮ]/.test(rawHn)) continue;
    // Strip leading zeros from HN
    const hn = rawHn.replace(/^0+/, '') || rawHn;

    const tranId = String(row[cols.tranId] ?? '').trim();
    const netComp = cols.netComp >= 0 ? toNum(row[cols.netComp]) : 0;
    const drugAmt  = cols.drug   >= 0 ? toNum(row[cols.drug])    : 0;
    const hcAmt    = netComp - drugAmt;

    const inscl    = String(row[cols.inscl]    ?? '').trim(); // UCS, WEL, etc.
    const projcode = String(row[cols.projcode] ?? '').trim(); // HC16, AE04, etc.
    const seqNo    = cols.seqNo >= 0 ? String(row[cols.seqNo] ?? '').trim() : '';
    const repNo    = cols.repNo >= 0 ? String(row[cols.repNo] ?? '').trim() : (meta.docRepNo || '');

    const admDateVal = cols.admDate >= 0 ? parseDateStr(row[cols.admDate]) : null;
    const disDateVal = cols.disDate >= 0 ? parseDateStr(row[cols.disDate]) : null;

    // Derive fund category from projcode (MAININSCL = UCS, WEL; PROJCODE = HC16, AE04, etc.)
    const fundCat = getFundCategory(projcode || inscl, inscl);

    records.push({
      repNo,
      tranId,
      hn,
      an:           String(row[cols.an]   ?? '').trim(),
      pid:          String(row[cols.pid]  ?? '').trim(),
      name:         String(row[cols.name] ?? '').trim(),
      admDate:      admDateVal,
      disDate:      disDateVal,
      serviceDate:  admDateVal,
      transferDate: null,
      netComp,
      hc:    Math.max(0, hcAmt),
      ae:    0,
      inst:  0,
      ip:    0,
      dmis:  0,
      drug:  drugAmt,
      fundMain: projcode || inscl,
      fundSub:  inscl,
      fundCat,
      seqNo,
      isDeny:    false,
      compFrom:  '',
      errorCode: '',
      fileType:  'stm',
      sourceFile: filename,
    });
    count++;
  }

  return { records, count, meta };
}

// =============================================
// FUND CATEGORY MAPPING
// =============================================
function getFundCategory(fundMain, fundSub) {
  const code = (fundMain || '').toUpperCase();
  const sub  = (fundSub  || '').toUpperCase();
  if (code.startsWith('AE')) {
    if (sub.includes('DRUG')) return 'ae_drug';
    return 'ae';
  }
  if (code.startsWith('DM') || code.startsWith('PP')) return 'dmis';
  if (code.startsWith('IP')) return 'ip';
  if (code.startsWith('HC') || code.startsWith('OP')) {
    if (sub.includes('INST') || sub.includes('DENTURE') || sub === 'OPINST') return 'inst';
    if (sub.includes('DRUG') || sub.includes('HERB')) return 'drug';
    return 'hc';
  }
  return 'other';
}

// Fund category Thai label
const FUND_LABELS = {
  hc:'HC ค่าใช้จ่ายสูง', ae:'AE ฉุกเฉิน', ae_drug:'AE ยา', inst:'INST อวัยวะเทียม',
  drug:'DRUG ยา', dmis:'DMIS โรคเฉพาะ', ip:'IP ผู้ป่วยใน', other:'อื่นๆ'
};

// =============================================
// MATCHING ENGINE
// =============================================
function buildEclaimIndex() {
  const byHn = {};
  const byPid = {};
  for (const rec of store.eclaimRecords) {
    if (rec.hn) {
      if (!byHn[rec.hn]) byHn[rec.hn] = [];
      byHn[rec.hn].push(rec);
    }
    if (rec.pid) {
      if (!byPid[rec.pid]) byPid[rec.pid] = [];
      byPid[rec.pid].push(rec);
    }
  }
  return {byHn, byPid};
}

function getMatchStatus(imp, eclaimMatches) {
  if (!eclaimMatches || eclaimMatches.length === 0) return 'none';
  const hasDeny = eclaimMatches.some(r => r.isDeny);
  if (hasDeny && eclaimMatches.length === eclaimMatches.filter(r => r.isDeny).length) return 'deny';
  const totalRep = eclaimMatches.reduce((s, r) => s + r.netComp, 0);
  const diff = Math.abs(totalRep - imp.rightPay);
  if (diff < 1 || (imp.rightPay > 0 && diff / imp.rightPay < 0.05)) return 'matched';
  if (totalRep > 0) return 'partial';
  return 'none';
}

function getMatchedData() {
  const idx = buildEclaimIndex();
  return store.impRecords.map((imp, _storeIdx) => {
    let matches = idx.byHn[imp.hn] || [];
    if (matches.length === 0 && imp.pid) matches = idx.byPid[imp.pid] || [];
    if (imp.an && matches.length > 1) {
      const anMatch = matches.filter(r => r.an === imp.an);
      if (anMatch.length > 0) matches = anMatch;
    }
    const totalRep   = matches.reduce((s, r) => s + r.netComp, 0);
    const totalHc    = matches.reduce((s, r) => s + r.hc, 0);
    const totalAe    = matches.reduce((s, r) => s + r.ae, 0);
    const totalInst  = matches.reduce((s, r) => s + r.inst, 0);
    const totalIp    = matches.reduce((s, r) => s + r.ip, 0);
    const totalDmis  = matches.reduce((s, r) => s + r.dmis, 0);
    const totalDrug  = matches.reduce((s, r) => s + r.drug, 0);
    const repNos     = [...new Set(matches.map(r => r.repNo).filter(Boolean))].join(', ');
    const status     = getMatchStatus(imp, matches);
    const diff       = totalRep - imp.rightPay;
    // Build กองทุนหลัก breakdown map  { HC16: 150, AE04: 500, ... }
    const fundCodeMap = {};
    const fundSubMap  = {};
    matches.forEach(r => {
      if (r.fundMain) fundCodeMap[r.fundMain] = (fundCodeMap[r.fundMain] || 0) + r.netComp;
      if (r.fundSub)  fundSubMap[r.fundSub]   = (fundSubMap[r.fundSub]  || 0) + r.netComp;
    });
    return { ...imp, _storeIdx, matches, totalRep, totalHc, totalAe, totalInst, totalIp, totalDmis, totalDrug, repNos, status, diff, fundCodeMap, fundSubMap };
  });
}

// =============================================
// REFRESH ALL VIEWS
// =============================================
function refreshAll() {
  updateImportStats();
  populateFilters();
  renderDashboard();
  renderMonthly();
  renderMatching();
  renderAccounting();
  renderEclaimTable();
  renderImpFileList();
  renderEclaimFileList();
  saveToStorage();
}

function updateImportStats() {
  document.getElementById('imp-record-count').textContent = fmtInt(store.impRecords.length);
  document.getElementById('eclaim-record-count').textContent = fmtInt(store.eclaimRecords.length);
  const repNos = new Set(store.eclaimRecords.map(r => r.repNo).filter(Boolean));
  document.getElementById('rep-count').textContent = fmtInt(repNos.size);
  const months = new Set(store.impRecords.map(r => r.disMonth).filter(m => m !== 'ไม่ระบุ'));
  document.getElementById('month-count').textContent = fmtInt(months.size);
}

function getUniqueMonths() {
  return [...new Set(store.impRecords.map(r => r.disMonth))].filter(m => m !== 'ไม่ระบุ').sort();
}

function getUniqueRights() {
  return [...new Set(store.impRecords.map(r => r.rightNorm))].sort();
}

function populateFilters() {
  const months = getUniqueMonths();
  const rights = getUniqueRights();
  ['filter-month','match-filter-month','acc-month'].forEach(id => {
    const sel = document.getElementById(id);
    const val = sel.value;
    sel.innerHTML = '<option value="">-- ทุกเดือน --</option>';
    months.forEach(m => sel.innerHTML += `<option value="${m}" ${val===m?'selected':''}>${monthLabel(m)}</option>`);
  });
  ['filter-right','match-filter-right','acc-right'].forEach(id => {
    const sel = document.getElementById(id);
    const val = sel.value;
    sel.innerHTML = '<option value="">-- ทุกสิทธิ --</option>';
    rights.forEach(r => sel.innerHTML += `<option value="${r}" ${val===r?'selected':''}>${r}</option>`);
  });
}

// =============================================
// DASHBOARD
// =============================================
let chartRight = null, chartStatus = null;

function renderDashboard() {
  const data = getMatchedData();
  const total = data.length;
  const matched = data.filter(r => r.status === 'matched').length;
  const partial = data.filter(r => r.status === 'partial').length;
  const noneCount = data.filter(r => r.status === 'none').length;

  const totalAmt = data.reduce((s, r) => s + r.rightPay, 0);
  const matchedAmt = data.filter(r => r.status === 'matched').reduce((s, r) => s + r.totalRep, 0);
  const partialAmt = data.filter(r => r.status === 'partial').reduce((s, r) => s + r.totalRep, 0);
  const noneAmt    = data.filter(r => r.status === 'none').reduce((s, r) => s + r.rightPay, 0);

  document.getElementById('d-total-count').textContent = fmtInt(total);
  document.getElementById('d-total-amt').textContent = '฿ ' + fmt(totalAmt);
  document.getElementById('d-matched-count').textContent = fmtInt(matched);
  document.getElementById('d-matched-amt').textContent = '฿ ' + fmt(matchedAmt);
  document.getElementById('d-partial-count').textContent = fmtInt(partial);
  document.getElementById('d-partial-amt').textContent = '฿ ' + fmt(partialAmt);
  document.getElementById('d-none-count').textContent = fmtInt(noneCount);
  document.getElementById('d-none-amt').textContent = '฿ ' + fmt(noneAmt);
  document.getElementById('d-imp-files').textContent = fmtInt(store.impFileCount);
  document.getElementById('d-eclaim-files').textContent = 'REP ' + fmtInt(store.eclaimFileCount) + ' ฉบับ';

  // Chart by right
  const rightMap = {};
  data.forEach(r => {
    rightMap[r.rightNorm] = (rightMap[r.rightNorm] || 0) + r.rightPay;
  });
  const rightLabels = Object.keys(rightMap);
  const rightVals = Object.values(rightMap);
  const colors = ['#2980b9','#27ae60','#e67e22','#8e44ad','#e74c3c','#16a085','#f39c12','#2c3e50'];

  if (chartRight) chartRight.destroy();
  chartRight = new Chart(document.getElementById('chart-by-right'), {
    type: 'bar',
    data: {
      labels: rightLabels,
      datasets: [{label:'ยอดตั้งหนี้ (บาท)', data: rightVals, backgroundColor: colors}]
    },
    options: {
      responsive: true,
      plugins: { legend: { display: false } },
      scales: {
        y: { ticks: { callback: v => (v/1000).toFixed(0) + 'K' } },
        x: { ticks: { font: { size: 11 } } }
      }
    }
  });

  // Pie status chart
  if (chartStatus) chartStatus.destroy();
  chartStatus = new Chart(document.getElementById('chart-status'), {
    type: 'doughnut',
    data: {
      labels: ['จับคู่ครบ','บางส่วน','ไม่มี REP','DENY'],
      datasets: [{
        data: [matched, partial, noneCount, data.filter(r=>r.status==='deny').length],
        backgroundColor: ['#27ae60','#e67e22','#e74c3c','#8e44ad']
      }]
    },
    options: { responsive: true, plugins: { legend: { position: 'bottom', labels: { font: { size: 11 } } } } }
  });

  // Monthly table
  const monthlyMap = {};
  data.forEach(r => {
    const key = `${r.disMonth}||${r.rightNorm}`;
    if (!monthlyMap[key]) monthlyMap[key] = {month:r.disMonth, right:r.rightNorm, count:0, total:0, patPay:0, rightPay:0, rep:0};
    monthlyMap[key].count++;
    monthlyMap[key].total += r.totalCost;
    monthlyMap[key].patPay += r.patPay;
    monthlyMap[key].rightPay += r.rightPay;
    monthlyMap[key].rep += r.totalRep;
  });

  const rows = Object.values(monthlyMap).sort((a,b) => a.month.localeCompare(b.month) || a.right.localeCompare(b.right));
  document.getElementById('dash-month-badge').textContent = rows.length + ' รายการ';
  document.getElementById('dash-monthly-body').innerHTML = rows.length === 0
    ? '<tr><td colspan="9" style="text-align:center;padding:24px;color:#a0aec0">ยังไม่มีข้อมูล</td></tr>'
    : rows.map(r => {
        const diff = r.rep - r.rightPay;
        const pct = r.rightPay > 0 ? (r.rep/r.rightPay*100).toFixed(1) : '0';
        let status = pct >= 95 ? '<span class="badge-matched">✅ ครบ</span>' : pct > 0 ? '<span class="badge-partial">⚠️ บางส่วน</span>' : '<span class="badge-none">❌ ยังไม่มี</span>';
        return `<tr>
          <td>${monthLabel(r.month)}</td>
          <td>${r.right}</td>
          <td class="num">${fmtInt(r.count)}</td>
          <td class="num">${fmt(r.total)}</td>
          <td class="num">${fmt(r.patPay)}</td>
          <td class="num">${fmt(r.rightPay)}</td>
          <td class="num">${fmt(r.rep)}</td>
          <td class="num" style="color:${diff<0?'#e74c3c':diff>0?'#27ae60':'inherit'}">${fmt(diff)}</td>
          <td>${status}</td>
        </tr>`;
      }).join('');
}

// =============================================
// MONTHLY VIEW
// =============================================
function renderMonthly() {
  const fMonth = document.getElementById('filter-month').value;
  const fRight = document.getElementById('filter-right').value;
  const fPtype = document.getElementById('filter-ptype').value;
  const data = getMatchedData().filter(r =>
    (!fMonth || r.disMonth === fMonth) &&
    (!fRight || r.rightNorm === fRight) &&
    (!fPtype || r.ptype === fPtype)
  );

  // Summary
  const summaryMap = {};
  data.forEach(r => {
    const key = `${r.disMonth}||${r.rightNorm}`;
    if (!summaryMap[key]) summaryMap[key] = {month:r.disMonth, right:r.rightNorm, count:0, total:0, patPay:0, rightPay:0, rep:0};
    summaryMap[key].count++;
    summaryMap[key].total += r.totalCost;
    summaryMap[key].patPay += r.patPay;
    summaryMap[key].rightPay += r.rightPay;
    summaryMap[key].rep += r.totalRep;
  });
  const rows = Object.values(summaryMap).sort((a,b) => a.month.localeCompare(b.month));
  document.getElementById('monthly-count-badge').textContent = rows.length + ' รายการ';
  const totals = {count:0, total:0, patPay:0, rightPay:0, rep:0};
  rows.forEach(r => { totals.count+=r.count; totals.total+=r.total; totals.patPay+=r.patPay; totals.rightPay+=r.rightPay; totals.rep+=r.rep; });

  document.getElementById('monthly-summary-body').innerHTML = rows.length === 0
    ? '<tr><td colspan="9" style="text-align:center;padding:24px;color:#a0aec0">ไม่มีข้อมูล</td></tr>'
    : rows.map(r => {
        const diff = r.rep - r.rightPay;
        const pct = r.rightPay > 0 ? (r.rep/r.rightPay*100).toFixed(1) : '0';
        return `<tr>
          <td>${monthLabel(r.month)}</td><td>${r.right}</td>
          <td class="num">${fmtInt(r.count)}</td>
          <td class="num">${fmt(r.total)}</td>
          <td class="num">${fmt(r.patPay)}</td>
          <td class="num">${fmt(r.rightPay)}</td>
          <td class="num">${fmt(r.rep)}</td>
          <td class="num" style="color:${diff<0?'#e74c3c':diff>0?'#27ae60':'inherit'}">${fmt(diff)}</td>
          <td class="num">${pct}%</td>
        </tr>`;
      }).join('');
  document.getElementById('monthly-summary-foot').innerHTML = `<tr class="tfoot">
    <td colspan="2">รวมทั้งหมด</td>
    <td class="num">${fmtInt(totals.count)}</td>
    <td class="num">${fmt(totals.total)}</td>
    <td class="num">${fmt(totals.patPay)}</td>
    <td class="num">${fmt(totals.rightPay)}</td>
    <td class="num">${fmt(totals.rep)}</td>
    <td class="num">${fmt(totals.rep - totals.rightPay)}</td>
    <td class="num">${totals.rightPay > 0 ? (totals.rep/totals.rightPay*100).toFixed(1) : '0'}%</td>
  </tr>`;

  // Patient list
  document.getElementById('patient-list-badge').textContent = data.length + ' ราย';
  document.getElementById('patient-list-body').innerHTML = data.length === 0
    ? '<tr><td colspan="11" style="text-align:center;padding:24px;color:#a0aec0">ไม่มีข้อมูล</td></tr>'
    : data.map(r => {
        const sb = r.status === 'matched' ? 'badge-matched' : r.status === 'partial' ? 'badge-partial' : r.status === 'deny' ? 'badge-deny' : 'badge-none';
        const sl = r.status === 'matched' ? '✅ ครบ' : r.status === 'partial' ? '⚠️ บางส่วน' : r.status === 'deny' ? '🚫 DENY' : '❌ ไม่มี';
        return `<tr>
          <td>${r.hn}</td><td>${r.an}</td><td>${r.name}</td>
          <td>${r.dischargeDate ? r.dischargeDate.toLocaleDateString('th-TH') : '-'}</td>
          <td>${r.rightNorm}</td>
          <td class="num">${fmt(r.patPay)}</td>
          <td class="num">${fmt(r.rightPay)}</td>
          <td class="num">${fmt(r.totalCost)}</td>
          <td class="num">${fmt(r.totalRep)}</td>
          <td><span class="${sb}">${sl}</span></td>
          <td><button class="btn btn-danger btn-sm" onclick="deleteImpRecord(${r._storeIdx})" title="ลบรายการนี้">🗑️</button></td>
        </tr>`;
      }).join('');
}

// =============================================
// MATCHING VIEW
// =============================================
function renderMatching() {
  const fMonth  = document.getElementById('match-filter-month').value;
  const fStatus = document.getElementById('match-filter-status').value;
  const fRight  = document.getElementById('match-filter-right').value;
  const data = getMatchedData().filter(r =>
    (!fMonth  || r.disMonth === fMonth) &&
    (!fStatus || r.status === fStatus) &&
    (!fRight  || r.rightNorm === fRight)
  );

  const matched = data.filter(r => r.status === 'matched').length;
  const partial = data.filter(r => r.status === 'partial').length;
  const none    = data.filter(r => r.status === 'none').length;
  const deny    = data.filter(r => r.status === 'deny').length;
  document.getElementById('m-matched').textContent = fmtInt(matched);
  document.getElementById('m-partial').textContent = fmtInt(partial);
  document.getElementById('m-none').textContent    = fmtInt(none);
  document.getElementById('m-deny').textContent    = fmtInt(deny);

  // Matching detail table
  let tRightPay=0, tRep=0, tHc=0, tAe=0, tInst=0, tIp=0, tDmis=0, tDrug=0;
  document.getElementById('matching-body').innerHTML = data.length === 0
    ? '<tr><td colspan="15" style="text-align:center;padding:24px;color:#a0aec0">ไม่มีข้อมูล</td></tr>'
    : data.map(r => {
        tRightPay+=r.rightPay; tRep+=r.totalRep; tHc+=r.totalHc; tAe+=r.totalAe;
        tInst+=r.totalInst; tIp+=r.totalIp; tDmis+=r.totalDmis; tDrug+=r.totalDrug;
        const sb = r.status === 'matched' ? 'badge-matched' : r.status === 'partial' ? 'badge-partial' : r.status === 'deny' ? 'badge-deny' : 'badge-none';
        const sl = r.status === 'matched' ? '✅ ครบ' : r.status === 'partial' ? '⚠️ บางส่วน' : r.status === 'deny' ? '🚫 DENY' : '❌ ไม่มี';
        return `<tr>
          <td>${r.hn}</td><td>${r.name}</td>
          <td>${r.dischargeDate ? r.dischargeDate.toLocaleDateString('th-TH') : '-'}</td>
          <td>${r.rightNorm}</td>
          <td style="font-size:11px;color:#4a5568">${r.repNos || '-'}</td>
          <td class="num">${fmt(r.rightPay)}</td>
          <td class="num">${fmt(r.totalRep)}</td>
          <td class="num">${r.totalHc ? fmt(r.totalHc) : '-'}</td>
          <td class="num">${r.totalAe ? fmt(r.totalAe) : '-'}</td>
          <td class="num">${r.totalInst ? fmt(r.totalInst) : '-'}</td>
          <td class="num">${r.totalIp ? fmt(r.totalIp) : '-'}</td>
          <td class="num">${r.totalDmis ? fmt(r.totalDmis) : '-'}</td>
          <td class="num">${r.totalDrug ? fmt(r.totalDrug) : '-'}</td>
          <td class="num" style="color:${r.diff<0?'#e74c3c':r.diff>0?'#27ae60':'inherit'}">${fmt(r.diff)}</td>
          <td><span class="${sb}">${sl}</span></td>
        </tr>`;
      }).join('');

  document.getElementById('matching-foot').innerHTML = `<tr class="tfoot">
    <td colspan="5">รวม</td>
    <td class="num">${fmt(tRightPay)}</td>
    <td class="num">${fmt(tRep)}</td>
    <td class="num">${fmt(tHc)}</td>
    <td class="num">${fmt(tAe)}</td>
    <td class="num">${fmt(tInst)}</td>
    <td class="num">${fmt(tIp)}</td>
    <td class="num">${fmt(tDmis)}</td>
    <td class="num">${fmt(tDrug)}</td>
    <td class="num">${fmt(tRep - tRightPay)}</td>
    <td></td>
  </tr>`;

  // Fund summary by right
  const fundMap = {};
  data.forEach(r => {
    if (!fundMap[r.rightNorm]) fundMap[r.rightNorm] = {count:0, rightPay:0, rep:0, hc:0, ae:0, inst:0, ip:0, dmis:0, drug:0};
    const f = fundMap[r.rightNorm];
    f.count++; f.rightPay+=r.rightPay; f.rep+=r.totalRep;
    f.hc+=r.totalHc; f.ae+=r.totalAe; f.inst+=r.totalInst;
    f.ip+=r.totalIp; f.dmis+=r.totalDmis; f.drug+=r.totalDrug;
  });
  document.getElementById('fund-summary-body').innerHTML = Object.entries(fundMap).map(([right, f]) =>
    `<tr>
      <td>${right}</td>
      <td class="num">${fmtInt(f.count)}</td>
      <td class="num">${fmt(f.rightPay)}</td>
      <td class="num">${fmt(f.rep)}</td>
      <td class="num">${f.hc ? fmt(f.hc) : '-'}</td>
      <td class="num">${f.ae ? fmt(f.ae) : '-'}</td>
      <td class="num">${f.inst ? fmt(f.inst) : '-'}</td>
      <td class="num">${f.ip ? fmt(f.ip) : '-'}</td>
      <td class="num">${f.dmis ? fmt(f.dmis) : '-'}</td>
      <td class="num">${f.drug ? fmt(f.drug) : '-'}</td>
      <td class="num" style="color:${f.rep-f.rightPay<0?'#e74c3c':'#27ae60'}">${fmt(f.rep-f.rightPay)}</td>
    </tr>`
  ).join('') || '<tr><td colspan="11" style="text-align:center;padding:16px;color:#a0aec0">ไม่มีข้อมูล</td></tr>';

  // === กองทุนหลัก (Fund Code) breakdown ===
  // Aggregate from eclaim records (only matched ones within current filter)
  const matchedHns = new Set(data.filter(r => r.status !== 'none').map(r => r.hn));
  const rightOfHn  = {};
  data.forEach(r => { rightOfHn[r.hn] = r.rightNorm; });

  // key: rightNorm|fundMain|fundSub  → {count, amount}
  const codeMap = {};
  store.eclaimRecords.forEach(rec => {
    if (!matchedHns.has(rec.hn)) return;
    if (!rec.fundMain) return;
    const right = rightOfHn[rec.hn] || 'ไม่ระบุ';
    // Apply filters
    if (fMonth) {
      const m = rec.serviceDate ? monthKey(rec.serviceDate) : (rec.admDate ? monthKey(rec.admDate) : '');
      if (m && m !== fMonth) return;
    }
    if (fRight && right !== fRight) return;
    const key = `${right}||${rec.fundMain}||${rec.fundSub || '-'}`;
    if (!codeMap[key]) codeMap[key] = {right, fundMain: rec.fundMain, fundSub: rec.fundSub || '-', cat: getFundCategory(rec.fundMain, rec.fundSub), count: 0, amount: 0};
    codeMap[key].count++;
    codeMap[key].amount += rec.netComp;
  });

  const codeRows = Object.values(codeMap).sort((a,b) =>
    a.right.localeCompare(b.right) || a.fundMain.localeCompare(b.fundMain) || a.fundSub.localeCompare(b.fundSub)
  );
  let codeTotal = 0;
  document.getElementById('fund-code-body').innerHTML = codeRows.length === 0
    ? '<tr><td colspan="6" style="text-align:center;padding:16px;color:#a0aec0">ไม่มีข้อมูล</td></tr>'
    : codeRows.map(r => {
        codeTotal += r.amount;
        const catLabel = FUND_LABELS[r.cat] || r.cat;
        const catColor = r.cat==='hc'?'#2980b9':r.cat.startsWith('ae')?'#e67e22':r.cat==='dmis'?'#8e44ad':r.cat==='inst'?'#16a085':r.cat==='drug'?'#c0392b':r.cat==='ip'?'#27ae60':'#718096';
        return `<tr>
          <td>${r.right}</td>
          <td><b style="background:#f1f5f9;padding:2px 6px;border-radius:4px;font-size:12px">${r.fundMain}</b></td>
          <td><span style="color:${catColor};font-size:11px;font-weight:600">${catLabel}</span></td>
          <td style="font-size:11px;color:#4a5568">${r.fundSub}</td>
          <td class="num">${fmtInt(r.count)}</td>
          <td class="num">${fmt(r.amount)}</td>
        </tr>`;
      }).join('');
  document.getElementById('fund-code-foot').innerHTML = `<tr class="tfoot">
    <td colspan="4">รวมทั้งหมด</td>
    <td class="num">${fmtInt(codeRows.reduce((s,r)=>s+r.count,0))}</td>
    <td class="num">${fmt(codeTotal)}</td>
  </tr>`;
}

// =============================================
// ACCOUNTING REPORT
// =============================================
function renderAccounting() {
  const fMonth = document.getElementById('acc-month').value;
  const fRight = document.getElementById('acc-right').value;
  const container = document.getElementById('accounting-report');

  if (!fMonth) {
    container.innerHTML = '<div style="text-align:center;padding:40px;color:#a0aec0;background:#fff;border-radius:10px">เลือกเดือนที่ต้องการออกรายงาน</div>';
    return;
  }

  const data = getMatchedData().filter(r =>
    r.disMonth === fMonth &&
    (!fRight || r.rightNorm === fRight)
  );

  if (data.length === 0) {
    container.innerHTML = '<div style="text-align:center;padding:40px;color:#a0aec0;background:#fff;border-radius:10px">ไม่พบข้อมูลในเดือนที่เลือก</div>';
    return;
  }

  const monthStr = monthLabel(fMonth);
  const totalCost = data.reduce((s,r) => s + r.totalCost, 0);
  const patPay    = data.reduce((s,r) => s + r.patPay, 0);
  const rightPay  = data.reduce((s,r) => s + r.rightPay, 0);
  const repPay    = data.reduce((s,r) => s + r.totalRep, 0);
  const diffAmt   = repPay - rightPay;
  const outstanding = rightPay - repPay;

  // Group by right+ptype for accounting lines (แยก OPD/IPD เพื่อได้รหัสบัญชีที่ถูกต้อง)
  const rightMap = {};
  data.forEach(r => {
    const key = r.rightNorm + '||' + r.ptype;
    if (!rightMap[key]) rightMap[key] = {right:r.rightNorm, ptype:r.ptype, count:0, rightPay:0, repPay:0, hc:0, ae:0, inst:0, ip:0, dmis:0, drug:0};
    const m = rightMap[key];
    m.count++; m.rightPay+=r.rightPay; m.repPay+=r.totalRep;
    m.hc+=r.totalHc; m.ae+=r.totalAe; m.inst+=r.totalInst;
    m.ip+=r.totalIp; m.dmis+=r.totalDmis; m.drug+=r.totalDrug;
  });

  const rightRows = Object.values(rightMap).sort((a,b) => {
    const ac = getAccountCode(a.right, a.ptype).code;
    const bc = getAccountCode(b.right, b.ptype).code;
    return ac.localeCompare(bc);
  });

  container.innerHTML = `
  <div class="report-page" id="acc-printable">
    <div class="report-title">โรงพยาบาลป่าพะยอม</div>
    <div class="report-sub">รายงานลูกหนี้ค่ารักษาพยาบาล ประจำเดือน ${monthStr}</div>
    <div class="report-sub" style="font-size:12px;color:#718096">ออกรายงานวันที่ ${new Date().toLocaleDateString('th-TH', {year:'numeric',month:'long',day:'numeric'})}</div>
    <hr style="margin:12px 0;border-color:#e2e8f0">

    <div class="report-section-title">ส่วนสูง — ยอดตั้งลูกหนี้ (เดบิต)</div>
    <table style="width:100%;border-collapse:collapse;font-size:12px;margin-bottom:8px">
      <thead><tr style="background:#f1f5f9">
        <th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e2e8f0">รหัสบัญชี</th>
        <th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e2e8f0">ชื่อบัญชี / สิทธิ</th>
        <th style="padding:6px 10px;text-align:right;border-bottom:1px solid #e2e8f0">จำนวนราย</th>
        <th style="padding:6px 10px;text-align:right;border-bottom:1px solid #e2e8f0">เดบิต (บาท)</th>
        <th style="padding:6px 10px;text-align:right;border-bottom:1px solid #e2e8f0">เครดิต (บาท)</th>
      </tr></thead>
      <tbody>
        ${rightRows.map(r => {
          const acc = getAccountCode(r.right, r.ptype);
          return `<tr>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;color:#1a5276;font-weight:600;font-family:monospace">${acc.code}</td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0">${acc.name}</td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">${fmtInt(r.count)}</td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">${fmt(r.rightPay)}</td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">-</td>
        </tr>`;}).join('')}
        <tr style="font-weight:700;background:#f8fafc">
          <td colspan="2" style="padding:7px 10px">รวมยอดตั้งลูกหนี้</td>
          <td style="padding:7px 10px;text-align:right">${fmtInt(data.length)}</td>
          <td style="padding:7px 10px;text-align:right;color:#1a5276">${fmt(rightPay)}</td>
          <td style="padding:7px 10px;text-align:right">-</td>
        </tr>
        <tr><td colspan="2" style="padding:5px 10px;color:#718096;font-size:11px">  ค่าใช้จ่ายทั้งหมด</td>
          <td style="padding:5px 10px;text-align:right;font-size:11px;color:#718096">${fmtInt(data.length)}</td>
          <td style="padding:5px 10px;text-align:right;font-size:11px;color:#718096">${fmt(totalCost)}</td>
          <td style="padding:5px 10px;text-align:right;font-size:11px;color:#718096">-</td>
        </tr>
        <tr><td colspan="2" style="padding:5px 10px;color:#718096;font-size:11px">  ผู้ป่วยชำระเอง</td>
          <td style="padding:5px 10px;text-align:right;font-size:11px;color:#718096">-</td>
          <td style="padding:5px 10px;text-align:right;font-size:11px;color:#718096">-</td>
          <td style="padding:5px 10px;text-align:right;font-size:11px;color:#718096">${fmt(patPay)}</td>
        </tr>
      </tbody>
    </table>

    <div class="report-section-title">รายละเอียดการรับชำระ — แยกตามสิทธิ/ประเภทผู้ป่วย และหมวดกองทุน (เครดิต)</div>
    <table style="width:100%;border-collapse:collapse;font-size:11.5px;margin-bottom:8px">
      <thead><tr style="background:#f1f5f9">
        <th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e2e8f0;font-family:monospace">รหัสบัญชี GFMIS</th>
        <th style="padding:6px 10px;text-align:left;border-bottom:1px solid #e2e8f0">สิทธิ / ประเภท</th>
        <th style="padding:6px 10px;text-align:right;border-bottom:1px solid #e2e8f0">ชดเชยสุทธิ</th>
        <th style="padding:6px 10px;text-align:right;border-bottom:1px solid #e2e8f0">HC ค่าใช้จ่ายสูง<br><span style="font-size:10px;font-weight:normal;font-family:monospace">218/219</span></th>
        <th style="padding:6px 10px;text-align:right;border-bottom:1px solid #e2e8f0">AE ฉุกเฉิน<br><span style="font-size:10px;font-weight:normal;font-family:monospace">216/217</span></th>
        <th style="padding:6px 10px;text-align:right;border-bottom:1px solid #e2e8f0">INST อวัยวะ</th>
        <th style="padding:6px 10px;text-align:right;border-bottom:1px solid #e2e8f0">DMI โรคเฉพาะ<br><span style="font-size:10px;font-weight:normal;font-family:monospace">220/221</span></th>
        <th style="padding:6px 10px;text-align:right;border-bottom:1px solid #e2e8f0">DRUG ยา</th>
      </tr></thead>
      <tbody>
        ${rightRows.map(r => {
          const acc = getAccountCode(r.right, r.ptype);
          return `<tr>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;font-family:monospace;font-size:11px;color:#1a5276;font-weight:600">${acc.code}</td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0">${r.right} <span style="font-size:10px;background:#e2e8f0;border-radius:3px;padding:1px 4px">${r.ptype||''}</span></td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">${fmt(r.repPay)}</td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">${r.hc ? fmt(r.hc) : '-'}</td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">${r.ae ? fmt(r.ae) : '-'}</td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">${r.inst ? fmt(r.inst) : '-'}</td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">${r.dmis ? fmt(r.dmis) : '-'}</td>
          <td style="padding:5px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">${r.drug ? fmt(r.drug) : '-'}</td>
        </tr>`;}).join('')}
        <tr style="font-weight:700;background:#f8fafc">
          <td colspan="2" style="padding:7px 10px">รวมรับชำระทั้งหมด</td>
          <td style="padding:7px 10px;text-align:right;color:#27ae60">${fmt(repPay)}</td>
          <td colspan="5" style="padding:7px 10px"></td>
        </tr>
      </tbody>
    </table>

    ${(() => {
      // กองทุนหลัก detail breakdown from eclaim records
      const matchedHns = new Set(data.filter(r => r.totalRep > 0).map(r => r.hn));
      const codeAcc = {};
      store.eclaimRecords.forEach(rec => {
        if (!matchedHns.has(rec.hn) || !rec.fundMain) return;
        const svcMonth = rec.serviceDate ? monthKey(rec.serviceDate) : (rec.admDate ? monthKey(rec.admDate) : '');
        if (fMonth && svcMonth && svcMonth !== fMonth) return;
        const cat = getFundCategory(rec.fundMain, rec.fundSub || '');
        const key = rec.fundMain + '||' + (rec.fundSub || '-');
        if (!codeAcc[key]) codeAcc[key] = {fundMain: rec.fundMain, fundSub: rec.fundSub||'-', cat, count:0, amount:0};
        codeAcc[key].count++;
        codeAcc[key].amount += rec.netComp;
      });
      const cRows = Object.values(codeAcc).sort((a,b) => a.fundMain.localeCompare(b.fundMain));
      if (cRows.length === 0) return '';

      // Map fund category → GFMIS account  (UC เป็น default; ใช้ ptype จาก fundMain hint)
      // hc/ae/dmis → CR (216/217); drug/inst → หมวดหลัก (201/202)
      const fundCatToAcct = (cat, fundMain) => {
        const isIP = /[_\-]?IP\b/i.test(fundMain);
        const pt   = isIP ? 'IPD' : 'OPD';
        return getAccountCode('UC (บัตรทอง)', pt, cat);
      };

      return `<div class="report-section-title">รายละเอียดกองทุนหลัก (Fund Code) — รหัสบัญชี GFMIS ตามผังบัญชี 2560</div>
      <table style="width:100%;border-collapse:collapse;font-size:11.5px;margin-bottom:8px">
        <thead><tr style="background:#f1f5f9">
          <th style="padding:5px 10px;text-align:left;border-bottom:1px solid #e2e8f0">รหัสบัญชี GFMIS</th>
          <th style="padding:5px 10px;text-align:left;border-bottom:1px solid #e2e8f0">รหัสกองทุน REP</th>
          <th style="padding:5px 10px;text-align:left;border-bottom:1px solid #e2e8f0">หมวดกองทุน</th>
          <th style="padding:5px 10px;text-align:left;border-bottom:1px solid #e2e8f0">กองทุนย่อย</th>
          <th style="padding:5px 10px;text-align:right;border-bottom:1px solid #e2e8f0">จำนวนรายการ</th>
          <th style="padding:5px 10px;text-align:right;border-bottom:1px solid #e2e8f0">ยอดชดเชย (บาท)</th>
        </tr></thead>
        <tbody>
          ${cRows.map(r => {
            const acct = fundCatToAcct(r.cat, r.fundMain);
            return `<tr>
            <td style="padding:4px 10px;border-bottom:1px dotted #e2e8f0;font-family:monospace;font-weight:600;color:#1a5276;font-size:11px">${acct.code}</td>
            <td style="padding:4px 10px;border-bottom:1px dotted #e2e8f0;font-weight:600">${r.fundMain}</td>
            <td style="padding:4px 10px;border-bottom:1px dotted #e2e8f0;color:#2980b9">${FUND_LABELS[r.cat]||r.cat}</td>
            <td style="padding:4px 10px;border-bottom:1px dotted #e2e8f0;color:#4a5568;font-size:11px">${r.fundSub}</td>
            <td style="padding:4px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">${fmtInt(r.count)}</td>
            <td style="padding:4px 10px;border-bottom:1px dotted #e2e8f0;text-align:right">${fmt(r.amount)}</td>
          </tr>`;}).join('')}
          <tr style="font-weight:700;background:#f8fafc">
            <td colspan="4" style="padding:6px 10px">รวมทั้งหมด</td>
            <td style="padding:6px 10px;text-align:right">${fmtInt(cRows.reduce((s,r)=>s+r.count,0))}</td>
            <td style="padding:6px 10px;text-align:right;color:#27ae60">${fmt(cRows.reduce((s,r)=>s+r.amount,0))}</td>
          </tr>
        </tbody>
      </table>`;
    })()}

    <div class="report-section-title">ส่วนต่ำ — สรุปลูกหนี้คงเหลือ</div>
    <div style="font-size:13px;padding:4px 0">
      <div class="report-line"><span>ยอดตั้งลูกหนี้ทั้งหมด (เดบิต)</span><span style="font-weight:700">${fmt(rightPay)} บาท</span></div>
      <div class="report-line"><span>ยอดรับชำระจาก eclaim REP (เครดิต)</span><span style="font-weight:700;color:#27ae60">${fmt(repPay)} บาท</span></div>
      <div class="report-line"><span>ผู้ป่วยชำระเอง</span><span>${fmt(patPay)} บาท</span></div>
      <div class="report-total" style="margin-top:8px">
        <span>ลูกหนี้คงเหลือ</span>
        <span style="color:${outstanding > 0 ? '#e74c3c' : '#27ae60'}">${fmt(outstanding)} บาท</span>
      </div>
      <div class="report-line" style="font-size:11px;color:#718096"><span>ส่วนต่าง (REP เกิน/ขาด)</span><span style="color:${diffAmt>=0?'#27ae60':'#e74c3c'}">${fmt(diffAmt)} บาท</span></div>
    </div>

    <hr style="margin:16px 0;border-color:#e2e8f0">
    <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;font-size:12px;text-align:center;margin-top:12px">
      <div><div style="border-top:1px solid #2d3748;padding-top:6px;margin-top:40px">ผู้จัดทำ</div></div>
      <div><div style="border-top:1px solid #2d3748;padding-top:6px;margin-top:40px">หัวหน้ากลุ่มงานการเงิน</div></div>
      <div><div style="border-top:1px solid #2d3748;padding-top:6px;margin-top:40px">ผู้อำนวยการโรงพยาบาล</div></div>
    </div>
  </div>`;
}

function printAccounting() {
  const el = document.getElementById('acc-printable');
  if (!el) { alert('กรุณาเลือกเดือนก่อน'); return; }
  const w = window.open('','_blank');
  w.document.write(`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>รายงานลูกหนี้</title>
    <style>body{font-family:Arial,sans-serif;font-size:13px;padding:20px} table{width:100%;border-collapse:collapse} th,td{padding:5px 8px} @media print{button{display:none}}</style>
  </head><body>${el.outerHTML}</body></html>`);
  w.document.close();
  w.focus();
  setTimeout(() => w.print(), 500);
}

// =============================================
// EXPORTS
// =============================================
function exportMonthlyExcel() {
  const fMonth = document.getElementById('filter-month').value;
  const fRight = document.getElementById('filter-right').value;
  const data = getMatchedData().filter(r =>
    (!fMonth || r.disMonth === fMonth) &&
    (!fRight || r.rightNorm === fRight)
  );
  const rows = data.map(r => ({
    'เดือน': monthLabel(r.disMonth),
    'HN': r.hn, 'AN': r.an, 'ชื่อ-สกุล': r.name,
    'วันจำหน่าย': r.dischargeDate ? r.dischargeDate.toLocaleDateString('th-TH') : '',
    'สิทธิ': r.rightNorm,
    'ผู้ป่วยชำระ': r.patPay, 'สิทธิชำระ': r.rightPay,
    'ค่าใช้จ่ายรวม': r.totalCost, 'REP รับ': r.totalRep,
    'ส่วนต่าง': r.diff, 'สถานะ': r.status
  }));
  const ws = XLSX.utils.json_to_sheet(rows);
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, 'ลูกหนี้รายเดือน');
  XLSX.writeFile(wb, `ลูกหนี้_${fMonth || 'ทุกเดือน'}.xlsx`);
}

function exportMatchingExcel() {
  const data = getMatchedData();
  const rows = data.map(r => ({
    'HN': r.hn, 'AN': r.an, 'ชื่อ-สกุล': r.name,
    'วันจำหน่าย': r.dischargeDate ? r.dischargeDate.toLocaleDateString('th-TH') : '',
    'สิทธิ': r.rightNorm, 'REP No.': r.repNos,
    'สิทธิชำระ': r.rightPay, 'ชดเชยสุทธิ': r.totalRep,
    'HC': r.totalHc, 'AE': r.totalAe, 'INST': r.totalInst,
    'IP': r.totalIp, 'DMIS': r.totalDmis, 'DRUG': r.totalDrug,
    'ส่วนต่าง': r.diff, 'สถานะ': r.status
  }));
  const ws = XLSX.utils.json_to_sheet(rows);
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, 'จับคู่REP');
  XLSX.writeFile(wb, 'matching_REP.xlsx');
}

function exportData() {
  const json = JSON.stringify({
    impRecords: store.impRecords.map(r => ({...r, admitDate: r.admitDate?.toISOString(), dischargeDate: r.dischargeDate?.toISOString()})),
    eclaimRecords: store.eclaimRecords.map(r => ({...r, admDate: r.admDate?.toISOString(), disDate: r.disDate?.toISOString()})),
    impFileCount: store.impFileCount,
    eclaimFileCount: store.eclaimFileCount,
  }, null, 2);
  const blob = new Blob([json], {type:'application/json'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = `ลูกหนี้_backup_${new Date().toLocaleDateString('th-TH').replace(/\//g,'-')}.json`;
  a.click(); URL.revokeObjectURL(url);
  log('💾 บันทึกข้อมูลสำเร็จ', 'ok');
}

function importData(input) {
  const file = input.files[0];
  if (!file) return;
  const reader = new FileReader();
  reader.onload = e => {
    try {
      const data = JSON.parse(e.target.result);
      store.impRecords = (data.impRecords || []).map(r => ({...r, admitDate: r.admitDate ? new Date(r.admitDate) : null, dischargeDate: r.dischargeDate ? new Date(r.dischargeDate) : null}));
      store.eclaimRecords = (data.eclaimRecords || []).map(r => ({
    ...r,
    admDate: r.admDate ? new Date(r.admDate) : null,
    disDate: r.disDate ? new Date(r.disDate) : null,
    serviceDate: r.serviceDate ? new Date(r.serviceDate) : null,
    transferDate: r.transferDate ? new Date(r.transferDate) : null,
  }));
      store.impFileCount = data.impFileCount || 0;
      store.eclaimFileCount = data.eclaimFileCount || 0;
      refreshAll();
      log(`📂 โหลดข้อมูลสำเร็จ — IMP: ${store.impRecords.length} ราย, eclaim: ${store.eclaimRecords.length} ราย`, 'ok');
    } catch(err) {
      log('❌ ไม่สามารถโหลดไฟล์: ' + err.message, 'err');
    }
  };
  reader.readAsText(file, 'UTF-8');
  input.value = '';
}

// =============================================
// LOCALSTORAGE PERSISTENCE
// =============================================
function saveToStorage() {
  const tag = document.getElementById('storage-tag');
  try {
    const data = {
      v: 2,
      impRecords: store.impRecords.map(r => ({
        ...r,
        admitDate: r.admitDate ? r.admitDate.toISOString() : null,
        dischargeDate: r.dischargeDate ? r.dischargeDate.toISOString() : null,
        repRecords: undefined, // ไม่ต้องบันทึก rebuilt dynamically
      })),
      eclaimRecords: store.eclaimRecords.map(r => ({
        ...r,
        admDate:      r.admDate      ? r.admDate.toISOString()      : null,
        disDate:      r.disDate      ? r.disDate.toISOString()       : null,
        serviceDate:  r.serviceDate  ? r.serviceDate.toISOString()  : null,
        transferDate: r.transferDate ? r.transferDate.toISOString() : null,
      })),
      impFileCount: store.impFileCount,
      eclaimFileCount: store.eclaimFileCount,
      impFiles: store.impFiles,
      eclaimFiles: store.eclaimFiles,
    };
    localStorage.setItem('hospital_ar_data_v2', JSON.stringify(data));
    const ts = new Date().toLocaleTimeString('th-TH', {hour:'2-digit', minute:'2-digit'});
    if (tag) { tag.textContent = `💾 บันทึก ${ts}`; tag.className = 'storage-tag saved'; }
  } catch(e) {
    if (tag) { tag.textContent = '⚠️ พื้นที่เต็ม'; tag.className = 'storage-tag warn'; }
    console.warn('localStorage save failed:', e.message);
  }
}

function loadFromStorage() {
  const tag = document.getElementById('storage-tag');
  try {
    const json = localStorage.getItem('hospital_ar_data_v2');
    if (!json) return;
    const data = JSON.parse(json);
    store.impRecords = (data.impRecords || []).map(r => ({
      ...r,
      admitDate:    r.admitDate    ? new Date(r.admitDate)    : null,
      dischargeDate:r.dischargeDate? new Date(r.dischargeDate): null,
      repRecords: [],
    }));
    store.eclaimRecords = (data.eclaimRecords || []).map(r => ({
      ...r,
      admDate:      r.admDate      ? new Date(r.admDate)      : null,
      disDate:      r.disDate      ? new Date(r.disDate)       : null,
      serviceDate:  r.serviceDate  ? new Date(r.serviceDate)  : null,
      transferDate: r.transferDate ? new Date(r.transferDate) : null,
    }));
    store.impFileCount     = data.impFileCount     || 0;
    store.eclaimFileCount  = data.eclaimFileCount  || 0;
    store.impFiles         = data.impFiles         || [];
    store.eclaimFiles      = data.eclaimFiles      || [];
    if (store.impRecords.length > 0 || store.eclaimRecords.length > 0) {
      if (tag) { tag.textContent = '✅ โหลดข้อมูลแล้ว'; tag.className = 'storage-tag saved'; }
      log(`📂 โหลดข้อมูลจากเครื่อง — IMP: ${store.impRecords.length} ราย, eclaim: ${store.eclaimRecords.length} ราย`, 'ok');
      refreshAllNoSave(); // ไม่ save ซ้ำตอน load
    }
  } catch(e) {
    console.warn('localStorage load failed:', e.message);
  }
}

// refreshAll without triggering save (for load-time use)
function refreshAllNoSave() {
  updateImportStats();
  populateFilters();
  renderDashboard();
  renderMonthly();
  renderMatching();
  renderAccounting();
  renderEclaimTable();
  renderImpFileList();
  renderEclaimFileList();
}

// =============================================
// DELETE RECORD FUNCTIONS
// =============================================
function deleteImpRecord(storeIdx) {
  const r = store.impRecords[storeIdx];
  if (!r) return;
  if (!confirm(`ลบรายการ IMP ?\nHN: ${r.hn}  AN: ${r.an || '-'}\nชื่อ: ${r.name}\nสิทธิ: ${r.rightNorm}\nวันที่: ${r.dischargeDate ? r.dischargeDate.toLocaleDateString('th-TH') : '-'}`)) return;
  store.impRecords.splice(storeIdx, 1);
  log(`🗑️ ลบ IMP HN ${r.hn} (${r.name}) แล้ว`, 'warn');
  refreshAll();
}

function deleteEclaimRecord(storeIdx) {
  const r = store.eclaimRecords[storeIdx];
  if (!r) return;
  if (!confirm(`ลบรายการ eclaim ?\nREP No: ${r.repNo || '-'}\nHN: ${r.hn}  AN: ${r.an || '-'}\nกองทุน: ${r.fundMain || '-'} / ${r.fundSub || '-'}\nยอด: ${fmt(r.netComp)} บาท`)) return;
  store.eclaimRecords.splice(storeIdx, 1);
  log(`🗑️ ลบ eclaim HN ${r.hn} REP ${r.repNo || '-'} แล้ว`, 'warn');
  refreshAll();
}

// =============================================
// ECLAIM RECORDS TABLE
// =============================================
function renderEclaimTable() {
  const searchEl = document.getElementById('eclaim-search');
  const fundEl   = document.getElementById('eclaim-fund-filter');
  if (!searchEl) return;
  const search    = searchEl.value.toLowerCase().trim();
  const fundFilter = (fundEl?.value || '').toUpperCase().trim();
  const LIMIT = 500;

  let records = store.eclaimRecords.map((r, i) => ({...r, _storeIdx: i}));
  if (search) records = records.filter(r =>
    (r.hn    || '').toLowerCase().includes(search) ||
    (r.repNo || '').toLowerCase().includes(search) ||
    (r.an    || '').toLowerCase().includes(search)
  );
  if (fundFilter) records = records.filter(r => (r.fundMain || '').toUpperCase().includes(fundFilter));

  const total = records.length;
  const shown = records.slice(0, LIMIT);

  const badge = document.getElementById('eclaim-records-badge');
  if (badge) badge.textContent = fmtInt(total) + ' รายการ';

  const body = document.getElementById('eclaim-records-body');
  if (!body) return;
  body.innerHTML = shown.length === 0
    ? '<tr><td colspan="10" style="text-align:center;padding:16px;color:#a0aec0">ไม่มีข้อมูล</td></tr>'
    : shown.map(r => {
        const svcDate = r.serviceDate || r.admDate || r.disDate || null;
        const fname = (r.sourceFile || '').replace(/.*[/\\]/, '').substring(0, 22);
        return `<tr>
          <td style="color:#718096;font-size:11px">${r._storeIdx + 1}</td>
          <td style="font-size:11px">${r.repNo || '-'}</td>
          <td><b>${r.hn || '-'}</b></td>
          <td style="font-size:11px">${r.an || '-'}</td>
          <td style="font-size:11px">${svcDate ? svcDate.toLocaleDateString('th-TH') : '-'}</td>
          <td><b style="font-size:11px;background:#f1f5f9;padding:1px 5px;border-radius:3px">${r.fundMain || '-'}</b></td>
          <td style="font-size:11px;color:#4a5568">${r.fundSub || '-'}</td>
          <td class="num">${fmt(r.netComp)}</td>
          <td style="font-size:10px;color:#718096;max-width:120px;overflow:hidden;text-overflow:ellipsis" title="${r.sourceFile||''}">${fname}</td>
          <td><button class="btn btn-danger btn-sm" onclick="deleteEclaimRecord(${r._storeIdx})" title="ลบรายการนี้">🗑️</button></td>
        </tr>`;
      }).join('');

  const moreEl = document.getElementById('eclaim-records-more');
  if (moreEl) moreEl.textContent = total > LIMIT ? `แสดง ${LIMIT} จาก ${fmtInt(total)} รายการ (ใช้ตัวกรองเพื่อค้นหาเพิ่มเติม)` : '';
}

// =============================================
// TAB NAVIGATION
// =============================================
function showTab(name) {
  document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
  document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
  document.getElementById(`panel-${name}`).classList.add('active');
  event.target.classList.add('active');
  if (name === 'dashboard') renderDashboard();
  if (name === 'import')    renderEclaimTable();
  if (name === 'monthly')   renderMonthly();
  if (name === 'matching')  renderMatching();
  if (name === 'accounting')renderAccounting();
}

// Init
document.addEventListener('DOMContentLoaded', () => {
  log('🏥 ระบบบริหารลูกหนี้ โรงพยาบาลป่าพะยอม พร้อมใช้งาน', 'ok');
  log('📌 วิธีใช้: นำเข้าไฟล์ IMP ก่อน จากนั้นนำเข้า eclaim REP เพื่อจับคู่', 'ok');
  log('💾 ข้อมูลบันทึกอัตโนมัติในเครื่อง (localStorage) — ไม่สูญหายเมื่อปิด browser', 'ok');
  loadFromStorage();
});
</script>
</body>
</html>

Youez - 2016 - github.com/yon3zu
LinuXploit