Files
sap-erp/dist-1/public/cost-sheet.html
T
John 69b4e68baf
SAP-ERP Portal CI/CD / build (push) Failing after 5m20s
first commit
2026-09-23 17:31:02 +05:30

1021 lines
49 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/auth-guard.js?v=4"></script>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Cost Sheet — Finance</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700;800&display=swap" rel="stylesheet"/>
<script>document.documentElement.setAttribute('data-theme',localStorage.getItem('portal_theme')||'obsidian')</script>
<link rel="stylesheet" href="/theme.css?v=3"/>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Space Grotesk',sans-serif;background:var(--bg,#f8fafc);color:var(--text,#0f172a);-webkit-font-smoothing:antialiased}
/* ── Page wrapper ── */
.page{padding:20px 24px 32px}
/* ── Page header ── */
.page-hd{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:20px}
.page-title{font-size:20px;font-weight:800;letter-spacing:-.4px;color:var(--text)}
.page-sub{font-size:12px;color:var(--text3);margin-top:3px}
/* ── Filter bar ── */
.filter-bar{
display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap;
background:var(--surface);border:1px solid var(--border);
border-radius:14px;padding:16px 20px;margin-bottom:20px;
}
.f-group{display:flex;flex-direction:column;gap:5px;min-width:140px}
.f-label{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text3)}
/* ── Searchable multi-select ── */
.sms-wrap{position:relative;width:100%}
.sms-box{border:1px solid var(--border);border-radius:8px;background:var(--surface);overflow:hidden}
.sms-search{display:flex;align-items:center;gap:6px;padding:5px 8px;border-bottom:1px solid var(--border);background:var(--surface)}
.sms-search input{flex:1;border:none;outline:none;font-size:12px;font-family:'Space Grotesk',sans-serif;background:transparent;color:var(--text)}
.sms-search input::placeholder{color:var(--text3)}
.sms-list{max-height:110px;overflow-y:auto}
.sms-list::-webkit-scrollbar{width:4px}
.sms-list::-webkit-scrollbar-thumb{background:#c1c7ce;border-radius:4px}
.sms-opt{display:flex;align-items:center;gap:8px;padding:5px 10px;cursor:pointer;font-size:12px;color:var(--text);transition:background .1s;user-select:none}
.sms-opt:hover{background:var(--surface3,#f1f5f9)}
.sms-opt.selected{background:#e0f4f4;color:#004d40}
.sms-opt input[type=checkbox]{width:13px;height:13px;accent-color:#047e7e;flex-shrink:0;cursor:pointer}
.sms-chips{display:flex;flex-wrap:wrap;gap:5px;padding:5px 8px;min-height:28px;border-bottom:1px solid var(--border);background:var(--surface3,#f8fafb)}
.sms-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:5px;font-size:11px;font-weight:600;cursor:default}
.sms-chip.teal{background:#e0f4f4;color:#004d40;border:1px solid #80cbc4}
.sms-chip.purple{background:#ede9fe;color:#5b21b6;border:1px solid #c4b5fd}
.sms-chip button{background:none;border:none;cursor:pointer;font-size:13px;line-height:1;padding:0;color:inherit;opacity:.6}
.sms-chip button:hover{opacity:1}
.sms-empty{padding:10px;text-align:center;font-size:11px;color:var(--text3)}
.f-input,.f-select{
padding:8px 11px;border:1px solid var(--border);border-radius:8px;
background:var(--surface);color:var(--text);font-size:13px;
font-family:'Space Grotesk',sans-serif;outline:none;
transition:border-color .15s,box-shadow .15s;
}
.f-input:focus,.f-select:focus{border-color:var(--blue,#3b82f6);box-shadow:0 0 0 3px rgba(59,130,246,.1)}
.btn{
padding:9px 18px;border-radius:9px;border:none;
font-size:13px;font-weight:600;font-family:'Space Grotesk',sans-serif;
cursor:pointer;transition:all .15s;
}
.btn-primary{background:var(--blue,#3b82f6);color:#fff;box-shadow:0 2px 8px rgba(59,130,246,.25)}
.btn-primary:hover{filter:brightness(1.08)}
.btn-primary:disabled{opacity:.5;cursor:not-allowed}
.btn-outline{background:var(--surface);border:1px solid var(--border);color:var(--text2)}
.btn-outline:hover{border-color:var(--blue,#3b82f6);color:var(--blue,#3b82f6)}
/* ── Status bar ── */
.status-bar{
display:flex;align-items:center;gap:8px;
font-size:12px;font-weight:500;color:var(--text3);
margin-bottom:16px;padding:0 2px;
}
.status-dot{width:8px;height:8px;border-radius:50%;background:var(--text3);flex-shrink:0}
.status-dot.ok{background:#16a34a}
.status-dot.loading{background:#f59e0b;animation:pulse 1s infinite}
.status-dot.err{background:#dc2626}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
/* ── Cost sheet table ── */
.cs-wrap{background:var(--surface);border:1px solid var(--border);border-radius:14px;overflow:hidden;margin-bottom:16px}
.cs-tbl-scroll{overflow-x:auto;overflow-y:auto;max-height:52vh}
.cs-section-hd{
display:flex;align-items:center;gap:10px;
padding:10px 16px;
background:var(--surface3,#f1f5f9);
border-bottom:1px solid var(--border);
}
.cs-section-tag{
padding:3px 10px;border-radius:20px;
font-size:10px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;
}
.tag-bb{background:rgba(37,99,235,.1);color:#2563eb}
.tag-capd{background:rgba(124,58,237,.1);color:#7c3aed}
.cs-section-label{font-size:12px;font-weight:700;color:var(--text2)}
table.cs-tbl{width:100%;border-collapse:collapse;font-size:12.5px}
table.cs-tbl th{
padding:9px 12px;text-align:left;
background:var(--surface3,#f1f5f9);
color:var(--text3);font-size:10.5px;font-weight:700;
text-transform:uppercase;letter-spacing:.6px;
border-bottom:1px solid var(--border);white-space:nowrap;
position:sticky;top:0;z-index:2;
}
table.cs-tbl th.num,table.cs-tbl td.num{text-align:right}
table.cs-tbl td{
padding:8px 12px;border-bottom:1px solid var(--border);
color:var(--text);vertical-align:middle;
}
table.cs-tbl tbody tr:last-child td{border-bottom:none}
table.cs-tbl tbody tr:hover td{background:rgba(59,130,246,.03)}
.td-code{font-family:monospace;font-size:11.5px;color:var(--blue,#3b82f6);font-weight:600}
.td-desc{color:var(--text);font-weight:500;max-width:280px}
/* Total rows */
tr.total-row td{
background:var(--surface3,#f1f5f9) !important;
font-weight:700;border-top:2px solid var(--border);
font-size:12px;
}
tr.grand-total td{
background:linear-gradient(135deg,#1e3264,#2563eb) !important;
color:#fff !important;font-weight:700;font-size:13px;
}
tr.grand-total td.num{color:#fff !important}
/* ── Empty / loading states ── */
.empty{padding:40px;text-align:center;color:var(--text3);font-size:13px}
.skeleton-row td{background:linear-gradient(90deg,var(--surface3,#f1f5f9) 25%,var(--surface2,#f8fafc) 50%,var(--surface3,#f1f5f9) 75%);animation:shimmer 1.2s infinite}
@keyframes shimmer{0%{background-position:-400px 0}100%{background-position:400px 0}}
/* ── Configure Allocation Panel ── */
.cfg-panel{background:var(--surface);border:1px solid var(--border);border-radius:14px;margin-bottom:16px;overflow:hidden}
.cfg-hdr{display:flex;align-items:center;gap:10px;padding:11px 18px;cursor:pointer;user-select:none;border-bottom:1px solid transparent;transition:border-color .15s}
.cfg-hdr:hover{background:var(--surface3,#f8fafc)}
.cfg-hdr.open{border-bottom-color:var(--border)}
.cfg-title{font-size:12px;font-weight:700;color:var(--text2);text-transform:uppercase;letter-spacing:.5px}
.cfg-chev{margin-left:auto;font-size:13px;color:var(--text3);transition:transform .2s}
.cfg-hdr.open .cfg-chev{transform:rotate(180deg)}
.cfg-body{padding:14px 18px;display:none}
.cfg-body.open{display:block}
.cfg-table{width:100%;border-collapse:collapse;font-size:12px}
.cfg-table th{padding:6px 10px;text-align:left;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--text3);border-bottom:1px solid var(--border)}
.cfg-table th.r{text-align:right}
.cfg-table td{padding:7px 10px;border-bottom:1px solid var(--border);color:var(--text);vertical-align:middle}
.cfg-table tbody tr:last-child td{border-bottom:none}
.cfg-table td.r{text-align:right}
.cfg-inp{width:80px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;font-size:12px;font-family:'Space Grotesk',sans-serif;background:var(--surface);color:var(--text);outline:none;text-align:right}
.cfg-inp:focus{border-color:var(--blue,#3b82f6);box-shadow:0 0 0 2px rgba(59,130,246,.1)}
.cfg-auto{font-size:11px;color:var(--text3);font-style:italic}
.cfg-badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700}
.cfg-badge.bb{background:rgba(37,99,235,.1);color:#2563eb}
.cfg-badge.capd{background:rgba(124,58,237,.1);color:#7c3aed}
.cfg-reset{background:none;border:none;cursor:pointer;font-size:10px;color:var(--text3);padding:2px 6px;border-radius:4px;font-family:inherit}
.cfg-reset:hover{color:#dc2626;background:rgba(220,38,38,.06)}
.cfg-result{margin-top:10px;padding:8px 12px;background:var(--surface3,#f8fafc);border-radius:8px;font-size:11px;color:var(--text3);display:none}
.cfg-result.show{display:block}
.cfg-result b{color:var(--text2)}
/* ── Toast ── */
.toast-c{position:fixed;bottom:24px;right:24px;z-index:9999;display:flex;flex-direction:column;gap:8px;pointer-events:none}
.toast{padding:11px 18px;border-radius:9px;font-size:12.5px;font-weight:600;box-shadow:0 4px 16px rgba(0,0,0,.15);display:flex;align-items:center;gap:8px;pointer-events:auto;animation:toastIn .2s ease}
.toast.ok{background:#16a34a;color:#fff}
.toast.err{background:#dc2626;color:#fff}
@keyframes toastIn{from{transform:translateY(10px);opacity:0}to{transform:translateY(0);opacity:1}}
@media(max-width:768px){
.page{padding:12px 14px}
.filter-bar{padding:12px}
table.cs-tbl th,table.cs-tbl td{padding:6px 8px;font-size:11px}
}
</style>
</head>
<body>
<div class="toast-c" id="toastC"></div>
<div class="page" id="pageRoot">
<!-- Page Header -->
<div class="page-hd">
<div>
<div class="page-title">Cost Sheet</div>
<div class="page-sub">Production · Sales · RM Consumption · Employee Cost</div>
</div>
<div style="display:flex;gap:8px">
<button class="btn btn-outline" id="btnPrint" disabled onclick="printCostSheet()">Print</button>
<button class="btn btn-outline" id="btnExport" disabled onclick="exportXLSX()">↓ Export Excel</button>
</div>
</div>
<!-- Row 1: dates + actions -->
<div class="filter-bar" style="border-bottom:none;border-radius:14px 14px 0 0;padding-bottom:10px">
<div class="f-group">
<label class="f-label">From Date</label>
<input type="date" class="f-input" id="fromDate"/>
</div>
<div class="f-group">
<label class="f-label">To Date</label>
<input type="date" class="f-input" id="toDate"/>
</div>
<div style="margin-left:auto;display:flex;gap:8px;align-items:flex-end">
<button class="btn btn-primary" id="btnLoad" onclick="loadData()">Load Cost Sheet</button>
<button class="btn btn-outline" onclick="clearAll()" style="color:#dc2626;border-color:#fca5a5">Clear All</button>
</div>
</div>
<!-- Row 2: salary + snapshot searchable multi-selects -->
<div style="display:flex;gap:14px;padding:10px 20px 14px;background:var(--surface);border:1px solid var(--border);border-top:none;border-radius:0 0 14px 14px;margin-bottom:20px;flex-wrap:wrap">
<div class="f-group" style="flex:1;min-width:200px">
<label class="f-label">Salary Periods</label>
<div class="sms-box" id="sms-sal">
<div class="sms-chips" id="sms-sal-chips"></div>
<div class="sms-search">
<input type="text" id="sms-sal-q" placeholder="Search periods…" oninput="smsFilter('sal')">
</div>
<div class="sms-list" id="sms-sal-list"></div>
</div>
</div>
<div class="f-group" style="flex:2;min-width:260px">
<label class="f-label">Employee Pool Snapshot <span style="font-weight:400;color:#9ca3af;text-transform:none">(blank = live data)</span></label>
<div class="sms-box" id="sms-snap">
<div class="sms-chips" id="sms-snap-chips"></div>
<div class="sms-search">
<input type="text" id="sms-snap-q" placeholder="Search snapshots…" oninput="smsFilter('snap')">
</div>
<div class="sms-list" id="sms-snap-list"></div>
</div>
</div>
</div>
<!-- Boiler cost info bar (auto-computed, shown after load) -->
<div style="padding:6px 20px 10px;background:var(--surface);border:1px solid var(--border);border-top:none;border-radius:0 0 14px 14px;margin-bottom:12px">
<div id="boilerInfoBar" style="display:none;font-size:12px;color:#047e7e;font-weight:600;line-height:1.7"></div>
</div>
<!-- Employee pool label (shown after snapshot selection) -->
<div id="snapEmpPoolWrap" style="display:none;padding:6px 20px;background:#f5f0ff;border-radius:8px;margin-bottom:10px">
<span id="snapEmpPoolLabel" style="font-size:12px;color:#7c3aed;font-weight:700"></span>
</div>
<!-- Configure Allocation Panel -->
<div class="cfg-panel">
<div class="cfg-hdr" id="cfgHdr" onclick="toggleCfg()">
<span style="font-size:15px">⚙</span>
<span class="cfg-title">Configure Cost Allocation</span>
<span style="font-size:11px;color:var(--text3);font-weight:400">Blood Bag % vs CAPD % per cost head (blank = auto)</span>
<span class="cfg-chev">▼</span>
</div>
<div class="cfg-body" id="cfgBody">
<table class="cfg-table">
<thead>
<tr>
<th style="min-width:130px">Cost Head</th>
<th class="r" style="min-width:140px">Total Pool (₹)</th>
<th class="r" style="min-width:90px;color:#0ea5e9">Power %</th>
<th class="r" style="min-width:120px;color:#0ea5e9">Power Absorbed (₹)</th>
<th class="r">Blood Bag %</th>
<th class="r" style="min-width:130px">BB Amount (₹)</th>
<th class="r">CAPD %</th>
<th class="r" style="min-width:130px">CAPD Amount (₹)</th>
<th class="r">Auto Basis</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="cfg-badge bb">Employee</span></td>
<td class="r cfg-auto" id="cfg-emp-pool">—</td>
<td class="r"><input class="cfg-inp" id="cfg-emp-power-pct" type="number" min="0" max="100" step="0.1" placeholder="0" style="border-color:#0ea5e9" oninput="syncPowerPct()"></td>
<td class="r cfg-auto" id="cfg-emp-power-abs" style="color:#0ea5e9">—</td>
<td class="r"><input class="cfg-inp" id="cfg-emp-bb" type="number" min="0" max="100" step="0.1" placeholder="auto" oninput="syncCfg('emp')"></td>
<td class="r cfg-auto" id="cfg-emp-bb-amt">—</td>
<td class="r" id="cfg-emp-capd-disp" style="font-size:12px;color:var(--text3)">—</td>
<td class="r cfg-auto" id="cfg-emp-capd-amt">—</td>
<td class="r cfg-auto" id="cfg-emp-auto">salary ratio</td>
<td><button class="cfg-reset" onclick="resetCfg('emp')">Reset</button></td>
</tr>
<tr>
<td><span class="cfg-badge bb">QA Cost</span></td>
<td class="r cfg-auto" id="cfg-qa-pool">—</td>
<td class="r"><input class="cfg-inp" id="cfg-qa-power-pct" type="number" min="0" max="100" step="0.1" placeholder="0" style="border-color:#0ea5e9" oninput="syncPowerPct()"></td>
<td class="r cfg-auto" id="cfg-qa-power-abs" style="color:#0ea5e9">—</td>
<td class="r"><input class="cfg-inp" id="cfg-qa-bb" type="number" min="0" max="100" step="0.1" placeholder="auto" oninput="syncCfg('qa')"></td>
<td class="r cfg-auto" id="cfg-qa-bb-amt">—</td>
<td class="r" id="cfg-qa-capd-disp" style="font-size:12px;color:var(--text3)">—</td>
<td class="r cfg-auto" id="cfg-qa-capd-amt">—</td>
<td class="r cfg-auto" id="cfg-qa-auto">RM ratio</td>
<td><button class="cfg-reset" onclick="resetCfg('qa')">Reset</button></td>
</tr>
<tr>
<td><span class="cfg-badge bb">QC Cost</span></td>
<td class="r cfg-auto" id="cfg-qc-pool">—</td>
<td class="r"><input class="cfg-inp" id="cfg-qc-power-pct" type="number" min="0" max="100" step="0.1" placeholder="0" style="border-color:#0ea5e9" oninput="syncPowerPct()"></td>
<td class="r cfg-auto" id="cfg-qc-power-abs" style="color:#0ea5e9">—</td>
<td class="r"><input class="cfg-inp" id="cfg-qc-bb" type="number" min="0" max="100" step="0.1" placeholder="auto" oninput="syncCfg('qc')"></td>
<td class="r cfg-auto" id="cfg-qc-bb-amt">—</td>
<td class="r" id="cfg-qc-capd-disp" style="font-size:12px;color:var(--text3)">—</td>
<td class="r cfg-auto" id="cfg-qc-capd-amt">—</td>
<td class="r cfg-auto" id="cfg-qc-auto">RM ratio</td>
<td><button class="cfg-reset" onclick="resetCfg('qc')">Reset</button></td>
</tr>
<tr>
<td><span class="cfg-badge capd">Boiler</span></td>
<td class="r cfg-auto" id="cfg-boiler-pool">—</td>
<td class="r"><input class="cfg-inp" id="cfg-boiler-power-pct" type="number" min="0" max="100" step="0.1" placeholder="0" style="border-color:#0ea5e9" oninput="syncPowerPct()"></td>
<td class="r cfg-auto" id="cfg-boiler-power-abs" style="color:#0ea5e9">—</td>
<td class="r"><input class="cfg-inp" id="cfg-boiler-bb" type="number" min="0" max="100" step="0.1" placeholder="auto" oninput="syncCfg('boiler')"></td>
<td class="r cfg-auto" id="cfg-boiler-bb-amt">—</td>
<td class="r" id="cfg-boiler-capd-disp" style="font-size:12px;color:var(--text3)">—</td>
<td class="r cfg-auto" id="cfg-boiler-capd-amt">—</td>
<td class="r cfg-auto" id="cfg-boiler-auto">RM ratio</td>
<td><button class="cfg-reset" onclick="resetCfg('boiler')">Reset</button></td>
</tr>
<tr style="background:rgba(14,165,233,.04)">
<td><span class="cfg-badge capd">Power Cost</span></td>
<td class="r cfg-auto" id="cfg-power-pool">—</td>
<td class="r cfg-auto" id="cfg-power-remaining" style="color:#0ea5e9;font-weight:700">remaining</td>
<td class="r cfg-auto" id="cfg-power-power-abs" style="color:#0ea5e9">—</td>
<td class="r"><input class="cfg-inp" id="cfg-power-bb" type="number" min="0" max="100" step="0.1" placeholder="auto" oninput="syncCfg('power')"></td>
<td class="r cfg-auto" id="cfg-power-bb-amt">—</td>
<td class="r" id="cfg-power-capd-disp" style="font-size:12px;color:var(--text3)">—</td>
<td class="r cfg-auto" id="cfg-power-capd-amt">—</td>
<td class="r cfg-auto" id="cfg-power-auto">RM ratio</td>
<td><button class="cfg-reset" onclick="resetCfg('power')">Reset</button></td>
</tr>
<tr>
<td><span class="cfg-badge capd">R&amp;M Cost</span></td>
<td class="r cfg-auto" id="cfg-repair-pool">—</td>
<td class="r"><input class="cfg-inp" id="cfg-repair-power-pct" type="number" min="0" max="100" step="0.1" placeholder="0" style="border-color:#0ea5e9" oninput="syncPowerPct()"></td>
<td class="r cfg-auto" id="cfg-repair-power-abs" style="color:#0ea5e9">—</td>
<td class="r"><input class="cfg-inp" id="cfg-repair-bb" type="number" min="0" max="100" step="0.1" placeholder="auto" oninput="syncCfg('repair')"></td>
<td class="r cfg-auto" id="cfg-repair-bb-amt">—</td>
<td class="r" id="cfg-repair-capd-disp" style="font-size:12px;color:var(--text3)">—</td>
<td class="r cfg-auto" id="cfg-repair-capd-amt">—</td>
<td class="r cfg-auto" id="cfg-repair-auto">RM ratio</td>
<td><button class="cfg-reset" onclick="resetCfg('repair')">Reset</button></td>
</tr>
<tr>
<td><span class="cfg-badge capd">R&amp;D Cost</span></td>
<td class="r cfg-auto" id="cfg-rd-pool">—</td>
<td class="r"><input class="cfg-inp" id="cfg-rd-power-pct" type="number" min="0" max="100" step="0.1" placeholder="0" style="border-color:#0ea5e9" oninput="syncPowerPct()"></td>
<td class="r cfg-auto" id="cfg-rd-power-abs" style="color:#0ea5e9">—</td>
<td class="r"><input class="cfg-inp" id="cfg-rd-bb" type="number" min="0" max="100" step="0.1" placeholder="auto" oninput="syncCfg('rd')"></td>
<td class="r cfg-auto" id="cfg-rd-bb-amt">—</td>
<td class="r" id="cfg-rd-capd-disp" style="font-size:12px;color:var(--text3)">—</td>
<td class="r cfg-auto" id="cfg-rd-capd-amt">—</td>
<td class="r cfg-auto" id="cfg-rd-auto">RM ratio</td>
<td><button class="cfg-reset" onclick="resetCfg('rd')">Reset</button></td>
</tr>
</tbody>
</table>
<div class="cfg-result" id="cfgResult"></div>
<div style="padding:10px 16px 14px;display:flex;gap:8px;align-items:center">
<button class="btn btn-primary" onclick="loadData()" style="padding:8px 20px;font-size:12px">Apply & Reload</button>
<button class="btn btn-outline" onclick="resetAllCfg()" style="padding:8px 14px;font-size:12px;color:#dc2626;border-color:#fca5a5">Reset All</button>
<span style="font-size:11px;color:var(--text3)">Changes take effect after reload</span>
</div>
</div>
</div>
<!-- Status -->
<div class="status-bar" id="statusBar">
<span class="status-dot" id="statusDot"></span>
<span id="statusMsg">Select a date range and click Load.</span>
</div>
<!-- Content placeholder -->
<div id="csContent"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script>
'use strict';
const tok = sessionStorage.getItem('portal_token') || '';
const hdrs = { 'Content-Type':'application/json', Authorization: 'Bearer '+tok };
let pageData = null;
let _lastPowerPool = 0;
let snapEmpPool = null;
let snapBoilerPool = null;
let snapPowerPool = null;
let snapRepairPool = null;
let snapRdPool = null;
let boilerInfo = null;
// ── Load snapshot list ────────────────────────────────────────────────────────
(async function loadSnapshotList() {
try {
const r = await fetch('/api/costing/snapshots', { headers: hdrs }).then(r => r.json());
if (!r.success || !r.data.length) return;
smsState.snap.options = r.data.map(s => {
const dt = new Date(s.saved_at).toLocaleDateString('en-IN');
return { value: String(s.id), label: `${s.snap_name} (${s.period_from}→${s.period_to}) ${dt}` };
});
smsRenderList('snap'); smsRenderChips('snap');
} catch(_) {}
})();
const EXCLUDE_EMP_CODES = new Set([
'5103030001','5103030002','5103030003','5103030004','5103030005',
'5103030006','5103030008','5103030010','5103030012','5103030013',
'5240040001','5240040002','5240040003','5240040004','5240040005',
'5240040006','5240040008','5240040009','5240040010','5240040012',
'5240040013','5240040015'
]);
const MED_FEST_CODES = new Set(['5190090026','5190090029']);
function calcEmpPoolFromSnapshot(accounts) {
let pool = 0;
accounts.forEach(r => {
if (MED_FEST_CODES.has(r.acct_code)) {
pool += (r.period_debit || 0) + (r.provision || 0);
return;
}
if (r.sheet === 'Employee' && !EXCLUDE_EMP_CODES.has(r.acct_code) &&
!(r.acct_name || '').toUpperCase().includes('S&M')) {
pool += (r.period_debit - r.period_credit) + (r.provision || 0);
}
});
return pool;
}
function calcBoilerFromSnapshot(accounts) {
const row = accounts.find(a => a.acct_code === '5110010010');
if (!row) return null;
return Number(row.period_debit||0) - Number(row.period_credit||0) + (row.provision!=null?Number(row.provision):0);
}
function calcPowerFromSnapshot(accounts) {
const rows = accounts.filter(a => a.acct_code==='5110010008' || a.acct_code==='5110010009');
if (!rows.length) return null;
return rows.reduce((sum, row) =>
sum + Number(row.period_debit||0) - Number(row.period_credit||0) + (row.provision!=null?Number(row.provision):0),
0);
}
function calcRepairFromSnapshot(accounts) {
const RM_ACCTS = new Set(['5190090010','5190090011','5190090012','5190090013','5190090028']);
const rows = accounts.filter(a => RM_ACCTS.has(a.acct_code));
if (!rows.length) return null;
return rows.reduce((sum, row) =>
sum + Number(row.period_debit||0) - Number(row.period_credit||0) + (row.provision!=null?Number(row.provision):0),
0);
}
function calcRdFromSnapshot(accounts) {
const RD_ACCTS = new Set(['5240040018','5240040019','5240040020','5240040021']);
const rows = accounts.filter(a => RD_ACCTS.has(a.acct_code));
if (!rows.length) return null;
return rows.reduce((sum, row) =>
sum + Number(row.period_debit||0) - Number(row.period_credit||0) + (row.provision!=null?Number(row.provision):0),
0);
}
async function onSnapSelect() {
const lbl = document.getElementById('snapEmpPoolLabel');
const wrap = document.getElementById('snapEmpPoolWrap');
const ids = smsState.snap.selected;
if (!ids.length) {
snapEmpPool = null; snapBoilerPool = null; snapPowerPool = null; snapRepairPool = null; snapRdPool = null;
if (wrap) wrap.style.display = 'none';
return;
}
if (lbl) lbl.textContent = 'Calculating…';
if (wrap) wrap.style.display = '';
try {
let totalEmp = 0, totalBoiler = 0, totalPower = 0, totalRepair = 0, totalRd = 0;
let boilerFound = false, powerFound = false, repairFound = false, rdFound = false;
for (const id of ids) {
const r = await fetch(`/api/costing/snapshot/${id}`, { headers: hdrs }).then(r => r.json());
if (!r.success) throw new Error(r.message);
const accounts = (r.data.accounts || []).map(a => ({
...a, provision: a.override_val != null ? Number(a.override_val) : null
}));
totalEmp += calcEmpPoolFromSnapshot(accounts);
const boiler = calcBoilerFromSnapshot(accounts);
if (boiler !== null) { totalBoiler += boiler; boilerFound = true; }
const power = calcPowerFromSnapshot(accounts);
if (power !== null) { totalPower += power; powerFound = true; }
const repair = calcRepairFromSnapshot(accounts);
if (repair !== null) { totalRepair += repair; repairFound = true; }
const rd = calcRdFromSnapshot(accounts);
if (rd !== null) { totalRd += rd; rdFound = true; }
}
snapEmpPool = totalEmp;
snapBoilerPool = boilerFound ? totalBoiler : null;
snapPowerPool = powerFound ? totalPower : null;
snapRepairPool = repairFound ? totalRepair : null;
snapRdPool = rdFound ? totalRd : null;
if (lbl) {
const fmtI = v => v.toLocaleString('en-IN',{minimumFractionDigits:2,maximumFractionDigits:2});
const extra = [
boilerFound ? `Boiler: ₹ ${fmtI(totalBoiler)}` : '',
powerFound ? `Power: ₹ ${fmtI(totalPower)}` : '',
repairFound ? `R&M: ₹ ${fmtI(totalRepair)}` : '',
rdFound ? `R&D: ₹ ${fmtI(totalRd)}` : '',
].filter(Boolean).join(' · ');
lbl.textContent = `Employee Pool: ₹ ${fmtI(totalEmp)}` + (extra ? ` · ${extra}` : '');
}
} catch(e) {
snapEmpPool = null; snapBoilerPool = null; snapPowerPool = null; snapRepairPool = null; snapRdPool = null;
if (lbl) lbl.textContent = 'Error: ' + e.message;
}
}
function toast(msg, type='ok') {
const el = document.createElement('div');
el.className = 'toast ' + type;
el.textContent = msg;
document.getElementById('toastC').appendChild(el);
setTimeout(() => el.remove(), 4000);
}
function setStatus(msg, state='') {
document.getElementById('statusMsg').textContent = msg;
const dot = document.getElementById('statusDot');
dot.className = 'status-dot' + (state ? ' ' + state : '');
}
function fmt(v) {
if (v === null || v === undefined || v === '') return '—';
const n = parseFloat(v);
if (isNaN(n)) return v;
return n.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function fmtQty(v) {
const n = parseFloat(v) || 0;
return n.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
// Default dates to current month
(function initDates() {
const now = new Date();
const y = now.getFullYear(), m = String(now.getMonth()+1).padStart(2,'0');
document.getElementById('fromDate').value = `${y}-${m}-01`;
const last = new Date(y, now.getMonth()+1, 0);
document.getElementById('toDate').value = `${y}-${m}-${String(last.getDate()).padStart(2,'0')}`;
})();
// ── Searchable multi-select engine ───────────────────────────────────────────
const smsState = {
sal: { options:[], selected:[] },
snap: { options:[], selected:[] },
};
function smsRenderList(key) {
const q = (document.getElementById(`sms-${key}-q`)?.value || '').toLowerCase();
const list = document.getElementById(`sms-${key}-list`);
const opts = smsState[key].options.filter(o => !q || o.label.toLowerCase().includes(q));
if (!opts.length) { list.innerHTML=`<div class="sms-empty">No results</div>`; return; }
list.innerHTML = opts.map(o => {
const sel = smsState[key].selected.includes(o.value);
return `<label class="sms-opt${sel?' selected':''}">
<input type="checkbox" ${sel?'checked':''} onchange="smsToggle('${key}','${o.value.replace(/'/g,"\\'")}',this.checked)">
<span>${o.label}</span>
</label>`;
}).join('');
}
function smsRenderChips(key) {
const el = document.getElementById(`sms-${key}-chips`);
if (!el) return;
const isTeal = key === 'sal';
el.innerHTML = smsState[key].selected.map(v => {
const opt = smsState[key].options.find(o => o.value === v);
return `<span class="sms-chip ${isTeal?'teal':'purple'}">${opt?opt.label:v}
<button onclick="smsToggle('${key}','${v.replace(/'/g,"\\'")}',false)">×</button>
</span>`;
}).join('') || `<span style="font-size:11px;color:var(--text3)">None selected</span>`;
}
function smsToggle(key, value, checked) {
const s = smsState[key].selected;
if (checked && !s.includes(value)) s.push(value);
if (!checked) { const i=s.indexOf(value); if(i>=0) s.splice(i,1); }
smsRenderChips(key);
smsRenderList(key);
if (key === 'snap') onSnapSelect();
}
window.smsToggle = smsToggle;
window.smsFilter = key => smsRenderList(key);
// ── Configure Allocation Panel ────────────────────────────────────────────────
function toggleCfg() {
const hdr = document.getElementById('cfgHdr');
const body = document.getElementById('cfgBody');
hdr.classList.toggle('open');
body.classList.toggle('open');
}
function syncCfg(key) {
const bbEl = document.getElementById(`cfg-${key}-bb`);
const capdEl = document.getElementById(`cfg-${key}-capd-disp`);
const val = bbEl.value.trim();
if (val === '' || isNaN(parseFloat(val))) {
capdEl.textContent = '—';
capdEl.style.color = 'var(--text3)';
} else {
const bb = Math.min(100, Math.max(0, parseFloat(val)));
capdEl.textContent = (100 - bb).toFixed(1) + '%';
capdEl.style.color = 'var(--text)';
}
}
function resetCfg(key) {
const bbEl = document.getElementById(`cfg-${key}-bb`);
const capdEl = document.getElementById(`cfg-${key}-capd-disp`);
if (bbEl) { bbEl.value = ''; }
if (capdEl) { capdEl.textContent = '—'; capdEl.style.color = 'var(--text3)'; }
const pwrEl = document.getElementById(`cfg-${key}-power-pct`);
const absEl = document.getElementById(`cfg-${key}-power-abs`);
if (pwrEl) pwrEl.value = '';
if (absEl) absEl.textContent = '—';
syncPowerPct();
}
function resetAllCfg() {
['emp','qa','qc','boiler','power','repair','rd'].forEach(k => resetCfg(k));
if (pageData) loadData();
}
window.resetAllCfg = resetAllCfg;
// Live preview: show power absorbed per cost head as user types
function syncPowerPct() {
const powerPool = _lastPowerPool || 0;
const distKeys = ['emp','qa','qc','boiler','repair','rd'];
let totalPct = 0;
distKeys.forEach(k => {
const v = parseFloat(document.getElementById(`cfg-${k}-power-pct`)?.value || 0) || 0;
totalPct += v;
const amt = powerPool * v / 100;
const el = document.getElementById(`cfg-${k}-power-abs`);
if (el) el.textContent = v > 0
? '₹' + amt.toLocaleString('en-IN', {minimumFractionDigits:2,maximumFractionDigits:2})
: '—';
});
const remaining = Math.max(0, powerPool * (1 - totalPct/100));
const remEl = document.getElementById('cfg-power-power-abs');
const remEl2 = document.getElementById('cfg-power-remaining');
if (remEl) remEl.textContent = '₹' + remaining.toLocaleString('en-IN',{minimumFractionDigits:2,maximumFractionDigits:2});
if (remEl2) remEl2.style.color = totalPct > 100 ? '#dc2626' : '#0ea5e9';
}
window.syncPowerPct = syncPowerPct;
function getCfgParams() {
const keys = ['emp','qa','qc','boiler','power','repair','rd'];
const apiKeys = { emp:'bbEmpPct', qa:'bbQaPct', qc:'bbQcPct', boiler:'bbBoilerPct', power:'bbPowerPct', repair:'bbRepairPct', rd:'bbRdPct' };
const pwrApiKeys = { emp:'empPowerPct', qa:'qaPowerPct', qc:'qcPowerPct', boiler:'boilerPowerPct', repair:'repairPowerPct', rd:'rdPowerPct' };
const parts = [];
keys.forEach(k => {
const v = document.getElementById(`cfg-${k}-bb`).value.trim();
if (v !== '' && !isNaN(parseFloat(v))) {
const clamped = Math.min(100, Math.max(0, parseFloat(v)));
parts.push(`${apiKeys[k]}=${clamped}`);
}
// Power distribution %
if (k !== 'power') {
const pv = document.getElementById(`cfg-${k}-power-pct`)?.value.trim() || '';
if (pv !== '' && !isNaN(parseFloat(pv))) {
const clamped = Math.min(100, Math.max(0, parseFloat(pv)));
if (clamped > 0) parts.push(`${pwrApiKeys[k]}=${clamped}`);
}
}
});
return parts.join('&');
}
function updateCfgAfterLoad(allocation) {
if (!allocation) return;
const fmtC = v => typeof v === 'number'
? '₹' + v.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
: '—';
const autoBasis = { emp: 'salary ratio', qa: 'RM ratio', qc: 'RM ratio', boiler: 'RM ratio', power: 'RM ratio', repair: 'RM ratio', rd: 'RM ratio' };
const map = {
emp: allocation.emp, qa: allocation.qa, qc: allocation.qc,
boiler: allocation.boiler, power: allocation.power, repair: allocation.repair, rd: allocation.rd,
};
// Track total power pool for live preview
const pw = map.power;
if (pw) _lastPowerPool = (pw.bbPool || 0) + (pw.capdPool || 0) + (pw.powerAbsorbed || 0);
let summary = [];
['emp','qa','qc','boiler','power','repair','rd'].forEach(k => {
const d = map[k];
if (!d) return;
const bbPool = typeof d.bbPool === 'number' ? d.bbPool : 0;
const capdPool = typeof d.capdPool === 'number' ? d.capdPool : 0;
const total = bbPool + capdPool;
const bbPct = typeof d.bbPct === 'number' ? d.bbPct.toFixed(1) : '?';
const capdPct = (100 - parseFloat(bbPct)).toFixed(1);
const poolEl = document.getElementById(`cfg-${k}-pool`);
const bbAmtEl = document.getElementById(`cfg-${k}-bb-amt`);
const capdAmtEl = document.getElementById(`cfg-${k}-capd-amt`);
const autoEl = document.getElementById(`cfg-${k}-auto`);
const absEl = document.getElementById(`cfg-${k}-power-abs`);
if (poolEl) poolEl.textContent = fmtC(total);
if (bbAmtEl) bbAmtEl.textContent = fmtC(bbPool);
if (capdAmtEl) capdAmtEl.textContent = fmtC(capdPool);
if (absEl && typeof d.powerAbsorbed === 'number' && d.powerAbsorbed > 0)
absEl.textContent = fmtC(d.powerAbsorbed);
if (autoEl) {
const autoPct = typeof d.autoPct === 'number' ? d.autoPct.toFixed(1) : '?';
autoEl.textContent = `${autoBasis[k]} → ${autoPct}% BB`;
}
syncCfg(k);
summary.push(`<b>${k.toUpperCase()}</b>: BB ${bbPct}% / CAPD ${capdPct}%`);
});
syncPowerPct();
const res = document.getElementById('cfgResult');
if (res) {
res.innerHTML = 'Applied: ' + summary.join('&nbsp;&nbsp;·&nbsp;&nbsp;');
res.classList.add('show');
}
}
window.toggleCfg = toggleCfg;
window.syncCfg = syncCfg;
window.resetCfg = resetCfg;
function clearAll() {
smsState.sal.selected = [];
smsState.snap.selected = [];
smsRenderChips('sal'); smsRenderList('sal');
smsRenderChips('snap'); smsRenderList('snap');
snapEmpPool = null; snapBoilerPool = null; snapPowerPool = null; snapRepairPool = null; snapRdPool = null;
const lbl = document.getElementById('snapEmpPoolLabel');
if (lbl) { lbl.style.display='none'; lbl.textContent=''; }
}
window.clearAll = clearAll;
// Load salary periods
(async function loadPeriods() {
try {
const r = await fetch('/api/costsheet/salary-periods', { headers: hdrs }).then(r => r.json());
if (r.success && r.data.length) {
smsState.sal.options = r.data.map(p => ({ value: p, label: p }));
smsRenderList('sal'); smsRenderChips('sal');
}
} catch(_) {}
})();
async function loadData() {
const from = document.getElementById('fromDate').value;
const to = document.getElementById('toDate').value;
const sp = smsState.sal.selected.join(',');
if (!from || !to) { toast('Please select a date range.','err'); return; }
const btn = document.getElementById('btnLoad');
btn.disabled = true;
setStatus('Loading cost sheet…', 'loading');
document.getElementById('csContent').innerHTML = '';
document.getElementById('btnExport').disabled = true;
try {
const cfgParams = getCfgParams();
const url = `/api/costsheet/data?from=${from}&to=${to}${sp?'&salaryPeriod='+sp:''}${snapEmpPool!=null?'&overrideEmpPool='+snapEmpPool:''}${snapBoilerPool!=null?'&overrideBoilerLedger='+snapBoilerPool:''}${snapPowerPool!=null?'&overridePowerLedger='+snapPowerPool:''}${snapRepairPool!=null?'&overrideRepairLedger='+snapRepairPool:''}${snapRdPool!=null?'&overrideRdLedger='+snapRdPool:''}${cfgParams?'&'+cfgParams:''}`;
const r = await fetch(url, { headers: hdrs }).then(r => r.json());
if (!r.success) throw new Error(r.message);
pageData = r.data;
boilerInfo = r.boilerInfo || null;
updateCfgAfterLoad(r.allocation);
// Show boiler breakdown
const bar = document.getElementById('boilerInfoBar');
if (bar && boilerInfo) {
bar.style.display = '';
bar.innerHTML = `Boiler Ledger (5110010010): <b>₹${fmt(boilerInfo.ledger)}</b>&nbsp;&nbsp;+&nbsp;&nbsp;Boiler Salary: <b>₹${fmt(boilerInfo.salary)}</b>&nbsp;&nbsp;=&nbsp;&nbsp;Total Boiler: <b style="color:#047e7e">₹${fmt(boilerInfo.total)}</b>`;
}
renderSheet(pageData, from, to);
document.getElementById('btnExport').disabled = false;
document.getElementById('btnPrint').disabled = false;
const bbCount = pageData.bloodbag.length, capdCount = pageData.capd.length;
setStatus(`Loaded: ${bbCount} Blood Bag items · ${capdCount} CAPD items (${from} to ${to})`, 'ok');
} catch(e) {
setStatus(e.message, 'err');
toast(e.message, 'err');
} finally {
btn.disabled = false;
}
}
function sectionTotal(rows) {
return rows.reduce((t, r) => ({
productionQty: t.productionQty + r.productionQty,
salesQty: t.salesQty + r.salesQty,
rmConsumption: t.rmConsumption + r.rmConsumption,
employeeCost: t.employeeCost + r.employeeCost,
qaCost: t.qaCost + (r.qaCost || 0),
qcCost: t.qcCost + (r.qcCost || 0),
boilerCost: t.boilerCost + (r.boilerCost || 0),
powerCost: t.powerCost + (r.powerCost || 0),
repairCost: t.repairCost + (r.repairCost || 0),
rdCost: t.rdCost + (r.rdCost || 0),
}), { productionQty:0, salesQty:0, rmConsumption:0, employeeCost:0, qaCost:0, qcCost:0, boilerCost:0, powerCost:0, repairCost:0, rdCost:0 });
}
function buildTableRows(rows) {
if (!rows.length) return `<tr><td colspan="12" class="empty">No data for this period</td></tr>`;
return rows.map(r => `
<tr>
<td class="td-code">${r.itemCode}</td>
<td class="td-desc">${r.itemDescription || '—'}</td>
<td class="num">${fmtQty(r.productionQty)}</td>
<td class="num">${fmtQty(r.salesQty)}</td>
<td class="num">${fmt(r.rmConsumption)}</td>
<td class="num">${fmt(r.employeeCost)}</td>
<td class="num">${fmt(r.qaCost)}</td>
<td class="num">${fmt(r.qcCost)}</td>
<td class="num">${fmt(r.boilerCost)}</td>
<td class="num">${fmt(r.powerCost)}</td>
<td class="num">${fmt(r.repairCost)}</td>
<td class="num">${fmt(r.rdCost)}</td>
</tr>`).join('');
}
function buildTotalRow(label, t) {
return `<tr class="total-row">
<td colspan="2" style="letter-spacing:.5px">${label}</td>
<td class="num">${fmtQty(t.productionQty)}</td>
<td class="num">${fmtQty(t.salesQty)}</td>
<td class="num">${fmt(t.rmConsumption)}</td>
<td class="num">${fmt(t.employeeCost)}</td>
<td class="num">${fmt(t.qaCost)}</td>
<td class="num">${fmt(t.qcCost)}</td>
<td class="num">${fmt(t.boilerCost)}</td>
<td class="num">${fmt(t.powerCost)}</td>
<td class="num">${fmt(t.repairCost)}</td>
<td class="num">${fmt(t.rdCost)}</td>
</tr>`;
}
function renderSheet(data, from, to) {
const bbTot = sectionTotal(data.bloodbag);
const capdTot = sectionTotal(data.capd);
const grand = {
productionQty: bbTot.productionQty + capdTot.productionQty,
salesQty: bbTot.salesQty + capdTot.salesQty,
rmConsumption: bbTot.rmConsumption + capdTot.rmConsumption,
employeeCost: bbTot.employeeCost + capdTot.employeeCost,
qaCost: bbTot.qaCost + capdTot.qaCost,
qcCost: bbTot.qcCost + capdTot.qcCost,
boilerCost: bbTot.boilerCost + capdTot.boilerCost,
powerCost: bbTot.powerCost + capdTot.powerCost,
repairCost: bbTot.repairCost + capdTot.repairCost,
rdCost: bbTot.rdCost + capdTot.rdCost,
};
const header = `
<thead><tr>
<th style="min-width:100px">Item Code</th>
<th style="min-width:220px">Item Description</th>
<th class="num" style="min-width:130px">Production (Units)</th>
<th class="num" style="min-width:110px">Sales Qty</th>
<th class="num" style="min-width:140px">RM Consumption</th>
<th class="num" style="min-width:130px">Employee Cost</th>
<th class="num" style="min-width:110px">QA Cost</th>
<th class="num" style="min-width:110px">QC Cost</th>
<th class="num" style="min-width:120px">Boiler Cost</th>
<th class="num" style="min-width:120px">Power Cost</th>
<th class="num" style="min-width:120px">R&amp;M Cost</th>
<th class="num" style="min-width:120px">R&amp;D Cost</th>
</tr></thead>`;
document.getElementById('csContent').innerHTML = `
<!-- BLOOD BAG Section -->
<div class="cs-wrap">
<div class="cs-section-hd">
<span class="cs-section-tag tag-bb">Blood Bag</span>
<span class="cs-section-label">FG BLOOD BAG &nbsp;·&nbsp; ${from} to ${to}</span>
</div>
<div class="cs-tbl-scroll">
<table class="cs-tbl">
${header}
<tbody>
${buildTableRows(data.bloodbag)}
${buildTotalRow('BLOOD BAG TOTAL', bbTot)}
</tbody>
</table>
</div>
</div>
<!-- CAPD Section -->
<div class="cs-wrap">
<div class="cs-section-hd">
<span class="cs-section-tag tag-capd">CAPD</span>
<span class="cs-section-label">FG CAPD / FG CAPD ACCESSORIES &nbsp;·&nbsp; ${from} to ${to}</span>
</div>
<div class="cs-tbl-scroll">
<table class="cs-tbl">
${header}
<tbody>
${buildTableRows(data.capd)}
${buildTotalRow('CAPD TOTAL', capdTot)}
</tbody>
</table>
</div>
</div>
<!-- Grand Total -->
<div class="cs-wrap">
<div class="cs-tbl-scroll">
<table class="cs-tbl">
<tbody>
<tr class="grand-total">
<td colspan="2" style="font-size:13px;letter-spacing:.3px">TOTAL P&amp;L</td>
<td class="num">${fmtQty(grand.productionQty)}</td>
<td class="num">${fmtQty(grand.salesQty)}</td>
<td class="num">${fmt(grand.rmConsumption)}</td>
<td class="num">${fmt(grand.employeeCost)}</td>
<td class="num">${fmt(grand.qaCost)}</td>
<td class="num">${fmt(grand.qcCost)}</td>
<td class="num">${fmt(grand.boilerCost)}</td>
<td class="num">${fmt(grand.powerCost)}</td>
<td class="num">${fmt(grand.repairCost)}</td>
<td class="num">${fmt(grand.rdCost)}</td>
</tr>
</tbody>
</table>
</div>
</div>`;
}
// ── Excel Export ─────────────────────────────────────────────────────────────
function exportXLSX() {
if (!pageData) return;
const from = document.getElementById('fromDate').value;
const to = document.getElementById('toDate').value;
const cols = ['Item Code','Item Description','Production (Units)','Sales Qty','RM Consumption','Employee Cost','QA Cost','QC Cost','Boiler Cost','Power Cost','R&M Cost','R&D Cost'];
const toRow = (r, isTotal=false) => [
isTotal ? r.label : r.itemCode,
isTotal ? '' : (r.itemDescription||''),
r.productionQty, r.salesQty, r.rmConsumption, r.employeeCost,
r.qaCost||0, r.qcCost||0, r.boilerCost||0, r.powerCost||0, r.repairCost||0, r.rdCost||0,
];
const bbTot = sectionTotal(pageData.bloodbag);
const capdTot = sectionTotal(pageData.capd);
const grand = {
label:'TOTAL P&L',
productionQty: bbTot.productionQty + capdTot.productionQty,
salesQty: bbTot.salesQty + capdTot.salesQty,
rmConsumption: bbTot.rmConsumption + capdTot.rmConsumption,
employeeCost: bbTot.employeeCost + capdTot.employeeCost,
qaCost: bbTot.qaCost + capdTot.qaCost,
qcCost: bbTot.qcCost + capdTot.qcCost,
boilerCost: bbTot.boilerCost + capdTot.boilerCost,
powerCost: bbTot.powerCost + capdTot.powerCost,
repairCost: bbTot.repairCost + capdTot.repairCost,
rdCost: bbTot.rdCost + capdTot.rdCost,
};
const empty12 = new Array(12).fill('');
const rows = [
cols,
['BLOOD BAG',...empty12.slice(1)],
...pageData.bloodbag.map(r => toRow(r)),
toRow({...bbTot, label:'BLOOD BAG TOTAL'}, true),
[],
['CAPD',...empty12.slice(1)],
...pageData.capd.map(r => toRow(r)),
toRow({...capdTot, label:'CAPD TOTAL'}, true),
[],
toRow(grand, true),
];
const ws = XLSX.utils.aoa_to_sheet(rows);
ws['!cols'] = [14,36,18,14,18,16,14,14,14,14,14,14].map(w => ({wch:w}));
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, `Cost Sheet`);
XLSX.writeFile(wb, `Cost_Sheet_${from}_${to}.xlsx`);
toast('Excel exported successfully');
}
function printCostSheet() {
const el = document.getElementById('csContent');
if (!el || !el.innerHTML.trim()) { toast('Load data first','err'); return; }
const from = document.getElementById('fromDate').value;
const to = document.getElementById('toDate').value;
const w = window.open('','_blank','width=1200,height=800');
w.document.write(`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Cost Sheet</title>
<style>
@page{size:A4 landscape;margin:6mm}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:Arial,Helvetica,sans-serif;font-size:7.5px;color:#0f172a;padding:2px}
h2{font-size:9px;font-weight:700;margin-bottom:4px;color:#1e3264;padding-bottom:2px;border-bottom:1px solid #1e3264}
.cs-wrap{border:1px solid #e2e8f0;margin-bottom:6px}
.cs-section-hd{display:flex;align-items:center;gap:5px;padding:3px 6px;background:#f1f5f9;border-bottom:1px solid #e2e8f0;-webkit-print-color-adjust:exact;print-color-adjust:exact}
.cs-section-tag{padding:1px 5px;border-radius:8px;font-size:7px;font-weight:700;text-transform:uppercase}
.tag-bb{background:rgba(37,99,235,.1);color:#2563eb;-webkit-print-color-adjust:exact;print-color-adjust:exact}
.tag-capd{background:rgba(124,58,237,.1);color:#7c3aed;-webkit-print-color-adjust:exact;print-color-adjust:exact}
.cs-section-label{font-size:8px;font-weight:700;color:#334155}
.cs-tbl-scroll{overflow:visible!important;max-height:none!important}
table{width:100%;border-collapse:collapse}
th,td{font-size:7.5px!important;padding:2px 4px!important;line-height:1.3!important;white-space:nowrap}
th{text-align:left;background:#f1f5f9!important;color:#475569;font-weight:700;text-transform:uppercase;letter-spacing:.3px;border-bottom:1px solid #e2e8f0;-webkit-print-color-adjust:exact;print-color-adjust:exact}
th.num,td.num{text-align:right}
td{border-bottom:1px solid #f8f9fb;color:#1e293b}
tr.total-row td{background:#f1f5f9!important;font-weight:700;border-top:1.5px solid #e2e8f0;-webkit-print-color-adjust:exact;print-color-adjust:exact}
tr.grand-total td{background:#1e3264!important;color:#fff!important;font-weight:700;-webkit-print-color-adjust:exact;print-color-adjust:exact}
</style></head><body>
<h2>Cost Sheet &mdash; ${from} to ${to}</h2>
${el.innerHTML}
<script>window.onload=()=>{window.print();}<\/script>
</body></html>`);
w.document.close();
}
</script>
<script src="/sidebar.js?v=25"></script>
</body>
</html>