767 lines
38 KiB
HTML
767 lines
38 KiB
HTML
<!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>GSTR-2 Purchase Register | MITRA ERP Portal</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&display=swap" rel="stylesheet">
|
|
<script src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js"></script>
|
|
<style>
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
body{font-family:'Space Grotesk',sans-serif;background:#f0f2f5;color:#1a1a2e;min-height:100vh}
|
|
.pg{padding:20px 24px}
|
|
.hdr{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:18px}
|
|
.hdr h1{font-size:20px;font-weight:700;color:#1a1a2e}
|
|
.hdr h1 span{color:#7c3aed}
|
|
.btn{height:36px;padding:0 16px;border-radius:8px;font-size:12px;font-weight:700;font-family:inherit;border:none;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
|
|
.btn-primary{background:#7c3aed;color:#fff}.btn-primary:hover{background:#6d28d9}
|
|
.btn-primary:disabled{background:#b0bec5;cursor:not-allowed;opacity:.7}
|
|
.btn-outline{background:#fff;color:#7c3aed;border:1.5px solid #7c3aed}.btn-outline:hover{background:#f5f3ff}
|
|
.btn-tcs{background:#1d4ed8;color:#fff}.btn-tcs:hover{background:#1e40af}
|
|
.btn-tcs:disabled{background:#b0bec5;cursor:not-allowed;opacity:.7}
|
|
.card{background:#fff;border-radius:14px;box-shadow:0 1px 8px rgba(0,0,0,.08);overflow:hidden}
|
|
.filter-bar{display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap;padding:16px 20px;border-bottom:1px solid #e8eaed;background:#fff}
|
|
.fg{display:flex;flex-direction:column;gap:5px}
|
|
.fg label{font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.6px}
|
|
.fg input,.fg select{height:36px;border:1.5px solid #dde1e7;border-radius:8px;padding:0 12px;font-size:13px;font-family:inherit;color:#1a1a2e;background:#fff;outline:none;transition:border-color .15s}
|
|
.fg input:focus,.fg select:focus{border-color:#7c3aed;box-shadow:0 0 0 3px rgba(124,58,237,.08)}
|
|
.summary-chips{display:flex;gap:8px;flex-wrap:wrap;padding:12px 20px;border-bottom:1px solid #e8eaed;background:#f8fafb}
|
|
.chip{background:#fff;border:1.5px solid #ddd6fe;border-radius:10px;padding:6px 14px;font-size:11px;font-weight:600;color:#7c3aed;min-width:80px}
|
|
.chip span{font-size:15px;font-weight:700;display:block;color:#1a1a2e;margin-top:1px}
|
|
.tabs{display:flex;justify-content:space-evenly;border-bottom:2px solid #e8eaed;background:#fff}
|
|
.tab{flex:1;text-align:center;padding:14px 10px;font-size:12px;font-weight:700;color:#6b7280;cursor:pointer;border-bottom:3px solid transparent;margin-bottom:-2px;white-space:nowrap;transition:all .15s;letter-spacing:.2px}
|
|
.tab:hover{color:#7c3aed;background:#faf5ff}
|
|
.tab.active{color:#7c3aed;border-bottom-color:#7c3aed;background:#faf5ff}
|
|
.tab-badge{display:inline-block;background:#ede9fe;color:#7c3aed;border-radius:10px;padding:1px 7px;font-size:10px;font-weight:700;margin-left:5px}
|
|
.tab.active .tab-badge{background:#7c3aed;color:#fff}
|
|
.search-bar{display:flex;align-items:center;gap:10px;padding:10px 20px;border-bottom:1px solid #e8eaed;background:#fafbfc}
|
|
.search-bar input{flex:1;height:34px;border:1.5px solid #dde1e7;border-radius:8px;padding:0 12px 0 34px;font-size:12px;font-family:inherit;color:#1a1a2e;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") no-repeat 10px center;outline:none;transition:border-color .15s}
|
|
.search-bar input:focus{border-color:#7c3aed;box-shadow:0 0 0 3px rgba(124,58,237,.08)}
|
|
.srch-count{font-size:11px;color:#9ca3af;white-space:nowrap;flex-shrink:0;font-weight:600}
|
|
.tab-content{display:none}
|
|
.tab-content.active{display:block}
|
|
.tbl-wrap{overflow-x:auto;overflow-y:auto;max-height:54vh}
|
|
.tbl-wrap::-webkit-scrollbar{height:7px;width:7px}
|
|
.tbl-wrap::-webkit-scrollbar-track{background:#f1f3f5}
|
|
.tbl-wrap::-webkit-scrollbar-thumb{background:#c1c7ce;border-radius:4px}
|
|
.tbl-wrap::-webkit-scrollbar-thumb:hover{background:#9ca3af}
|
|
table{border-collapse:collapse;min-width:100%}
|
|
thead th{background:#f5f3ff;color:#4b5563;font-weight:700;padding:10px 12px;text-align:left;white-space:nowrap;position:sticky;top:0;z-index:2;border-bottom:2px solid #ddd6fe;font-size:10px;text-transform:uppercase;letter-spacing:.5px}
|
|
tbody tr{border-bottom:1px solid #f0f2f4;transition:background .1s;cursor:pointer}
|
|
tbody tr:hover{background:#f5f3ff}
|
|
tbody tr.selected{background:#ede9fe}
|
|
tbody td{padding:9px 12px;white-space:nowrap;color:#374151;font-size:12px}
|
|
tbody td.num{text-align:right;font-variant-numeric:tabular-nums;font-size:12px}
|
|
tbody td.trunc{max-width:160px;overflow:hidden;text-overflow:ellipsis}
|
|
tbody td.trunc-sm{max-width:110px;overflow:hidden;text-overflow:ellipsis}
|
|
.empty{text-align:center;padding:56px 20px;color:#9ca3af;font-size:13px}
|
|
.status-bar{display:flex;align-items:center;gap:10px;padding:10px 20px;background:#f8fafb;border-top:1px solid #e8eaed;font-size:12px;color:#6b7280;border-radius:0 0 14px 14px}
|
|
.status-dot{width:8px;height:8px;border-radius:50%;background:#d1d5db;flex-shrink:0}
|
|
.status-dot.ok{background:#10b981}.status-dot.err{background:#ef4444}
|
|
.status-dot.loading{background:#f59e0b;animation:pulse 1s infinite}
|
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
|
.badge-b2b{background:#dbeafe;color:#1d4ed8}
|
|
.badge-rcm{background:#fee2e2;color:#991b1b}
|
|
.badge-impg{background:#d1fae5;color:#065f46}
|
|
.badge{padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700}
|
|
.itc-capital{background:#fef3c7;color:#92400e;padding:2px 7px;border-radius:8px;font-size:10px;font-weight:600}
|
|
.itc-inputs{background:#d1fae5;color:#065f46;padding:2px 7px;border-radius:8px;font-size:10px;font-weight:600}
|
|
.itc-services{background:#dbeafe;color:#1d4ed8;padding:2px 7px;border-radius:8px;font-size:10px;font-weight:600}
|
|
.itc-ineligible{background:#fee2e2;color:#991b1b;padding:2px 7px;border-radius:8px;font-size:10px;font-weight:600}
|
|
.itc-other{background:#f3f4f6;color:#374151;padding:2px 7px;border-radius:8px;font-size:10px;font-weight:600}
|
|
.detail-overlay{position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:500;display:none;backdrop-filter:blur(2px)}
|
|
.detail-overlay.show{display:block}
|
|
.detail-panel{position:fixed;top:0;right:-440px;width:440px;height:100vh;background:#fff;z-index:501;box-shadow:-4px 0 24px rgba(0,0,0,.12);display:flex;flex-direction:column;transition:right .28s cubic-bezier(.4,0,.2,1)}
|
|
.detail-panel.show{right:0}
|
|
.detail-header{display:flex;align-items:center;justify-content:space-between;padding:18px 20px;border-bottom:1px solid #e8eaed;background:#f8fafb;flex-shrink:0}
|
|
.detail-header h3{font-size:14px;font-weight:700;color:#1a1a2e}
|
|
.detail-header .row-num{font-size:11px;color:#9ca3af;margin-top:2px}
|
|
.detail-close{width:32px;height:32px;border:none;background:#edf2f7;border-radius:8px;cursor:pointer;font-size:18px;color:#6b7280;display:flex;align-items:center;justify-content:center;transition:all .15s}
|
|
.detail-close:hover{background:#e2e8f0;color:#1a1a2e}
|
|
.detail-body{flex:1;overflow-y:auto;padding:16px 20px}
|
|
.detail-body::-webkit-scrollbar{width:4px}.detail-body::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:2px}
|
|
.detail-row{display:flex;gap:12px;padding:9px 0;border-bottom:1px solid #f3f4f6;align-items:center}
|
|
.detail-row:last-child{border-bottom:none}
|
|
.detail-key{font-size:11px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.4px;width:130px;flex-shrink:0;padding-top:1px}
|
|
.detail-val{font-size:13px;color:#1a1a2e;font-weight:500;word-break:break-all;flex:1}
|
|
.detail-val.num{font-variant-numeric:tabular-nums}
|
|
.detail-edit-input{flex:1;height:32px;border:1.5px solid #dde1e7;border-radius:6px;padding:0 10px;font-size:12px;font-family:inherit;color:#1a1a2e;outline:none;width:100%;box-sizing:border-box}
|
|
.detail-edit-input:focus{border-color:#7c3aed;box-shadow:0 0 0 3px rgba(124,58,237,.08)}
|
|
.detail-edit-input.num{text-align:right;font-variant-numeric:tabular-nums}
|
|
.detail-footer{display:flex;gap:8px;padding:14px 20px;border-top:1px solid #e8eaed;background:#f8fafb;flex-shrink:0}
|
|
.detail-footer .btn{flex:1;justify-content:center;height:34px}
|
|
.badge-edited{display:inline-block;margin-left:8px;font-size:10px;font-weight:700;color:#b45309;background:#fef3c7;border-radius:5px;padding:2px 7px;letter-spacing:.3px}
|
|
.badge-added{display:inline-block;margin-left:8px;font-size:10px;font-weight:700;color:#15803d;background:#dcfce7;border-radius:5px;padding:2px 7px;letter-spacing:.3px}
|
|
.toast{position:fixed;bottom:24px;right:24px;z-index:9999;padding:12px 20px;border-radius:10px;font-size:13px;font-weight:600;color:#fff;box-shadow:0 4px 20px rgba(0,0,0,.15);transform:translateY(20px);opacity:0;transition:all .3s;pointer-events:none;max-width:360px}
|
|
.toast.show{transform:translateY(0);opacity:1}
|
|
.toast.success{background:#10b981}.toast.error{background:#ef4444}.toast.info{background:#3b82f6}
|
|
</style>
|
|
<link rel="stylesheet" href="/theme.css?v=3"/>
|
|
</head>
|
|
<body>
|
|
<script src="/sidebar.js?v=25"></script>
|
|
|
|
<div class="pg">
|
|
<div class="hdr">
|
|
<h1><span>GSTR-2</span> Purchase Register</h1>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
<button class="btn btn-outline" id="btnDownload" disabled>Download Excel</button>
|
|
<input type="file" id="importFileInput" accept=".xlsx,.xls" style="display:none">
|
|
<button class="btn btn-outline" id="btnImportExcel" title="Import Excel into active tab (temporary)">Import Excel</button>
|
|
<select id="uploadSection" style="height:36px;border:1.5px solid #dde1e7;border-radius:8px;padding:0 12px;font-size:13px;font-family:inherit;color:#1a1a2e;background:#fff;outline:none;min-width:150px">
|
|
<option value="all">All Sections</option>
|
|
<option value="b2b">B2B Only</option>
|
|
<option value="impg">IMPG Only</option>
|
|
<option value="b2bur">B2BUR Only</option>
|
|
<option value="hsn">HSN Only</option>
|
|
<option value="cdn">CDN Only</option>
|
|
</select>
|
|
<button class="btn btn-tcs" id="btnUpload" disabled>Upload to TCS</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="filter-bar">
|
|
<div class="fg">
|
|
<label>From Date</label>
|
|
<input type="date" id="fromDate">
|
|
</div>
|
|
<div class="fg">
|
|
<label>To Date</label>
|
|
<input type="date" id="toDate">
|
|
</div>
|
|
<div class="fg">
|
|
<label>TCS Period (MMYYYY)</label>
|
|
<input type="text" id="tcsPeriod" placeholder="e.g. 052025" maxlength="6" style="width:140px">
|
|
</div>
|
|
<button class="btn btn-primary" id="btnLoad">Load Data</button>
|
|
</div>
|
|
|
|
<div class="summary-chips" id="summaryChips" style="display:none">
|
|
<div class="chip">B2B Rows<span id="cntB2b">—</span></div>
|
|
<div class="chip">IMPG Rows<span id="cntImpg">—</span></div>
|
|
<div class="chip">B2BUR Rows<span id="cntB2bur">—</span></div>
|
|
<div class="chip">HSN Lines<span id="cntHsn">—</span></div>
|
|
<div class="chip">CDN Rows<span id="cntCdn">—</span></div>
|
|
</div>
|
|
|
|
<div class="tabs" id="tabBar">
|
|
<div class="tab active" data-tab="b2b">B2B <span class="tab-badge" id="tb2b">0</span></div>
|
|
<div class="tab" data-tab="impg">IMPG <span class="tab-badge" id="timpg">0</span></div>
|
|
<div class="tab" data-tab="b2bur">B2BUR <span class="tab-badge" id="tb2bur">0</span></div>
|
|
<div class="tab" data-tab="hsn">HSN Summary <span class="tab-badge" id="thsn">0</span></div>
|
|
<div class="tab" data-tab="cdn">CDN <span class="tab-badge" id="tcdn">0</span></div>
|
|
</div>
|
|
|
|
<div class="search-bar">
|
|
<input type="text" id="searchInput" placeholder="Search across all columns in active tab…">
|
|
<span class="srch-count" id="srchCount"></span>
|
|
<button class="btn btn-outline" id="btnAddRow" style="height:34px" disabled>+ Add Row</button>
|
|
</div>
|
|
|
|
<div class="tab-content active" id="pane-b2b">
|
|
<div class="tbl-outer"><div class="tbl-wrap"><div class="empty" id="empty-b2b">Load data to view B2B purchases</div><table id="tbl-b2b" style="display:none"><thead></thead><tbody></tbody></table></div></div>
|
|
</div>
|
|
<div class="tab-content" id="pane-impg">
|
|
<div class="tbl-outer"><div class="tbl-wrap"><div class="empty" id="empty-impg">Load data to view Import of Goods</div><table id="tbl-impg" style="display:none"><thead></thead><tbody></tbody></table></div></div>
|
|
</div>
|
|
<div class="tab-content" id="pane-b2bur">
|
|
<div class="tbl-outer"><div class="tbl-wrap"><div class="empty" id="empty-b2bur">Load data to view B2BUR purchases</div><table id="tbl-b2bur" style="display:none"><thead></thead><tbody></tbody></table></div></div>
|
|
</div>
|
|
<div class="tab-content" id="pane-hsn">
|
|
<div class="tbl-outer"><div class="tbl-wrap"><div class="empty" id="empty-hsn">Load data to view HSN summary</div><table id="tbl-hsn" style="display:none"><thead></thead><tbody></tbody></table></div></div>
|
|
</div>
|
|
<div class="tab-content" id="pane-cdn">
|
|
<div class="tbl-outer"><div class="tbl-wrap"><div class="empty" id="empty-cdn">Load data to view Credit/Debit Notes</div><table id="tbl-cdn" style="display:none"><thead></thead><tbody></tbody></table></div></div>
|
|
</div>
|
|
|
|
<div class="status-bar">
|
|
<div class="status-dot" id="statusDot"></div>
|
|
<span id="statusMsg">Select date range and click Load Data</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="toast" id="toast"></div>
|
|
|
|
<div class="detail-overlay" id="detailOverlay"></div>
|
|
<div class="detail-panel" id="detailPanel">
|
|
<div class="detail-header">
|
|
<div>
|
|
<h3 id="detailTitle">Row Details</h3>
|
|
<div class="row-num" id="detailRowNum"></div>
|
|
</div>
|
|
<button class="detail-close" id="detailClose">✕</button>
|
|
</div>
|
|
<div class="detail-body" id="detailBody"></div>
|
|
<div class="detail-footer" id="detailFooterView">
|
|
<button class="btn btn-outline" id="detailEditBtn">Edit Row</button>
|
|
</div>
|
|
<div class="detail-footer" id="detailFooterEdit" style="display:none">
|
|
<button class="btn btn-outline" id="detailDeleteBtn" style="color:#dc2626;border-color:#dc2626">Delete Row</button>
|
|
<button class="btn btn-outline" id="detailCancelBtn">Cancel</button>
|
|
<button class="btn btn-primary" id="detailSaveBtn">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function(){
|
|
const tok=sessionStorage.getItem('portal_token');
|
|
if(!tok){window.location.href='/';return;}
|
|
|
|
const COLS={
|
|
b2b:[
|
|
{k:'invoice_type',l:'Type',fmt:typeBadge},
|
|
{k:'fp',l:'FP'},
|
|
{k:'cp_gstin',l:'Supplier GSTIN',cls:'trunc-sm'},
|
|
{k:'cp_gstin_name',l:'Supplier Name',cls:'trunc'},
|
|
{k:'invoice_no',l:'Invoice No',cls:'trunc-sm'},
|
|
{k:'invoice_dt',l:'Date',fmt:fmtDate},
|
|
{k:'invoice_val',l:'Inv Value',cls:'num',fmt:fmtNum},
|
|
{k:'rate_of_tax',l:'Rate%',cls:'num'},
|
|
{k:'taxable_val',l:'Taxable',cls:'num',fmt:fmtNum},
|
|
{k:'igst_amnt',l:'IGST',cls:'num',fmt:fmtNum},
|
|
{k:'cgst_amnt',l:'CGST',cls:'num',fmt:fmtNum},
|
|
{k:'sgst_amnt',l:'SGST',cls:'num',fmt:fmtNum},
|
|
{k:'cess_amnt',l:'Cess',cls:'num',fmt:fmtNum},
|
|
{k:'pos',l:'POS'},
|
|
{k:'rchrg',l:'RC'},
|
|
{k:'eligibility_of_itc',l:'ITC Eligibility',fmt:itcBadge},
|
|
],
|
|
impg:[
|
|
{k:'invoice_type',l:'Type',fmt:typeBadge},
|
|
{k:'fp',l:'FP'},
|
|
{k:'cp_gstin',l:'Supplier GSTIN',cls:'trunc-sm'},
|
|
{k:'cp_gstin_name',l:'Supplier Name',cls:'trunc'},
|
|
{k:'invoice_no',l:'Invoice No',cls:'trunc-sm'},
|
|
{k:'invoice_dt',l:'Date',fmt:fmtDate},
|
|
{k:'invoice_val',l:'Inv Value',cls:'num',fmt:fmtNum},
|
|
{k:'rate_of_tax',l:'Rate%',cls:'num'},
|
|
{k:'taxable_val',l:'Taxable',cls:'num',fmt:fmtNum},
|
|
{k:'igst_amnt',l:'IGST',cls:'num',fmt:fmtNum},
|
|
{k:'cess_amnt',l:'Cess',cls:'num',fmt:fmtNum},
|
|
{k:'pos',l:'POS'},
|
|
{k:'eligibility_of_itc',l:'ITC Eligibility',fmt:itcBadge},
|
|
{k:'is_pur_frm_sez',l:'Purchase from SEZ (Y/N)'},
|
|
{k:'bill_entry_no',l:'BoE No',cls:'trunc-sm'},
|
|
{k:'bill_entry_dt',l:'BoE Date',fmt:fmtDate},
|
|
{k:'bill_entry_val',l:'BoE Value',cls:'num',fmt:fmtNum},
|
|
],
|
|
b2bur:[
|
|
{k:'invoice_type',l:'Type',fmt:typeBadge},
|
|
{k:'fp',l:'FP'},
|
|
{k:'cp_gstin_name',l:'Supplier Name',cls:'trunc'},
|
|
{k:'invoice_category',l:'Category'},
|
|
{k:'invoice_no',l:'Invoice No',cls:'trunc-sm'},
|
|
{k:'invoice_dt',l:'Date',fmt:fmtDate},
|
|
{k:'payment_vchr_no',l:'Payment Vchr No',cls:'trunc-sm'},
|
|
{k:'payment_vchr_dt',l:'Payment Vchr Dt',fmt:fmtDate},
|
|
{k:'unique_ref_no',l:'Unique Ref No',cls:'trunc-sm'},
|
|
{k:'invoice_val',l:'Inv Value',cls:'num',fmt:fmtNum},
|
|
{k:'taxable_val',l:'Taxable',cls:'num',fmt:fmtNum},
|
|
{k:'rate_of_tax',l:'Rate%',cls:'num'},
|
|
{k:'igst_amnt',l:'IGST',cls:'num',fmt:fmtNum},
|
|
{k:'cgst_amnt',l:'CGST',cls:'num',fmt:fmtNum},
|
|
{k:'sgst_amnt',l:'SGST',cls:'num',fmt:fmtNum},
|
|
{k:'cess_amnt',l:'Cess',cls:'num',fmt:fmtNum},
|
|
{k:'pos',l:'POS'},
|
|
{k:'rchrg',l:'RC'},
|
|
{k:'eligibility_of_itc',l:'ITC Eligibility',fmt:itcBadge},
|
|
{k:'tot_tax_avlbl_itc_igst',l:'ITC IGST',cls:'num',fmt:fmtNum},
|
|
{k:'tot_tax_avlbl_itc_cgst',l:'ITC CGST',cls:'num',fmt:fmtNum},
|
|
{k:'tot_tax_avlbl_itc_sgst',l:'ITC SGST',cls:'num',fmt:fmtNum},
|
|
{k:'total_tax_itc_cess',l:'ITC Cess',cls:'num',fmt:fmtNum},
|
|
{k:'orig_invoice_no',l:'Orig Invoice No',cls:'trunc-sm'},
|
|
{k:'orig_invoice_dt',l:'Orig Invoice Dt',fmt:fmtDate},
|
|
],
|
|
hsn:[
|
|
{k:'fp',l:'FP'},
|
|
{k:'hsn_sc',l:'HSN/SAC'},
|
|
{k:'uom',l:'UQC'},
|
|
{k:'rate_of_tax',l:'Rate%',cls:'num'},
|
|
{k:'qty',l:'Qty',cls:'num',fmt:v=>fmtNum(v,3)},
|
|
{k:'txval',l:'Taxable',cls:'num',fmt:fmtNum},
|
|
{k:'iamt',l:'IGST',cls:'num',fmt:fmtNum},
|
|
{k:'camt',l:'CGST',cls:'num',fmt:fmtNum},
|
|
{k:'samt',l:'SGST',cls:'num',fmt:fmtNum},
|
|
{k:'csamt',l:'Cess',cls:'num',fmt:fmtNum},
|
|
],
|
|
cdn:[
|
|
{k:'invoice_date', l:'Date', fmt:fmtDate},
|
|
{k:'invoice_no', l:'CN/DN No', cls:'trunc-sm'},
|
|
{k:'bill_no', l:'Bill No', cls:'trunc-sm'},
|
|
{k:'supplier_name', l:'Supplier', cls:'trunc'},
|
|
{k:'supplier_gstin', l:'GSTIN', cls:'trunc-sm'},
|
|
{k:'state_pos', l:'State'},
|
|
{k:'taxable_value', l:'Taxable', cls:'num', fmt:fmtNum},
|
|
{k:'igst_rate', l:'IGST%', cls:'num'},
|
|
{k:'igst_amount', l:'IGST', cls:'num', fmt:fmtNum},
|
|
{k:'cgst_rate', l:'CGST%', cls:'num'},
|
|
{k:'cgst_amount', l:'CGST', cls:'num', fmt:fmtNum},
|
|
{k:'sgst_rate', l:'SGST%', cls:'num'},
|
|
{k:'sgst_amount', l:'SGST', cls:'num', fmt:fmtNum},
|
|
{k:'line_total_inc_gst', l:'Total incl GST', cls:'num', fmt:fmtNum},
|
|
],
|
|
};
|
|
|
|
function typeBadge(v){
|
|
const m={B2B:'badge-b2b',IMPG:'badge-impg',RCM:'badge-rcm',B2BUR:'badge-rcm'};
|
|
const lbl=v==='RCM'?'B2B':(v||'');
|
|
return`<span class="badge ${m[v]||''}">${lbl}</span>`;
|
|
}
|
|
function itcBadge(v){
|
|
if(!v)return'';
|
|
const m={'Capital Goods':'itc-capital','Inputs':'itc-inputs','Input Services':'itc-services','Ineligible':'itc-ineligible','All other ITC':'itc-other'};
|
|
return`<span class="${m[v]||'itc-other'}">${v}</span>`;
|
|
}
|
|
function fmtNum(v,dec=2){if(v==null||v==='')return'';return Number(v).toLocaleString('en-IN',{minimumFractionDigits:dec,maximumFractionDigits:dec});}
|
|
function fmtDate(v){
|
|
if(!v)return'';
|
|
const d=new Date(v);
|
|
if(isNaN(d))return String(v);
|
|
const dd=String(d.getUTCDate()).padStart(2,'0');
|
|
const mm=String(d.getUTCMonth()+1).padStart(2,'0');
|
|
return `${dd}-${mm}-${d.getUTCFullYear()}`;
|
|
}
|
|
|
|
const state={b2b:[],impg:[],b2bur:[],hsn:[],cdn:[]};
|
|
let activeTab='b2b',searchTerm='';
|
|
|
|
const fromDate=document.getElementById('fromDate');
|
|
const toDate=document.getElementById('toDate');
|
|
const tcsPeriod=document.getElementById('tcsPeriod');
|
|
const btnLoad=document.getElementById('btnLoad');
|
|
const btnDownload=document.getElementById('btnDownload');
|
|
const btnUpload=document.getElementById('btnUpload');
|
|
const uploadSection=document.getElementById('uploadSection');
|
|
const statusDot=document.getElementById('statusDot');
|
|
const statusMsg=document.getElementById('statusMsg');
|
|
const summaryChips=document.getElementById('summaryChips');
|
|
const searchInput=document.getElementById('searchInput');
|
|
const srchCount=document.getElementById('srchCount');
|
|
|
|
(function(){
|
|
const now=new Date();
|
|
const y=now.getFullYear(),m=now.getMonth();
|
|
const from=new Date(y,m,1),to=new Date(y,m+1,0);
|
|
fromDate.value=from.toISOString().slice(0,10);
|
|
toDate.value=to.toISOString().slice(0,10);
|
|
tcsPeriod.value=`${String(m+1).padStart(2,'0')}${y}`;
|
|
})();
|
|
|
|
document.getElementById('tabBar').addEventListener('click',e=>{
|
|
const t=e.target.closest('.tab');
|
|
if(!t)return;
|
|
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));
|
|
document.querySelectorAll('.tab-content').forEach(x=>x.classList.remove('active'));
|
|
t.classList.add('active');
|
|
activeTab=t.dataset.tab;
|
|
document.getElementById('pane-'+activeTab).classList.add('active');
|
|
applySearch();
|
|
});
|
|
|
|
searchInput.addEventListener('input',()=>{
|
|
searchTerm=searchInput.value.trim().toLowerCase();
|
|
applySearch();
|
|
});
|
|
|
|
function applySearch(){
|
|
const rows=state[activeTab]||[];
|
|
const filtered=filterRows(rows);
|
|
renderTable(activeTab,filtered,true);
|
|
srchCount.textContent=searchTerm&&rows.length?`${filtered.length} of ${rows.length}`:(rows.length?`${rows.length} rows`:'');
|
|
}
|
|
|
|
function filterRows(rows){
|
|
if(!searchTerm)return rows;
|
|
return rows.filter(r=>Object.values(r).some(v=>v!=null&&String(v).toLowerCase().includes(searchTerm)));
|
|
}
|
|
|
|
function setStatus(msg,st){statusMsg.textContent=msg;statusDot.className='status-dot'+(st?' '+st:'');}
|
|
function showToast(msg,type='info'){const t=document.getElementById('toast');t.textContent=msg;t.className=`toast ${type} show`;setTimeout(()=>t.classList.remove('show'),3500);}
|
|
|
|
function renderTable(tabKey,rows,isSearch=false){
|
|
const cols=COLS[tabKey];
|
|
const tbl=document.getElementById('tbl-'+tabKey);
|
|
const empty=document.getElementById('empty-'+tabKey);
|
|
if(!rows||!rows.length){
|
|
tbl.style.display='none';
|
|
empty.textContent=isSearch?'No matching rows':'No data for selected period';
|
|
empty.style.display='block';
|
|
return;
|
|
}
|
|
tbl.querySelector('thead').innerHTML=`<tr>${cols.map(c=>`<th>${c.l}</th>`).join('')}</tr>`;
|
|
tbl.querySelector('tbody').innerHTML=rows.map((r,i)=>{
|
|
const bg=r._added?'#ecfdf5':(r._edited?'#fffbeb':'');
|
|
return`<tr data-idx="${i}"${bg?` style="background:${bg}"`:''}>${cols.map((c,ci)=>{
|
|
const v=r[c.k];
|
|
const cell=c.fmt?c.fmt(v):escHtml(v==null?'':String(v));
|
|
const badge=ci===0?(r._added?'<span class="badge-added">NEW</span>':(r._edited?'<span class="badge-edited">EDITED</span>':'')):'';
|
|
return`<td${c.cls?` class="${c.cls}"`:''}>${cell}${badge}</td>`;
|
|
}).join('')}</tr>`;
|
|
}).join('');
|
|
tbl.style.display='table';
|
|
empty.style.display='none';
|
|
tbl.querySelector('tbody').addEventListener('click',e=>{
|
|
const tr=e.target.closest('tr');
|
|
if(!tr)return;
|
|
document.querySelectorAll('tbody tr.selected').forEach(r=>r.classList.remove('selected'));
|
|
tr.classList.add('selected');
|
|
openDetail(tabKey,rows[+tr.dataset.idx],+tr.dataset.idx);
|
|
});
|
|
}
|
|
|
|
function escHtml(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
|
|
|
const detailOverlay=document.getElementById('detailOverlay');
|
|
const detailPanel=document.getElementById('detailPanel');
|
|
const detailBody=document.getElementById('detailBody');
|
|
const detailTitle=document.getElementById('detailTitle');
|
|
const detailRowNum=document.getElementById('detailRowNum');
|
|
const detailFooterView=document.getElementById('detailFooterView');
|
|
const detailFooterEdit=document.getElementById('detailFooterEdit');
|
|
const detailEditBtn=document.getElementById('detailEditBtn');
|
|
const detailCancelBtn=document.getElementById('detailCancelBtn');
|
|
const detailSaveBtn=document.getElementById('detailSaveBtn');
|
|
const detailDeleteBtn=document.getElementById('detailDeleteBtn');
|
|
const btnAddRow=document.getElementById('btnAddRow');
|
|
const tabLabels={b2b:'B2B Invoice',impg:'IMPG Invoice',b2bur:'B2BUR Invoice',hsn:'HSN Line',cdn:'Credit/Debit Note'};
|
|
let curTab=null,curRow=null,curIdx=null;
|
|
|
|
function renderDetailView(){
|
|
const cols=COLS[curTab];
|
|
detailBody.innerHTML=cols.map(c=>{
|
|
const v=curRow[c.k];
|
|
const disp=v==null||v===''?'—':(c.fmt?v:String(v));
|
|
const isNum=!!c.cls?.includes('num');
|
|
const formatted=isNum&&v!=null&&v!==''?fmtNum(v):escHtml(String(disp));
|
|
return`<div class="detail-row"><div class="detail-key">${c.l}</div><div class="detail-val${isNum?' num':''}">${formatted}</div></div>`;
|
|
}).join('');
|
|
detailFooterView.style.display='flex';
|
|
detailFooterEdit.style.display='none';
|
|
}
|
|
|
|
function renderDetailEdit(){
|
|
const cols=COLS[curTab];
|
|
detailBody.innerHTML=cols.map(c=>{
|
|
const v=curRow[c.k];
|
|
const isNum=!!c.cls?.includes('num');
|
|
const val=v==null?'':String(v);
|
|
return`<div class="detail-row"><div class="detail-key">${c.l}</div>`
|
|
+`<input class="detail-edit-input${isNum?' num':''}" data-key="${c.k}" data-num="${isNum?'1':'0'}" value="${escHtml(val)}"></div>`;
|
|
}).join('');
|
|
detailFooterView.style.display='none';
|
|
detailFooterEdit.style.display='flex';
|
|
|
|
// Paste support: pasting a tab/newline-separated row of values (e.g. copied
|
|
// from Excel) into any field fills that field and the ones below it in order.
|
|
const inputs=Array.from(detailBody.querySelectorAll('.detail-edit-input'));
|
|
inputs.forEach((inp,startIdx)=>{
|
|
inp.addEventListener('paste',e=>{
|
|
const text=(e.clipboardData||window.clipboardData).getData('text');
|
|
const parts=text.split(/\r\n|\r|\n|\t/).map(s=>s.trim());
|
|
if(parts.length<2)return; // single value — let default paste happen
|
|
e.preventDefault();
|
|
parts.forEach((p,i)=>{
|
|
const target=inputs[startIdx+i];
|
|
if(!target)return;
|
|
target.value=p;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
detailEditBtn.addEventListener('click',()=>renderDetailEdit());
|
|
detailCancelBtn.addEventListener('click',()=>renderDetailView());
|
|
|
|
detailSaveBtn.addEventListener('click',()=>{
|
|
detailBody.querySelectorAll('.detail-edit-input').forEach(inp=>{
|
|
const key=inp.dataset.key;
|
|
const raw=inp.value.trim();
|
|
if(inp.dataset.num==='1'){
|
|
curRow[key]=raw===''?null:(isNaN(+raw)?raw:+raw);
|
|
} else {
|
|
curRow[key]=raw;
|
|
}
|
|
});
|
|
if(!curRow._added) curRow._edited=true;
|
|
showToast('Row saved locally — re-download/upload to apply','success');
|
|
renderDetailView();
|
|
applySearch();
|
|
});
|
|
|
|
detailDeleteBtn.addEventListener('click',()=>{
|
|
if(!confirm('Remove this row from the current dataset?'))return;
|
|
const arr=state[curTab];
|
|
const realIdx=arr.indexOf(curRow);
|
|
if(realIdx>-1) arr.splice(realIdx,1);
|
|
closeDetail();
|
|
applySearch();
|
|
showToast('Row removed locally — re-download/upload to apply','success');
|
|
});
|
|
|
|
btnAddRow.addEventListener('click',()=>{
|
|
const cols=COLS[activeTab];
|
|
const blank={};
|
|
cols.forEach(c=>{ blank[c.k]=c.cls?.includes('num')?0:''; });
|
|
blank._added=true;
|
|
state[activeTab].push(blank);
|
|
applySearch();
|
|
const idx=filterRows(state[activeTab]).indexOf(blank);
|
|
openDetail(activeTab,blank,idx<0?state[activeTab].length-1:idx);
|
|
renderDetailEdit();
|
|
});
|
|
|
|
function openDetail(tabKey,rowData,rowIndex){
|
|
curTab=tabKey;curRow=rowData;curIdx=rowIndex;
|
|
const badge=rowData._added?'<span class="badge-added">NEW</span>':(rowData._edited?'<span class="badge-edited">EDITED</span>':'');
|
|
detailTitle.innerHTML=(tabLabels[tabKey]||'Row Details')+badge;
|
|
detailRowNum.textContent=`Row ${rowIndex+1}`;
|
|
renderDetailView();
|
|
detailOverlay.classList.add('show');
|
|
detailPanel.classList.add('show');
|
|
}
|
|
|
|
function closeDetail(){
|
|
detailOverlay.classList.remove('show');
|
|
detailPanel.classList.remove('show');
|
|
document.querySelectorAll('tbody tr.selected').forEach(r=>r.classList.remove('selected'));
|
|
}
|
|
|
|
document.getElementById('detailClose').addEventListener('click',closeDetail);
|
|
detailOverlay.addEventListener('click',closeDetail);
|
|
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeDetail();});
|
|
|
|
// ── load data ─────────────────────────────────────────────────────────────
|
|
btnLoad.addEventListener('click', loadData);
|
|
|
|
async function loadData(){
|
|
const from=fromDate.value,to=toDate.value;
|
|
if(!from||!to){showToast('Select from and to dates','error');return;}
|
|
btnLoad.disabled=true;
|
|
setStatus('Loading purchase data…','loading');
|
|
summaryChips.style.display='none';
|
|
|
|
const hdrs={'Authorization':'Bearer '+tok};
|
|
const params=`from=${from}&to=${to}`;
|
|
|
|
try {
|
|
const [rb2b,rb2bur,rhsn,rcdn]=await Promise.all([
|
|
fetch(`/api/gstr2/b2b?${params}`,{headers:hdrs}).then(r=>r.json()),
|
|
fetch(`/api/gstr2/b2bur?${params}`,{headers:hdrs}).then(r=>r.json()),
|
|
fetch(`/api/gstr2/hsn?${params}`,{headers:hdrs}).then(r=>r.json()),
|
|
fetch(`/api/gstr2/cdn?${params}`,{headers:hdrs}).then(r=>r.json()),
|
|
]);
|
|
|
|
const errs=[rb2b,rb2bur,rhsn,rcdn].filter(r=>!r.success).map(r=>r.message);
|
|
if(errs.length){setStatus('Error: '+errs[0],'err');showToast(errs[0],'error');btnLoad.disabled=false;return;}
|
|
|
|
const allB2b=rb2b.data||[];
|
|
state.b2b =allB2b.filter(r=>r.invoice_type==='B2B'||r.invoice_type==='RCM');
|
|
state.impg =allB2b.filter(r=>r.invoice_type==='IMPG');
|
|
state.b2bur=rb2bur.data||[];
|
|
state.hsn =rhsn.data||[];
|
|
state.cdn =rcdn.data||[];
|
|
|
|
searchInput.value='';searchTerm='';srchCount.textContent='';
|
|
['b2b','impg','b2bur','hsn','cdn'].forEach(k=>renderTable(k,state[k]));
|
|
|
|
document.getElementById('tb2b').textContent=state.b2b.length;
|
|
document.getElementById('timpg').textContent=state.impg.length;
|
|
document.getElementById('tb2bur').textContent=state.b2bur.length;
|
|
document.getElementById('thsn').textContent=state.hsn.length;
|
|
document.getElementById('tcdn').textContent=state.cdn.length;
|
|
|
|
document.getElementById('cntB2b').textContent=state.b2b.length;
|
|
document.getElementById('cntImpg').textContent=state.impg.length;
|
|
document.getElementById('cntB2bur').textContent=state.b2bur.length;
|
|
document.getElementById('cntHsn').textContent=state.hsn.length;
|
|
document.getElementById('cntCdn').textContent=state.cdn.length;
|
|
summaryChips.style.display='flex';
|
|
|
|
const total=allB2b.length+state.b2bur.length+state.hsn.length+state.cdn.length;
|
|
setStatus(`Loaded ${total} rows for ${from} to ${to}`,'ok');
|
|
btnDownload.disabled=false;
|
|
btnUpload.disabled=false;
|
|
btnAddRow.disabled=false;
|
|
showToast('Data loaded successfully','success');
|
|
} catch(e){
|
|
setStatus('Failed: '+e.message,'err');
|
|
showToast(e.message,'error');
|
|
} finally {
|
|
btnLoad.disabled=false;
|
|
}
|
|
}
|
|
|
|
// ── download ───────────────────────────────────────────────────────────────
|
|
btnDownload.addEventListener('click',()=>{
|
|
try{
|
|
const wb=XLSX.utils.book_new();
|
|
const toSheet=(rows,cols)=>{
|
|
if(!rows.length)return XLSX.utils.aoa_to_sheet([cols.map(c=>c.l)]);
|
|
return XLSX.utils.json_to_sheet(rows.map(r=>{
|
|
const obj={};cols.forEach(c=>{obj[c.l]=r[c.k]??'';});return obj;
|
|
}),{header:cols.map(c=>c.l)});
|
|
};
|
|
const plain=k=>COLS[k].map(c=>({k:c.k,l:c.l}));
|
|
XLSX.utils.book_append_sheet(wb,toSheet(state.b2b,plain('b2b')),'B2B');
|
|
XLSX.utils.book_append_sheet(wb,toSheet(state.impg,plain('impg')),'IMPG');
|
|
XLSX.utils.book_append_sheet(wb,toSheet(state.b2bur,plain('b2bur')),'B2BUR');
|
|
XLSX.utils.book_append_sheet(wb,toSheet(state.hsn,plain('hsn')),'HSN Summary');
|
|
XLSX.utils.book_append_sheet(wb,toSheet(state.cdn,plain('cdn')),'CDN');
|
|
const from=fromDate.value.replace(/-/g,''),to=toDate.value.replace(/-/g,'');
|
|
XLSX.writeFile(wb,`GSTR2_${from}_${to}.xlsx`);
|
|
showToast('Excel downloaded','success');
|
|
}catch(e){showToast('Download failed: '+e.message,'error');}
|
|
});
|
|
|
|
// ── import excel (temporary) ───────────────────────────────────────────────
|
|
const btnImportExcel=document.getElementById('btnImportExcel');
|
|
const importFileInput=document.getElementById('importFileInput');
|
|
|
|
btnImportExcel.addEventListener('click',()=>importFileInput.click());
|
|
|
|
// Parse any date string/object → YYYY-MM-DD (what toTcsDate expects)
|
|
function parseToIso(v){
|
|
if(!v&&v!==0) return '';
|
|
// JS Date object (from cellDates:true)
|
|
if(v instanceof Date) return isNaN(v)?'':v.toISOString().slice(0,10);
|
|
const s=String(v).trim();
|
|
if(!s) return '';
|
|
// Already YYYY-MM-DD
|
|
if(/^\d{4}-\d{2}-\d{2}/.test(s)) return s.slice(0,10);
|
|
// DD-MM-YYYY or DD/MM/YYYY
|
|
const m=s.match(/^(\d{2})[\/\-](\d{2})[\/\-](\d{4})/);
|
|
if(m) return `${m[3]}-${m[2]}-${m[1]}`;
|
|
// MM/DD/YYYY (SheetJS default string)
|
|
const m2=s.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})/);
|
|
if(m2) return `${m2[3]}-${String(m2[1]).padStart(2,'0')}-${String(m2[2]).padStart(2,'0')}`;
|
|
return s;
|
|
}
|
|
|
|
importFileInput.addEventListener('change',e=>{
|
|
const file=e.target.files[0];
|
|
if(!file)return;
|
|
importFileInput.value=''; // reset so same file can re-import
|
|
const reader=new FileReader();
|
|
reader.onload=evt=>{
|
|
try{
|
|
const wb=XLSX.read(evt.target.result,{type:'array',cellDates:true});
|
|
// Map sheet name → tab key
|
|
const sheetMap={'B2B':'b2b','IMPG':'impg','B2BUR':'b2bur','HSN Summary':'hsn','HSN':'hsn','CDN':'cdn'};
|
|
let imported=0;
|
|
wb.SheetNames.forEach(sname=>{
|
|
const tabKey=sheetMap[sname];
|
|
if(!tabKey) return;
|
|
const cols=COLS[tabKey];
|
|
// build label→key map and date-key set
|
|
const l2k={};
|
|
const dateCols=new Set();
|
|
cols.forEach(c=>{
|
|
l2k[c.l.toLowerCase()]=c.k;
|
|
if(c.fmt===fmtDate) dateCols.add(c.k);
|
|
});
|
|
const rows=XLSX.utils.sheet_to_json(wb.Sheets[sname],{raw:true,defval:'',cellDates:true});
|
|
if(!rows.length) return;
|
|
const mapped=rows.map(r=>{
|
|
const obj={_added:true};
|
|
Object.entries(r).forEach(([h,v])=>{
|
|
const key=l2k[h.trim().toLowerCase()];
|
|
if(!key)return;
|
|
const col=cols.find(c=>c.k===key);
|
|
const isNum=!!col?.cls?.includes('num');
|
|
if(dateCols.has(key)){
|
|
obj[key]=parseToIso(v);
|
|
} else {
|
|
obj[key]=isNum&&v!==''&&v!=null?+v:v;
|
|
}
|
|
});
|
|
return obj;
|
|
});
|
|
state[tabKey]=[...state[tabKey],...mapped];
|
|
renderTable(tabKey,state[tabKey]);
|
|
document.getElementById('t'+tabKey).textContent=state[tabKey].length;
|
|
imported+=mapped.length;
|
|
});
|
|
if(imported){
|
|
btnDownload.disabled=false; btnUpload.disabled=false;
|
|
document.getElementById('btnAddRow').disabled=false;
|
|
summaryChips.style.display='flex';
|
|
['b2b','impg','b2bur','hsn','cdn'].forEach(k=>{
|
|
const el=document.getElementById({b2b:'cntB2b',impg:'cntImpg',b2bur:'cntB2bur',hsn:'cntHsn',cdn:'cntCdn'}[k]);
|
|
if(el) el.textContent=state[k].length||'—';
|
|
});
|
|
setStatus(`Imported ${imported} row(s) from Excel (temporary)`,'ok');
|
|
showToast(`Imported ${imported} row(s) — not saved to DB`,'success');
|
|
applySearch();
|
|
} else {
|
|
showToast('No matching sheets found. Expected: B2B, IMPG, B2BUR, HSN Summary, CDN','error');
|
|
}
|
|
}catch(err){showToast('Import failed: '+err.message,'error');}
|
|
};
|
|
reader.readAsArrayBuffer(file);
|
|
});
|
|
|
|
// ── upload to TCS ──────────────────────────────────────────────────────────
|
|
const SECTION_LABELS = { all:'All Sections', b2b:'B2B', impg:'IMPG', b2bur:'B2BUR', hsn:'HSN', cdn:'CDN' };
|
|
|
|
btnUpload.addEventListener('click', async () => {
|
|
const period = tcsPeriod.value.trim();
|
|
if (!/^\d{6}$/.test(period)) { showToast('Enter valid period as MMYYYY','error'); return; }
|
|
|
|
const sel = uploadSection.value; // 'all' | 'b2b' | 'impg' | 'b2bur' | 'hsn' | 'cdn'
|
|
const label = SECTION_LABELS[sel] || sel;
|
|
|
|
// b2b/impg all feed the single 'b2b' purchase array on the backend (b2b already includes RCM rows)
|
|
const purchaseSel = sel === 'all' || sel === 'b2b' || sel === 'impg';
|
|
const payload = {
|
|
period,
|
|
b2b: purchaseSel ? [
|
|
...(sel === 'all' || sel === 'b2b' ? state.b2b : []),
|
|
...(sel === 'all' || sel === 'impg' ? state.impg : []),
|
|
] : [],
|
|
b2bur: sel === 'all' || sel === 'b2bur' ? state.b2bur : [],
|
|
hsn: sel === 'all' || sel === 'hsn' ? state.hsn : [],
|
|
cdn: sel === 'all' || sel === 'cdn' ? state.cdn : [],
|
|
};
|
|
|
|
const total = payload.b2b.length + payload.b2bur.length + payload.hsn.length + payload.cdn.length;
|
|
if (!total) { showToast(`No ${label} data to upload. Load data first.`,'error'); return; }
|
|
|
|
const confirmMsg = sel === 'all'
|
|
? `Upload all sections to TCS GSP for period ${period}?\n\n` +
|
|
`B2B (incl. RCM): ${state.b2b.length} | IMPG: ${state.impg.length} | B2BUR: ${state.b2bur.length} | HSN: ${state.hsn.length} | CDN: ${state.cdn.length}`
|
|
: `Upload ${label} only to TCS GSP for period ${period}?\n\nRows: ${total}`;
|
|
if (!confirm(confirmMsg)) return;
|
|
|
|
btnUpload.disabled = true;
|
|
setStatus(`Uploading ${label} to TCS GSP…`,'loading');
|
|
try {
|
|
const resp = await fetch('/api/gstr2/upload-tcs', {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Bearer ' + tok, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const json = await resp.json();
|
|
if (json.success) {
|
|
setStatus('Uploaded ✓ ' + json.message, 'ok');
|
|
showToast(json.message, 'success');
|
|
} else {
|
|
setStatus('Upload failed: ' + json.message, 'err');
|
|
showToast(json.message, 'error');
|
|
}
|
|
} catch(e) {
|
|
setStatus('Error: ' + e.message, 'err');
|
|
showToast(e.message, 'error');
|
|
} finally {
|
|
btnUpload.disabled = false;
|
|
}
|
|
});
|
|
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|