EC Tax Bookkeeping is a free system. While Google is still processing the official verification of EC TAXPH, you will set up the app on your own Google account for now - so it runs entirely under you.
What this means:
Please use a computer, laptop, or Mac - this does not work on a cellphone.
What you need: a Google account (Gmail), and a computer / laptop / Mac.
For new users:
This is where the app will live.
Code.gs, select all (Ctrl+A) and delete./**
* EC TAX BOOKKEEPING SYSTEM - Web App Report Engine
* Version: Phase 2.2 - TB + GL with Group by Account | Group by Name
* Owner: Ed Cervantes (EC TAXPH)
*
* CHANGES FROM v2.1:
* - GL Group/Summarize dropdown reduced to 2 options:
* • Group by Account (default, existing)
* • Group by Name (NEW)
* - renderGL() now branches based on GL_STATE.groupMode
* - New helper: renderGL_byName()
* - Export (expGL) branches by mode
* - O/C toggles auto-hide when mode = name
*/
/* ═══════════════════════════════════════════
SERVER: doPost / doGet / conversion
═══════════════════════════════════════════ */
/* SERVER-SIDE canonical reference lists (§3A: app = master). Single source.
The client COA_FIX_/SET_FIX_ are injected from here by buildHTML_; doPost returns
this to the launcher, which idempotently mirrors it into the SoT _Helpers. */
var REF_LISTS_ = {
coa: {type:["Asset","Liability","Equity","Revenue","Expense"],normal:["Debit","Credit"],fsGroup:["Current Assets","Non-Current Assets","Current Liabilities","Long-Term Liabilities","Equity","Revenue","Cost of Sales","Operating Expenses","Other Income","Other Expenses","Income Tax"],noteGroup:["4 - Cash and Cash Equivalents","5 - Trade and Other Receivables","6 - Inventories","7 - Prepaid and Other Current Assets","8 - Property, Plant and Equipment","9 - Trade and Other Payables","10 - Short-term Borrowings","11 - Long-term Obligations","12 - Owner's Equity","13 - Revenue","14 - Cost of Sales","15 - Operating Expenses","16 - Other Income / (Expenses)"],taxLine:["Sales / Revenues / Receipts / Fees","Other Non-Operating Income","Cost of Sales / Services","1 - Amortizations","2 - Bad Debts","3 - Charitable and Other Contributions","4 - Depletion","5 - Depreciation","6 - Entertainment / Amusement and Recreation","7 - Fringe Benefits","8 - Interest","9 - Losses","10 - Pension Trusts","11 - Rental","12 - Research and Development","13 - Salaries / Wages and Allowances","14 - SSS / GSIS / PhilHealth / HDMF Contributions","15 - Taxes and Licenses","16 - Transportation and Travel","17a - Janitorial and Messengerial Services","17b - Professional Fees","17c - Security Services","17d - Other Deductible Expenses (see FS)","Withholding Tax - Compensation & Benefits","Withholding Tax - Creditable (EWT)","Withholding Tax - Final"],ptTaxLine:["PT010 - 3% - Exempt from VAT Sec.109(BB) (Sec. 116)","PT040 - 3% - Domestic Carriers & Garages (Sec. 117)","PT041 - 3% - International Carriers (Sec. 118)","PT060 - 2% - Franchise Gas & Water (Sec. 119)","PT070 - 3% - Franchise Radio/TV ≤P10M (Sec. 119)","PT090 - 10% - Overseas Dispatch (Sec. 120)","PT101 - 1% - Bank Lending >5yrs (Sec. 121)","PT102 - 0% - Bank Dividends (Sec. 121)","PT103 - 7% - Bank Other Income (Sec. 121)","PT104 - 7% - Bank FX Trading (Sec. 121)","PT105 - 5% - Bank Lending ≤5yrs (Sec. 121)","PT113 - 5% - Non-Bank Lending ≤5yrs (Sec. 122)","PT114 - 1% - Non-Bank Lending >5yrs (Sec. 122)","PT115 - 5% - Non-Bank Other Income (Sec. 122)","PT120 - 2% - Life Insurance Premiums (Sec. 123)","PT130 - 4% - Insurance Agents (Sec. 124)","PT132 - 5% - Foreign Insurance Owners (Sec. 124)","PT140 - 18% - Cockpits (Sec. 125)","PT150 - 18% - Cabarets & Night Clubs (Sec. 125)","PT160 - 10% - Boxing Exhibition (Sec. 125)","PT170 - 15% - Professional Basketball (Sec. 125)","PT180 - 30% - Jai-alai & Race Tracks (Sec. 125)","PTExempt - 0% - Exempt from Percentage Tax"]},
settings: {taxpayerClass:["Individual","Non-Individual"],regType:["Non-VAT Registered","VAT Registered"],category:["Private","Government"],taxableYear:["Calendar Year","Fiscal Year"],rdo:["RDO 001 - Laoag City, Ilocos Norte","RDO 002 - Vigan, Ilocos Sur","RDO 003 - San Fernando, La Union","RDO 004 - Calasiao, Pangasinan","RDO 005 - Alaminos, Pangasinan","RDO 006 - Urdaneta, East Pangasinan","RDO 007 - Bangued, Abra","RDO 008 - Baguio City","RDO 009 - La Trinidad, Benguet","RDO 010 - Bontoc, Mt. Province","RDO 011 - Tabuk City, Kalinga","RDO 012 - Lagawe, Ifugao","RDO 013 - Tuguegarao, Cagayan","RDO 014 - Bayombong, Nueva Vizcaya","RDO 015 - Naguilian, Isabela","RDO 016 - Cabarroguis, Quirino","RDO 17A - Tarlac City, Tarlac","RDO 17B - Paniqui, Tarlac","RDO 018 - Olongapo City","RDO 019 - Subic Bay Freeport Zone","RDO 020 - Balanga, Bataan","RDO 21A - North Pampanga","RDO 21B - South Pampanga","RDO 21C - Clark Freeport and Special Economic Zone","RDO 022 - Baler, Aurora","RDO 23A - North Nueva Ecija","RDO 23B - South Nueva Ecija","RDO 024 - Valenzuela City","RDO 25A - West Bulacan","RDO 25B - East Bulacan","RDO 026 - Malabon-Navotas","RDO 027 - Caloocan City","RDO 028 - Novaliches","RDO 029 - San Nicolas, Tondo","RDO 030 - Binondo","RDO 031 - Sta. Cruz","RDO 032 - Quiapo-Sampaloc-San Miguel","RDO 033 - Intramuros-Ermita-Malate","RDO 034 - Paco-Pandacan-Sta. Ana-San Andres","RDO 035 - Romblon","RDO 036 - Puerto Princesa","RDO 037 - San Jose, Occidental Mindoro","RDO 038 - North Quezon City","RDO 039 - South Quezon City","RDO 040 - Cubao","RDO 041 - Mandaluyong City","RDO 042 - San Juan","RDO 43A - East Pasig","RDO 43B - West Pasig","RDO 044 - Taguig-Pateros","RDO 045 - Marikina","RDO 046 - Cainta-Taytay","RDO 047 - East Makati","RDO 048 - West Makati","RDO 049 - North Makati","RDO 050 - South Makati","RDO 051 - Pasay City","RDO 052 - Parañaque City","RDO 53A - Las Piñas City","RDO 53B - Muntinlupa City","RDO 54A - Trece Martires City, East Cavite","RDO 54B - Kawit, West Cavite","RDO 055 - San Pablo City, East Laguna","RDO 056 - Calamba, Central Laguna","RDO 057 - Biñan, West Laguna","RDO 058 - Batangas City, West Batangas","RDO 059 - Lipa City, East Batangas","RDO 060 - Lucena City, North Quezon","RDO 061 - Gumaca, South Quezon","RDO 062 - Boac, Marinduque","RDO 063 - Calapan, Oriental Mindoro","RDO 064 - Talisay, Camarines Norte","RDO 065 - Naga City","RDO 066 - Iriga City","RDO 067 - Legazpi City, Albay","RDO 068 - Sorsogon, Sorsogon","RDO 069 - Virac, Catanduanes","RDO 070 - Masbate, Masbate","RDO 071 - Kalibo, Aklan","RDO 072 - Roxas City","RDO 073 - San Jose, Antique","RDO 074 - Iloilo City","RDO 075 - Zarraga, Iloilo City","RDO 076 - Victorias City, Negros Occidental","RDO 077 - Bacolod City","RDO 078 - Binalbagan, Negros Occidental","RDO 079 - Dumaguete City","RDO 080 - Mandaue City","RDO 081 - Cebu City North","RDO 082 - Cebu City South","RDO 083 - Talisay City, Cebu","RDO 084 - Tagbilaran City","RDO 085 - Catarman, Northern Samar","RDO 086 - Borongan, Eastern Samar","RDO 087 - Calbayog City, Samar","RDO 088 - Tacloban City","RDO 089 - Ormoc City","RDO 090 - Maasin, Southern Leyte","RDO 091 - Dipolog City","RDO 092 - Pagadian City, Zamboanga del Sur","RDO 93A - Zamboanga City, Zamboanga del Sur","RDO 93B - Zamboanga Sibugay","RDO 094 - Isabela, Basilan","RDO 095 - Jolo, Sulu","RDO 096 - Bongao, Tawi-Tawi","RDO 097 - Gingoog City","RDO 098 - Cagayan de Oro City","RDO 099 - Malaybalay City, Bukidnon","RDO 100 - Ozamis City","RDO 101 - Iligan City","RDO 102 - Marawi City","RDO 103 - Butuan City","RDO 104 - Bayugan City, Agusan del Sur","RDO 105 - Surigao City","RDO 106 - Tandag, Surigao del Sur","RDO 107 - Cotabato City","RDO 108 - Kidapawan, North Cotabato","RDO 109 - Tacurong, Sultan Kudarat","RDO 110 - General Santos City","RDO 111 - Koronadal City","RDO 112 - Tagum, Davao del Norte","RDO 113 - West Davao City","RDO 114 - Mati, Davao Oriental","RDO 115 - Digos, Davao del Sur","RDO 116 - Regular LT Audit Division I","RDO 121 - Excise LT Audit Division I","RDO 122 - LT Division, Makati","RDO 123 - LT Division, Cebu","RDO 124 - Excise LT Audit Division II","RDO 125 - Regular LT Audit Division II","RDO 126 - Regular LT Audit Division III","RDO 127 - LT Division, Davao","RDO 132 - East Davao City"]}
};
function doPost(e) {
try {
var data = JSON.parse(e.postData.contents);
var jeRows = convertBooksToJE_(data);
var salesVat = buildSalesVat_(data);
var purchaseVat = buildPurchaseVat_(data);
var payload = { jeRows: jeRows, voidedRows: convertBooksToVoided_(data), salesVat: salesVat, purchaseVat: purchaseVat, coa: data.coa, settings: data.settings, generatedAt: data.generatedAt, bookMeta: buildBookMeta_(data), contacts: buildContacts_(data), branches: data.branches || [], spreadsheetId: data.spreadsheetId || '', coaLists: (data.classLists && data.classLists.coaLists) || null, settingsLists: (data.classLists && data.classLists.settingsLists) || null };
var html = buildHTML_(payload);
var key = Utilities.getUuid();
var cache = CacheService.getScriptCache();
if (html.length > 90000) {
var chunks = [];
for (var c = 0; c < html.length; c += 90000) chunks.push(html.substring(c, c + 90000));
for (var ci = 0; ci < chunks.length; ci++) cache.put(key + '_' + ci, chunks[ci], 600);
cache.put(key + '_meta', String(chunks.length), 600);
} else { cache.put(key, html, 600); }
// Hardening B: bind this cached report to the requesting (owner) account, so even
// with the key, only the owner can retrieve it from cache. Best-effort (empty when anonymous).
try { var ownerEmail = Session.getActiveUser().getEmail(); if (ownerEmail) cache.put(key + '_owner', ownerEmail, 600); } catch (eo) {}
return ContentService.createTextOutput(JSON.stringify({ key: key, refLists: REF_LISTS_ })).setMimeType(ContentService.MimeType.JSON);
} catch (err) {
return ContentService.createTextOutput(JSON.stringify({ error: err.message })).setMimeType(ContentService.MimeType.JSON);
}
}
function doGet(e) {
var p = (e && e.parameter) ? e.parameter : {};
// ── Live render (WebApp deployed "Execute as: User accessing"). Runs AS the visitor,
// who owns their own Sheet copy, so openById succeeds and every in-app write works.
// No shared cache snapshot of the user's data - each load reads live in the user's session. ──
if (p.ssid) {
try {
var d = readAllDataFromSheet_(p.ssid);
var ssLive = null; try { ssLive = SpreadsheetApp.openById(p.ssid); } catch (eO) {}
if (ssLive) { try { syncRefLists_(ssLive, REF_LISTS_); } catch (e1) {} try { repairAccountDropdowns_(ssLive); } catch (e2) {} try { ensureTaxFilingsSheet_(ssLive); } catch (e3) {} try { ensureSettingsDefaults_(ssLive); } catch (e4) {} }
var pl = { jeRows: convertBooksToJE_(d), voidedRows: convertBooksToVoided_(d), salesVat: buildSalesVat_(d), purchaseVat: buildPurchaseVat_(d),
coa: d.coa, settings: d.settings, generatedAt: d.generatedAt, bookMeta: buildBookMeta_(d),
contacts: buildContacts_(d), branches: d.branches || [], spreadsheetId: p.ssid,
coaLists: null, settingsLists: null, taxFilings: (ssLive ? readTaxFilings_(ssLive) : {}) };
return HtmlService.createHtmlOutput(buildHTML_(pl)).setTitle('EC TAXPH Reports').setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
} catch (errL) {
var msg = (errL && errL.message) ? errL.message : String(errL);
return HtmlService.createHtmlOutput('<html><body style="font-family:sans-serif;padding:40px;text-align:center;"><h2 style="color:#1F3864;">EC TAXPH Reports</h2><p>Could not open your workbook.</p><p style="color:#888;font-size:13px;">' + msg + '</p><p style="color:#888;font-size:13px;">Make sure you are signed in to the Google account that owns this copy, then open it again from the Sheet menu (EC TAXPH → Open App).</p></body></html>');
}
}
var key = p.key ? p.key : null;
if (!key) return HtmlService.createHtmlOutput('<html><body style="font-family:sans-serif;padding:40px;text-align:center;"><h2 style="color:#1F3864;">EC TAXPH Report Engine</h2><p>Active | v2.2</p></body></html>');
var cache = CacheService.getScriptCache(); var html = null;
// Hardening B: legacy cache path - only the owner who generated it may retrieve it.
try { var owner = cache.get(key + '_owner'); var me = Session.getActiveUser().getEmail(); if (owner && me && owner !== me) return expPage_(); } catch (eb) {}
var meta = cache.get(key + '_meta');
if (meta) { var parts = []; for (var i = 0; i < parseInt(meta); i++) { var ch = cache.get(key + '_' + i); if (!ch) return expPage_(); parts.push(ch); } html = parts.join(''); }
else { html = cache.get(key); }
if (html) return HtmlService.createHtmlOutput(html).setTitle('EC TAXPH Reports').setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
return expPage_();
}
function expPage_() { return HtmlService.createHtmlOutput('<html><body style="font-family:sans-serif;padding:40px;text-align:center;"><h2>Report Expired</h2><p>Open the app again from the Sheet menu (EC TAXPH → Open App).</p></body></html>'); }
/* refLists sync (ported from the launcher in thin-launcher migration): mirror the WebApp
canonical lists (REF_LISTS_) into the Sheet's _Helpers ranges that native dropdowns read
from. Idempotent - rewrites only when values/format differ (no churn). Best-effort; runs
as the signed-in user (Execute as: User accessing). */
function syncRefLists_(ss, refLists) {
if (!ss || !refLists) return;
var hp = ss.getSheetByName('_Helpers'); if (!hp) return;
var jobs = [
{ list: refLists.settings && refLists.settings.rdo, col: 26, cap: 200 }, // Z = RDO Codes
{ list: refLists.coa && refLists.coa.taxLine, col: 7, cap: 50 }, // G = Tax Lines
{ list: refLists.coa && refLists.coa.noteGroup, col: 16, cap: 30 }, // P = Notes to FS
{ list: refLists.coa && refLists.coa.ptTaxLine, col: 9, cap: 24, rateCol: 10 } // I = PT Tax Lines (+ J = PT Rate)
];
for (var i = 0; i < jobs.length; i++) {
var j = jobs[i]; if (!j.list || !j.list.length) continue;
try { writeRefCol_(hp, 2, j.col, j.list, j.cap, j.rateCol); } catch (e) {}
}
}
function writeRefCol_(sh, startRow, col, list, capRow, rateCol) {
var n = list.length;
var cur = sh.getRange(startRow, col, n, 1).getValues();
var same = true;
for (var i = 0; i < n; i++) { if (String(cur[i][0]) !== String(list[i])) { same = false; break; } }
var fmtOK = true;
try { var fmts = sh.getRange(startRow, col, n, 1).getNumberFormats(); for (var f = 0; f < n; f++) { if (fmts[f][0] !== '@') { fmtOK = false; break; } } } catch (e) { fmtOK = false; }
if (!same || !fmtOK) {
var rng = sh.getRange(startRow, col, n, 1); rng.setNumberFormat('@');
var vals = []; for (var v = 0; v < n; v++) vals.push([String(list[v])]); rng.setValues(vals);
var clearFrom = startRow + n;
if (capRow >= clearFrom) sh.getRange(clearFrom, col, capRow - clearFrom + 1, 1).clearContent();
}
if (rateCol) {
var rates = []; for (var r = 0; r < n; r++) { var m = String(list[r]).match(/(\d+(?:\.\d+)?)\s*%/); rates.push([ m ? (parseFloat(m[1]) / 100) : 0 ]); }
var rcur = sh.getRange(startRow, rateCol, n, 1).getValues();
var rsame = true; for (var k = 0; k < n; k++) { if (Number(rcur[k][0]) !== Number(rates[k][0])) { rsame = false; break; } }
if (!rsame) sh.getRange(startRow, rateCol, n, 1).setValues(rates);
}
}
/* GUARDRAIL: re-apply the Account Title dropdowns so they ALWAYS cover the full Chart of
Accounts. Root cause of the "Input must fall within specified range" break: the native data
validation pointed to a FIXED COA sub-range (e.g. C7:C95); once the COA grew past it, valid
accounts got rejected. Open-ended C7:C is unreliable (Google snaps it back to a bound on
copy/save). Fix: on every Open App, recompute a generous explicit COA range (clamped to the
sheet) and re-apply it to every book's account selector. Self-healing - any reversion to a
stale hard-coded range is corrected on the next open. Best-effort; runs as the signed-in
user (Execute as: User accessing). Strict reject (allowInvalid=false) keeps typos out. */
function repairAccountDropdowns_(ss) {
if (!ss) return;
var coa = ss.getSheetByName('Chart_of_Accounts'); if (!coa) return;
// Keep a generous fixed FLOOR of 500 rows so the source range stays far ahead of the data: a newly
// added account shows in the dropdown immediately, and even a snap can't drop below ~500. One-time
// per copy (once the sheet has >=500 rows, no more inserts); rows added are plain blanks below the
// COA table and are ignored by the dropdown. Then bound = sheet rows (>=500).
try { var need = 500 - coa.getMaxRows(); if (need > 0) coa.insertRowsAfter(coa.getMaxRows(), need); } catch (eRows) {}
var bound = coa.getMaxRows(); if (bound < 8) return; // explicit, clamped to sheet (never open-ended)
var coaRange = coa.getRange(7, 3, bound - 7 + 1, 1); // Chart_of_Accounts!C7:C{>=500} (col C = Account Title)
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(coaRange, true) // show dropdown; blank cells ignored
.setAllowInvalid(false) // strict: reject anything not in the COA
.build();
// General Journal: per-row account selector in column F (6), data rows 7+.
var gj = ss.getSheetByName('General_Journal');
if (gj) { var gjB = gj.getMaxRows(); if (gjB >= 7) gj.getRange(7, 6, gjB - 7 + 1, 1).setDataValidation(rule); }
// Columnar books: row-6 account selector across the amount columns (row 7 = DR/CR), from O (col 15) onward.
var books = ['Cash_Receipts_Book', 'Cash_Disbursements_Book', 'SJ', 'PJ'];
for (var i = 0; i < books.length; i++) {
var sh = ss.getSheetByName(books[i]); if (!sh) continue;
var lastCol = sh.getLastColumn(); if (lastCol < 15) continue;
var r7 = sh.getRange(7, 15, 1, lastCol - 15 + 1).getValues()[0];
var lastAmt = 0;
for (var c = 0; c < r7.length; c++) { var v = String(r7[c] || '').trim().toUpperCase(); if (v === 'DR' || v === 'CR') lastAmt = 15 + c; }
if (lastAmt >= 15) sh.getRange(6, 15, 1, lastAmt - 15 + 1).setDataValidation(rule);
}
}
/* ═══════════════════════════════════════════
IN-APP "UPDATE" (live re-read of the source Sheet)
The client "Update" button calls apiRefresh(ssId) via google.script.run.
This opens the workbook by ID, re-reads the books (mirroring the launcher's
readAllData_), and returns a fresh DATA payload WITHOUT re-rendering server
HTML. Requires the WebApp deployed "Execute as: User accessing the web app" so
it runs as the signed-in user, who owns their own Sheet copy (openById succeeds).
Readers are copied verbatim from the launcher so the two stay identical in behavior.
═══════════════════════════════════════════ */
function apiRefresh(ssId) {
try {
if (!ssId) return { __error: 'No spreadsheet id. Re-deploy the launcher (it now sends the Sheet ID) and Generate Report once.' };
var data = readAllDataFromSheet_(ssId);
var jeRows = convertBooksToJE_(data);
return {
jeRows: jeRows,
voidedRows: convertBooksToVoided_(data),
salesVat: buildSalesVat_(data),
purchaseVat: buildPurchaseVat_(data),
coa: data.coa,
settings: data.settings,
generatedAt: data.generatedAt,
bookMeta: buildBookMeta_(data),
contacts: buildContacts_(data),
branches: data.branches || [],
spreadsheetId: ssId,
taxFilings: (function(){ try { return readTaxFilings_(SpreadsheetApp.openById(ssId)); } catch (e) { return {}; } })()
};
} catch (e) {
return { __error: (e && e.message) ? e.message : String(e) };
}
}
/* ── TAX FILINGS (P4): filed/unfiled tracking, persisted in a dedicated Tax_Filings sheet.
Each form AND each attachment (SLSP/QAP/SAWT) = its own keyed record (separate status).
Runs as the signed-in user (Execute as: User accessing). ── */
function ensureTaxFilingsSheet_(ss) {
var sh = ss.getSheetByName('Tax_Filings');
if (!sh) {
sh = ss.insertSheet('Tax_Filings');
sh.getRange(1, 1, 1, 7).setValues([['Key', 'Year', 'Period', 'Form/Attachment', 'Date Filed', 'Reference', 'Updated At']]);
sh.setFrozenRows(1);
try { sh.hideSheet(); } catch (e) {}
}
return sh;
}
function readTaxFilings_(ss) {
try {
var sh = ss.getSheetByName('Tax_Filings'); if (!sh) return {};
var lr = sh.getLastRow(); if (lr < 2) return {};
var vals = sh.getRange(2, 1, lr - 1, 7).getValues(); var map = {};
for (var i = 0; i < vals.length; i++) {
var k = String(vals[i][0] || '').trim(); if (!k) continue;
map[k] = { year: vals[i][1], period: String(vals[i][2] || ''), form: String(vals[i][3] || ''), dateFiled: vals[i][4] ? String(vals[i][4]) : '', ref: String(vals[i][5] || '') };
}
return map;
} catch (e) { return {}; }
}
function apiMarkFiled(ssId, rec) {
try {
if (!ssId || !rec || !rec.key) return { __error: 'Missing data.' };
var lock = LockService.getUserLock(); try { lock.tryLock(15000); } catch (eL) {}
var ss = SpreadsheetApp.openById(ssId); var sh = ensureTaxFilingsSheet_(ss);
var lr = sh.getLastRow(); var rowIdx = -1;
if (lr >= 2) { var keys = sh.getRange(2, 1, lr - 1, 1).getValues(); for (var i = 0; i < keys.length; i++) { if (String(keys[i][0]).trim() === String(rec.key).trim()) { rowIdx = 2 + i; break; } } }
var row = [String(rec.key), rec.year || '', rec.period || '', rec.form || '', rec.dateFiled || '', rec.ref || '', new Date().toISOString()];
if (rowIdx > 0) sh.getRange(rowIdx, 1, 1, 7).setValues([row]); else sh.appendRow(row);
try { lock.releaseLock(); } catch (e2) {}
return { ok: true, filings: readTaxFilings_(ss) };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
}
function apiUnmarkFiled(ssId, key) {
try {
if (!ssId || !key) return { __error: 'Missing data.' };
var lock = LockService.getUserLock(); try { lock.tryLock(15000); } catch (eL) {}
var ss = SpreadsheetApp.openById(ssId); var sh = ss.getSheetByName('Tax_Filings');
if (sh) { var lr = sh.getLastRow(); if (lr >= 2) { var keys = sh.getRange(2, 1, lr - 1, 1).getValues(); for (var i = keys.length - 1; i >= 0; i--) { if (String(keys[i][0]).trim() === String(key).trim()) sh.deleteRow(2 + i); } } }
try { lock.releaseLock(); } catch (e2) {}
return { ok: true, filings: sh ? readTaxFilings_(ss) : {} };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
}
/* Mirror of launcher readAllData_, but opens an arbitrary workbook by ID. */
function readAllDataFromSheet_(ssId) {
var ss = SpreadsheetApp.openById(ssId);
var base = {
settings: readSettings_(ss),
coa: readCOA_(ss),
crb: readTaggedBook_(ss, 'Cash_Receipts_Book'),
sj: readTaggedBook_(ss, 'SJ'),
cdb: readTaggedBook_(ss, 'Cash_Disbursements_Book'),
pj: readTaggedBook_(ss, 'PJ'),
gj: readGJ_(ss),
generatedAt: new Date().toISOString(),
spreadsheetId: ssId
};
var mb = readBranches_(ss);
if (mb) { base.branches = mb.branches; base.branchBooks = mb.branchBooks; }
return base;
}
/* ── Sheet readers (copied from launcher Code.gs; keep in sync) ── */
function readBranches_(ss) {
var sheets = ss.getSheets();
var pat = /^(CRB|CDB|SJ|PJ|GJ)(\d+)$/i;
var byBranch = {};
for (var i = 0; i < sheets.length; i++) {
var nm = sheets[i].getName();
var m = nm.match(pat);
if (!m) continue;
var book = m[1].toUpperCase(), bno = m[2];
if (!byBranch[bno]) byBranch[bno] = {};
byBranch[bno][book] = nm;
}
var branchNos = Object.keys(byBranch);
if (!branchNos.length) return null;
var meta = {};
var reg = ss.getSheetByName('Branches');
if (reg && reg.getLastRow() >= 2) {
var vals = reg.getRange(2, 1, reg.getLastRow() - 1, 6).getValues();
for (var r = 0; r < vals.length; r++) {
var no = String(vals[r][0] == null ? '' : vals[r][0]).trim();
if (!no) continue;
if (no.length < 2) no = ('0' + no).slice(-2);
meta[no] = { code: String(vals[r][1] || ''), tradeName: String(vals[r][2] || ''),
address: String(vals[r][3] || ''), type: String(vals[r][4] || ''), active: String(vals[r][5] || '') };
}
}
var branches = [], branchBooks = {};
for (var b = 0; b < branchNos.length; b++) {
var bn = branchNos[b], mm = byBranch[bn];
branchBooks[bn] = {
crb: mm.CRB ? readTaggedBook_(ss, mm.CRB) : null,
cdb: mm.CDB ? readTaggedBook_(ss, mm.CDB) : null,
sj: mm.SJ ? readTaggedBook_(ss, mm.SJ) : null,
pj: mm.PJ ? readTaggedBook_(ss, mm.PJ) : null,
gj: mm.GJ ? readGJ_(ss, mm.GJ) : []
};
var md = meta[bn] || {};
branches.push({ no: bn, code: md.code || bn, tradeName: md.tradeName || ('Branch ' + bn),
address: md.address || '', type: md.type || 'Branch', active: md.active || 'YES' });
}
return { branches: branches, branchBooks: branchBooks };
}
/* Idempotent: fill blank Settings defaults (Individual). Rep/Prepared-by/TIN = owner; citizenship = Filipino. Only writes blanks (never overwrites). Runs on Open App. */
function ensureSettingsDefaults_(ss) {
try {
var sh = ss.getSheetByName('Settings');
if (!sh) return;
function gv(c){ return String(sh.getRange(c).getValue() || '').trim(); }
function setIfBlank(c, val){ if (val && !gv(c)) sh.getRange(c).setValue(val); }
if (gv('C5').toUpperCase().indexOf('NON') === 0) return; // Non-Individual: rep is a separate signatory - skip
var ln = gv('C19'), fn = gv('C21'), mn = gv('C23');
var nm = [fn, mn, ln].filter(function(x){ return x; }).join(' ');
if (nm) { setIfBlank('C51', nm); setIfBlank('C53', 'Owner'); setIfBlank('C59', nm); }
var tin = gv('C9'); if (tin) setIfBlank('C55', tin);
setIfBlank('C63', 'Filipino');
} catch (e) {}
}
function readSettings_(ss) {
var sheet = ss.getSheetByName('Settings');
if (!sheet) return {};
var helpers = ss.getSheetByName('_Helpers');
var fyMonth = 1;
if (helpers) {
var rawFy = helpers.getRange('E13').getValue();
if (typeof rawFy === 'number' && rawFy >= 1 && rawFy <= 12) fyMonth = rawFy;
}
function v(a){ return sheet.getRange(a).getValue(); }
function s(a){ return String(v(a) || ''); }
var addr1 = s('C39'), addr2 = s('C41');
var address = addr1 + (addr2 ? ', ' + addr2 : '');
return {
businessName: v('C25') || '',
tradeName: v('C25') || '',
tin: s('C9'),
address: address,
preparedBy: v('C59') || '',
regType: v('C7') || '',
registrationType: v('C7') || '',
taxableYear: v('C33') || '',
fyStartMonth: fyMonth,
rdo: s('C13'),
contactNumber: s('C45'),
email: v('C47') || '',
zipCode: s('C43'),
dateOfBirth: (function(){var d=v('C61');if(d instanceof Date){return Utilities.formatDate(d,Session.getScriptTimeZone(),'yyyy-MM-dd');}return String(d||'');})(),
citizenship: v('C63') || '',
principalActivity: v('C29') || '',
taxpayerClass: s('C5'),
branchCode: s('C11'),
registeredNameNonInd: s('C17'),
ownerLast: s('C19'),
ownerFirst: s('C21'),
ownerMiddle: s('C23'),
category: v('C31') || '',
repName: s('C51'),
repTitle: s('C53'),
repTin: s('C55'),
addr1: addr1,
addr2: addr2,
dateOfIncorporation: (function(){var d=v('C75');if(d instanceof Date){return Utilities.formatDate(d,Session.getScriptTimeZone(),'yyyy-MM-dd');}return String(d||'');})(),
secRegNo: s('C77'),
authorizedShares: v('C79') || '',
parValue: v('C81') || '',
issuedShares: v('C83') || '',
booksStartDate: (function(){var d=v('C87');if(d instanceof Date){return Utilities.formatDate(d,Session.getScriptTimeZone(),'yyyy-MM-dd');}return String(d||'');})()
};
}
function readCOA_(ss) {
var sheet = ss.getSheetByName('Chart_of_Accounts');
if (!sheet) return [];
var lastRow = sheet.getLastRow();
if (lastRow < 7) return [];
var data = sheet.getRange(7, 2, lastRow - 6, 9).getValues();
var accounts = [];
for (var i = 0; i < data.length; i++) {
var code = data[i][0];
if (code === '' || code === null) continue;
accounts.push({
code: String(code), title: data[i][1] || '', type: data[i][2] || '',
fsGroup: data[i][3] || '', normal: data[i][4] || '', active: data[i][5] || '',
taxLine: data[i][6] || '', noteGroup: data[i][7] || '', ptTaxLine: data[i][8] || ''
});
}
return accounts;
}
function readGJ_(ss, sheetName) {
var sheet = ss.getSheetByName(sheetName || 'General_Journal');
if (!sheet) return [];
var lastRow = sheet.getLastRow();
if (lastRow < 7) return [];
var numRows = lastRow - 6;
var rawData = sheet.getRange(7, 2, numRows, 8).getValues();
var entries = [];
for (var r = 0; r < rawData.length; r++) {
var date = rawData[r][1];
if (date === '' || date === null) continue;
var debit = rawData[r][5];
var credit = rawData[r][6];
entries.push({
entryId: String(rawData[r][0] || ''),
date: dateToISO_(date),
ref: String(rawData[r][2] || ''),
desc: rawData[r][3] || '',
account: rawData[r][4] || '',
debit: (typeof debit === 'number') ? debit : (parseFloat(debit) || 0),
credit: (typeof credit === 'number') ? credit : (parseFloat(credit) || 0),
status: rawData[r][7] || '',
sourceRow: 7 + r
});
}
return entries;
}
function dateToISO_(d) {
if (d instanceof Date) {
return Utilities.formatDate(d, Session.getScriptTimeZone(), 'yyyy-MM-dd');
}
return String(d);
}
function readTaggedBook_(ss, sheetName) {
var EMPTY = { vatTags: [], headers: [], drCr: [], entries: [] };
var sheet = ss.getSheetByName(sheetName);
if (!sheet) return EMPTY;
var TAG_ROW = 5, TITLE_ROW = 6, DRCR_ROW = 7, FIRST_DATA_ROW = 8;
var lastRow = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
if (lastRow < FIRST_DATA_ROW || lastCol < 2) return EMPTY;
var width = lastCol - 1;
var tagRow = sheet.getRange(TAG_ROW, 2, 1, width).getValues()[0];
var titleRow = sheet.getRange(TITLE_ROW, 2, 1, width).getValues()[0];
var drcrRow = sheet.getRange(DRCR_ROW, 2, 1, width).getValues()[0];
var meta = {};
var amtCols = [];
for (var i = 0; i < width; i++) {
var dc = String(drcrRow[i] == null ? '' : drcrRow[i]).trim().toUpperCase();
if (dc === 'DR' || dc === 'CR') {
amtCols.push({
idx: i,
header: String(titleRow[i] == null ? '' : titleRow[i]).trim(),
tag: String(tagRow[i] == null ? '' : tagRow[i]).trim(),
drcr: dc
});
continue;
}
var field = classifyHeader_(titleRow[i]);
if (field && !(field in meta)) meta[field] = i;
}
if (amtCols.length === 0) return EMPTY;
var vatTags = amtCols.map(function (a) { return a.tag; });
var headers = amtCols.map(function (a) { return a.header; });
var drCr = amtCols.map(function (a) { return a.drcr; });
function mv(row, field) { return (field in meta) ? row[meta[field]] : ''; }
var entries = [];
var numRows = lastRow - (FIRST_DATA_ROW - 1);
if (numRows > 0) {
var raw = sheet.getRange(FIRST_DATA_ROW, 2, numRows, width).getValues();
for (var r = 0; r < raw.length; r++) {
var row = raw[r];
var date = mv(row, 'date');
if (date === '' || date === null) continue;
var regName = mv(row, 'regName'), last = mv(row, 'lastName'),
first = mv(row, 'firstName'), mid = mv(row, 'middleName');
var nm = String(regName == null ? '' : regName).trim();
if (!nm) {
nm = [last, first, mid]
.map(function (x) { return String(x == null ? '' : x).trim(); })
.filter(function (x) { return x; })
.join(' ').trim();
}
var amounts = amtCols.map(function (a) {
var v = row[a.idx];
return (typeof v === 'number') ? v : (parseFloat(v) || 0);
});
entries.push({
status: mv(row, 'status') || '',
entryId: String(mv(row, 'entryId') || ''),
date: dateToISO_(date),
ref: String(mv(row, 'ref') || ''),
tin: String(mv(row, 'tin') || ''),
branchCode: String(mv(row, 'branchCode') || ''),
regName: String(regName || ''),
lastName: String(last || ''),
firstName: String(first || ''),
middleName: String(mid || ''),
addr1: String(mv(row, 'addr1') || ''),
addr2: String(mv(row, 'addr2') || ''),
desc: mv(row, 'desc') || '',
name: nm,
sourceRow: FIRST_DATA_ROW + r,
amounts: amounts
});
}
}
var amtIdx = amtCols.map(function (a) { return a.idx + 2; });
var metaIdx = {};
for (var mk in meta) metaIdx[mk] = meta[mk] + 2;
return { vatTags: vatTags, headers: headers, drCr: drCr, entries: entries, amtIdx: amtIdx, metaIdx: metaIdx };
}
function classifyHeader_(label) {
var s = String(label == null ? '' : label).replace(/[\r\n]+/g, ' ').trim().toLowerCase();
if (!s) return null;
if (s.indexOf('status') === 0) return 'status';
if (s.indexOf('date') === 0) return 'date';
if (s.indexOf('reference') === 0 || s === 'ref') return 'ref';
if (s.indexOf('tin') === 0) return 'tin';
if (s.indexOf('branch') === 0) return 'branchCode';
if (s.indexOf('registered name') === 0 || s.indexOf('reg name') === 0) return 'regName';
if (s.indexOf('last name') === 0) return 'lastName';
if (s.indexOf('first name') === 0) return 'firstName';
if (s.indexOf('middle name') === 0) return 'middleName';
if (s.indexOf('substreet') === 0 || s.indexOf('address line 1') === 0 || s.indexOf('address 1') === 0) return 'addr1';
if (s.indexOf('district') === 0 || s.indexOf('address line 2') === 0 || s.indexOf('address 2') === 0) return 'addr2';
if (s.indexOf('description') === 0 || s === 'desc') return 'desc';
if (s.indexOf('entry id') >= 0 || /^(cr|cd|sj|pj|gj)\s*#/.test(s)) return 'entryId';
return null;
}
/* Email FS feature removed June 19, 2026 for OAuth verification (spreadsheets-only scope).
Backed up in _email_feature_backup_June19.gs; re-add in a later verification round. */
/* ═══════════════════════════════════════════
WRITE-BACK: saveTransaction (Phase 0+1)
Writes ONE transaction row into a book sheet (CRB/CDB/SJ/PJ) on the SoT, in
the exact header-driven layout, then the client re-reads via apiRefresh.
- LockService prevents concurrent-write races.
- Re-reads the live book layout (row 6 headers + row 7 DR/CR) so amounts land
in the correct tagged columns - never trusts client column positions.
- Auto Entry ID (nextEntryId_), Date written as a real Date, balanced check.
payload p = { book, date:'YYYY-MM-DD', ref, name, desc, status,
lines:[{account, side:'DR'|'CR', amount}] }
═══════════════════════════════════════════ */
function bookSheetName_(book) {
var map = { CRB: 'Cash_Receipts_Book', CDB: 'Cash_Disbursements_Book', SJ: 'SJ', PJ: 'PJ' };
return map[String(book || '').toUpperCase()] || '';
}
/* Create a new amount column at colAbs: copy format + data-validation from a sibling
amount column (srcAbs) for rows 5..maxR, clear the copied data values, then set
row5 = VAT tag, row6 = account header, row7 = DR/CR. Append a grid column if needed. */
function ntxnCreateCol_(sh, colAbs, srcAbs, tag, account, side, maxR) {
if (colAbs > sh.getMaxColumns()) sh.insertColumnsAfter(sh.getMaxColumns(), colAbs - sh.getMaxColumns());
if (srcAbs && srcAbs !== colAbs && maxR > 5) {
sh.getRange(5, srcAbs, maxR - 4, 1).copyTo(sh.getRange(5, colAbs, maxR - 4, 1)); // formats + validation + values
if (maxR >= 8) sh.getRange(8, colAbs, maxR - 7, 1).clearContent(); // clear sibling's data values
}
sh.getRange(5, colAbs).setValue(tag || '');
sh.getRange(6, colAbs).setValue(account);
sh.getRange(7, colAbs).setValue(side);
}
/* AUTO-BOOTSTRAP a blank book: lay down the standard data-entry header block (row 6, cols
B..N) so the very first saved entry creates the layout instead of failing with
"No amount columns". Idempotent - writes only when no meta headers exist; populated books
are left untouched. Amount columns then begin at column O. */
function bookEnsureHeaders_(sh) {
var STD = ['Status','Entry ID','Date','Reference','TIN','Branch Code','Registered Name','Last Name','First Name','Middle Name','Address 1','Address 2','Description'];
if (sh.getMaxColumns() < 15) sh.insertColumnsAfter(sh.getMaxColumns(), 15 - sh.getMaxColumns());
var cur = sh.getRange(6, 2, 1, 13).getValues()[0];
for (var i = 0; i < cur.length; i++) { if (classifyHeader_(cur[i])) return false; } // already has a layout
var hdr = []; for (var h = 0; h < STD.length; h++) hdr.push(STD[h]);
sh.getRange(6, 2, 1, 13).setValues([hdr]);
return true;
}
/* ── Account-name resolver (tolerant matching) ───────────────────────────────
Resolve a typed account title to its canonical COA title, tolerating
differences in CASE, extra/non-breaking SPACES, and SINGULAR/PLURAL (trailing
"s") - but only when the match is UNAMBIGUOUS. Returns {title:canonical} or
{error:true, suggestion:closest}. Prevents false "account not in COA" errors
(e.g. "Accounts Receivable" vs "Accounts Receivables") without mis-matching
genuinely different accounts. */
function coaResolveMap_(coa) {
var exact = {}, norm = {}, base = {}, titles = [];
function n1(s){ return String(s==null?'':s).replace(/ /g,' ').trim().toLowerCase().replace(/\s+/g,' '); }
function nb(s){ return n1(s).replace(/s$/,''); }
for (var i=0;i<coa.length;i++){ var t=String(coa[i].title==null?'':coa[i].title).trim(); if(!t) continue; titles.push(t);
exact[t]=t;
var a=n1(t); norm[a]=(norm.hasOwnProperty(a)&&norm[a]!==t)?'__AMB__':t;
var b=nb(t); if(!base[b])base[b]=[]; if(base[b].indexOf(t)<0)base[b].push(t);
}
return { exact:exact, norm:norm, base:base, titles:titles, n1:n1, nb:nb };
}
function coaResolve_(M, acct) {
var a = String(acct==null?'':acct).trim();
if (!a) return { error:true, suggestion:'' };
if (M.exact[a]) return { title:a };
var na = M.n1(a); if (M.norm.hasOwnProperty(na) && M.norm[na] !== '__AMB__') return { title:M.norm[na] };
var ba = M.nb(a); if (M.base[ba] && M.base[ba].length === 1) return { title:M.base[ba][0] };
var sug = (M.base[ba] && M.base[ba].length) ? M.base[ba][0] : '';
if (!sug) { var fw=na.split(' ')[0]; for (var i=0;i<M.titles.length;i++){ if (M.n1(M.titles[i]).split(' ')[0]===fw){ sug=M.titles[i]; break; } } }
return { error:true, suggestion:sug };
}
function coaResolveErr_(acct, sug){ return { __error: 'Account not in Chart of Accounts: "' + acct + '".' + (sug ? ' Did you mean "' + sug + '"?' : ' Add it to the COA first.') }; }
function saveTransaction(ssId, p) {
var lock = LockService.getUserLock(); // per-user lock: each user works on their own copy (Execute as: User accessing), so a global script lock would needlessly serialize everyone
try {
if (!ssId) return { __error: 'No spreadsheet id. Re-deploy the launcher and Generate Report once.' };
if (!p || !p.book) return { __error: 'No book specified.' };
var sheetName = bookSheetName_(p.book);
if (!sheetName) return { __error: 'Unknown book: ' + p.book };
var lines = (p.lines || []).filter(function (l) { return l && l.account && (parseFloat(l.amount) || 0) !== 0; });
if (lines.length < 2) return { __error: 'Need at least two lines (a debit and a credit).' };
try { lock.waitLock(15000); } catch (e) { return { __error: 'The workbook is busy - please try again.' }; }
var ss = SpreadsheetApp.openById(ssId);
var sh = ss.getSheetByName(sheetName);
if (!sh) return { __error: 'Book sheet not found: ' + sheetName };
bookEnsureHeaders_(sh); // blank book -> auto-create the standard header block (Issue A)
var lastCol = sh.getLastColumn();
if (lastCol < 2) return { __error: 'Book layout invalid (' + sheetName + ').' };
var maxR = sh.getMaxRows();
var width = lastCol - 1; // from col B
var tagRow = sh.getRange(5, 2, 1, width).getValues()[0];
var titleRow = sh.getRange(6, 2, 1, width).getValues()[0];
var drcrRow = sh.getRange(7, 2, 1, width).getValues()[0];
var meta = {}, amtCols = [], lastAmtRel = -1;
for (var i = 0; i < width; i++) {
var dc = String(drcrRow[i] == null ? '' : drcrRow[i]).trim().toUpperCase();
if (dc === 'DR' || dc === 'CR') { amtCols.push({ idx: i, header: String(titleRow[i] == null ? '' : titleRow[i]).trim(), tag: String(tagRow[i] == null ? '' : tagRow[i]).trim(), drcr: dc }); lastAmtRel = i; continue; }
var f = classifyHeader_(titleRow[i]); if (f && !(f in meta)) meta[f] = i;
}
var srcAbs = (lastAmtRel >= 0) ? lastAmtRel + 2 : 0; // sibling to copy format/validation from (0 = blank book, no sibling) (Issue A)
// validate accounts exist in COA
var M = null; try { var coa = readCOA_(ss); M = coaResolveMap_(coa); } catch (e) { M = null; }
// resolve each line to a column (creating one if needed)
var freePtr = (lastAmtRel >= 0) ? lastAmtRel + 1 : (('desc' in meta) ? meta.desc + 1 : 13), created = []; // blank book: amounts start after the Description meta col (Issue A)
function findFree_() {
while (freePtr < width && (String(titleRow[freePtr] == null ? '' : titleRow[freePtr]).trim() !== '' || String(drcrRow[freePtr] == null ? '' : drcrRow[freePtr]).trim() !== '')) freePtr++;
if (freePtr >= width) { width = freePtr + 1; titleRow[freePtr] = ''; drcrRow[freePtr] = ''; tagRow[freePtr] = ''; }
return freePtr;
}
var place = [], totalDr = 0, totalCr = 0;
for (var li = 0; li < lines.length; li++) {
var ln = lines[li], amt = Math.round((parseFloat(ln.amount) || 0) * 100) / 100, side = String(ln.side).toUpperCase();
var acct = String(ln.account).trim(), tag = String(ln.vatTag || '').trim();
if (M) { var rr = coaResolve_(M, acct); if (rr.error) return coaResolveErr_(acct, rr.suggestion); acct = rr.title; }
var col = -1;
for (var c = 0; c < amtCols.length; c++) { var hT = (M ? (coaResolve_(M, amtCols[c].header).title || amtCols[c].header) : amtCols[c].header); if (hT === acct && amtCols[c].drcr === side && (!tag || amtCols[c].tag === tag)) { col = amtCols[c].idx; break; } }
if (col < 0) { // auto-create a column (header + DR/CR + VAT tag)
var fc = findFree_();
ntxnCreateCol_(sh, fc + 2, srcAbs, tag, acct, side, maxR);
titleRow[fc] = acct; drcrRow[fc] = side; tagRow[fc] = tag;
amtCols.push({ idx: fc, header: acct, tag: tag, drcr: side });
created.push({ account: acct, side: side, tag: tag, col: fc + 2 });
col = fc; freePtr = fc + 1;
}
place.push({ col: col, amt: amt });
if (side === 'DR') totalDr += amt; else totalCr += amt;
}
if (Math.abs(Math.round((totalDr - totalCr) * 100) / 100) > 0.01) return { __error: 'Not balanced: DR ' + totalDr.toFixed(2) + ' vs CR ' + totalCr.toFixed(2) + '.' };
// next Entry ID (scan existing)
var prefix = ({ CRB: 'CR', CDB: 'CD', SJ: 'SJ', PJ: 'PJ' })[String(p.book).toUpperCase()] || '';
var nextId = '';
if ('entryId' in meta) {
var lr = sh.getLastRow(), ids = [];
if (lr >= 8) { var iv = sh.getRange(8, meta.entryId + 2, lr - 7, 1).getValues(); for (var r = 0; r < iv.length; r++) { var v = String(iv[r][0] || '').trim(); if (v) ids.push(v); } }
nextId = nextEntryId_(ids, prefix);
}
// build row at final width
var finalWidth = width; for (var pc = 0; pc < place.length; pc++) if (place[pc].col + 1 > finalWidth) finalWidth = place[pc].col + 1;
if (finalWidth < 13) finalWidth = 13;
var rowVals = []; for (var z = 0; z < finalWidth; z++) rowVals.push('');
for (var pp = 0; pp < place.length; pp++) rowVals[place[pp].col] = place[pp].amt;
function setMeta(field, val) { if (field in meta) rowVals[meta[field]] = val; }
setMeta('status', p.status || 'Posted');
setMeta('entryId', nextId);
setMeta('ref', String(p.ref || ''));
setMeta('desc', String(p.desc || ''));
var _isIndiv = !!(String(p.lastName || '').trim() || String(p.firstName || '').trim()); // individual if name parts given -> Registered Name column stays blank (either/or per BIR layout)
setMeta('regName', _isIndiv ? '' : String(p.name || '').trim());
setMeta('tin', String(p.tin || '').trim());
setMeta('branchCode', String(p.branchCode || '').trim());
setMeta('lastName', _isIndiv ? String(p.lastName || '').trim() : '');
setMeta('firstName', _isIndiv ? String(p.firstName || '').trim() : '');
setMeta('middleName', _isIndiv ? String(p.middleName || '').trim() : '');
setMeta('addr1', String(p.addr1 || '').trim());
setMeta('addr2', String(p.addr2 || '').trim());
if ('date' in meta && /^\d{4}-\d{2}-\d{2}$/.test(String(p.date || ''))) {
var dp = String(p.date).split('-');
rowVals[meta.date] = new Date(parseInt(dp[0], 10), parseInt(dp[1], 10) - 1, parseInt(dp[2], 10));
}
// True next empty row: scan the Date column from row 8 for the first blank (getLastRow over-counts).
var keyRel = ('date' in meta) ? meta.date : (('entryId' in meta) ? meta.entryId : (('status' in meta) ? meta.status : amtCols[0].idx));
var lastRow2 = sh.getLastRow();
var nextRow = 0;
if (lastRow2 >= 8) {
var keyVals = sh.getRange(8, keyRel + 2, lastRow2 - 7, 1).getValues();
for (var k2 = 0; k2 < keyVals.length; k2++) { if (String(keyVals[k2][0] == null ? '' : keyVals[k2][0]).trim() === '') { nextRow = 8 + k2; break; } }
}
if (!nextRow) nextRow = Math.max(8, lastRow2 + 1);
// TIN/branch as text
if ('tin' in meta && rowVals[meta.tin]) sh.getRange(nextRow, meta.tin + 2).setNumberFormat('@');
if ('branchCode' in meta && rowVals[meta.branchCode]) sh.getRange(nextRow, meta.branchCode + 2).setNumberFormat('@');
sh.getRange(nextRow, 2, 1, finalWidth).setValues([rowVals]);
SpreadsheetApp.flush();
return { ok: true, book: p.book, sheet: sheetName, row: nextRow, entryId: nextId, dr: Math.round(totalDr * 100) / 100, created: created };
} catch (e) {
return { __error: (e && e.message) ? e.message : String(e) };
} finally {
try { lock.releaseLock(); } catch (e) {}
}
}
/* General Journal writer: one row per leg, all legs share one Entry ID (JE-####).
GJ layout cols B-I: Entry ID | Date | Reference No | Description | Account Title | Debit | Credit | Status. Data from row 7. */
function saveJournal(ssId, p) {
var lock = LockService.getUserLock();
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
var lines = (p.lines || []).filter(function (l) { return l && l.account && (parseFloat(l.amount) || 0) !== 0; });
if (lines.length < 2) return { __error: 'A journal needs at least two lines.' };
try { lock.waitLock(15000); } catch (e) { return { __error: 'The workbook is busy - please try again.' }; }
var ss = SpreadsheetApp.openById(ssId);
var sh = ss.getSheetByName('General_Journal');
if (!sh) return { __error: 'General_Journal sheet not found.' };
var M = null; try { var coa = readCOA_(ss); M = coaResolveMap_(coa); } catch (e) { M = null; }
var dr = 0, cr = 0;
for (var v = 0; v < lines.length; v++) {
var s = String(lines[v].side).toUpperCase(), a = Math.round((parseFloat(lines[v].amount) || 0) * 100) / 100, acct = String(lines[v].account).trim();
if (M) { var rrj = coaResolve_(M, acct); if (rrj.error) return coaResolveErr_(acct, rrj.suggestion); lines[v].account = rrj.title; }
if (s === 'DR') dr += a; else cr += a;
}
if (Math.abs(Math.round((dr - cr) * 100) / 100) > 0.01) return { __error: 'Not balanced: DR ' + dr.toFixed(2) + ' vs CR ' + cr.toFixed(2) + '.' };
// next JE id: scan col B for JE-#### only
var lastRow = sh.getLastRow(), jeIds = [];
if (lastRow >= 7) { var bv = sh.getRange(7, 2, lastRow - 6, 1).getValues(); for (var r = 0; r < bv.length; r++) { var id = String(bv[r][0] || '').trim(); if (/^JE-?\d+$/i.test(id)) jeIds.push(id); } }
var entryId = nextEntryId_(jeIds, 'JE');
// next data row: first blank Date (col C = 3) from row 7
var nextRow = 0;
if (lastRow >= 7) { var cvals = sh.getRange(7, 3, lastRow - 6, 1).getValues(); for (var k = 0; k < cvals.length; k++) { if (String(cvals[k][0] == null ? '' : cvals[k][0]).trim() === '') { nextRow = 7 + k; break; } } }
if (!nextRow) nextRow = Math.max(7, lastRow + 1);
var dateObj = ''; if (/^\d{4}-\d{2}-\d{2}$/.test(String(p.date || ''))) { var d2 = String(p.date).split('-'); dateObj = new Date(parseInt(d2[0], 10), parseInt(d2[1], 10) - 1, parseInt(d2[2], 10)); }
var out = [];
for (var li = 0; li < lines.length; li++) {
var ln = lines[li], sd = String(ln.side).toUpperCase(), amt = Math.round((parseFloat(ln.amount) || 0) * 100) / 100;
out.push([entryId, dateObj, String(p.ref || ''), String(p.desc || ''), String(ln.account).trim(), sd === 'DR' ? amt : '', sd === 'CR' ? amt : '', p.status || 'Posted']);
}
sh.getRange(nextRow, 2, out.length, 8).setValues(out);
SpreadsheetApp.flush();
return { ok: true, book: 'GJ', sheet: 'General_Journal', row: nextRow, entryId: entryId, legs: out.length, dr: Math.round(dr * 100) / 100 };
} catch (e) {
return { __error: (e && e.message) ? e.message : String(e) };
} finally {
try { lock.releaseLock(); } catch (e) {}
}
}
/* ============================================================
EDIT / VOID an existing entry (in-place, keeps Entry ID).
getEntryForEdit -> read row meta + legs for the editor.
updateTransaction -> overwrite the same row(s).
voidTransaction -> set Status = Void (or back to Posted).
============================================================ */
function entryFmtDate_(v) {
if (v instanceof Date) { var y = v.getFullYear(), m = ('0' + (v.getMonth() + 1)).slice(-2), d = ('0' + v.getDate()).slice(-2); return y + '-' + m + '-' + d; }
var s = String(v == null ? '' : v).trim();
var mm = s.match(/^(\d{4})-(\d{2})-(\d{2})/); if (mm) return mm[1] + '-' + mm[2] + '-' + mm[3];
return s;
}
function entryParseColumnar_(sh) {
var lastCol = sh.getLastColumn(); if (lastCol < 2) return null;
var width = lastCol - 1;
var tagRow = sh.getRange(5, 2, 1, width).getValues()[0];
var titleRow = sh.getRange(6, 2, 1, width).getValues()[0];
var drcrRow = sh.getRange(7, 2, 1, width).getValues()[0];
var meta = {}, amtCols = [], lastAmtRel = -1;
for (var i = 0; i < width; i++) {
var dc = String(drcrRow[i] == null ? '' : drcrRow[i]).trim().toUpperCase();
if (dc === 'DR' || dc === 'CR') { amtCols.push({ idx: i, header: String(titleRow[i] == null ? '' : titleRow[i]).trim(), tag: String(tagRow[i] == null ? '' : tagRow[i]).trim(), drcr: dc }); lastAmtRel = i; continue; }
var f = classifyHeader_(titleRow[i]); if (f && !(f in meta)) meta[f] = i;
}
return { width: width, tagRow: tagRow, titleRow: titleRow, drcrRow: drcrRow, meta: meta, amtCols: amtCols, lastAmtRel: lastAmtRel };
}
/* Find the sheet row for an entryId: trust hintRow if its id matches, else scan. startRow=8 columnar, 7 GJ. idRel=relative entryId col. */
function entryResolveRow_(sh, idRel, entryId, hintRow, startRow) {
entryId = String(entryId || '').trim();
if (hintRow && hintRow >= startRow) {
var hv = String(sh.getRange(hintRow, idRel + 2).getValue() || '').trim();
if (hv && hv === entryId) return hintRow;
}
if (!entryId) return 0;
var lr = sh.getLastRow(); if (lr < startRow) return 0;
var ids = sh.getRange(startRow, idRel + 2, lr - startRow + 1, 1).getValues();
for (var r = 0; r < ids.length; r++) { if (String(ids[r][0] || '').trim() === entryId) return startRow + r; }
return 0;
}
function getEntryForEdit(ssId, p) {
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
if (!p || !p.entryId) return { __error: 'No entry id.' };
var ss = SpreadsheetApp.openById(ssId);
var src = String(p.source || '').toUpperCase();
if (src === 'GJ') {
var gsh = ss.getSheetByName('General_Journal');
if (!gsh) return { __error: 'General_Journal sheet not found.' };
var glr = gsh.getLastRow(); if (glr < 7) return { __error: 'No journal rows.' };
var gv = gsh.getRange(7, 2, glr - 6, 8).getValues(); // B..I: id,date,ref,desc,acct,dr,cr,status
var legs = [], rows = [], meta0 = null;
for (var r = 0; r < gv.length; r++) {
if (String(gv[r][0] || '').trim() !== String(p.entryId).trim()) continue;
if (!meta0) meta0 = { date: entryFmtDate_(gv[r][1]), ref: String(gv[r][2] || ''), desc: String(gv[r][3] || ''), status: String(gv[r][7] || 'Posted') };
var dr = parseFloat(gv[r][5]) || 0, cr = parseFloat(gv[r][6]) || 0;
legs.push({ account: String(gv[r][4] || '').trim(), side: dr > 0 ? 'DR' : 'CR', amount: dr > 0 ? dr : cr });
rows.push(7 + r);
}
if (!legs.length) return { __error: 'Entry not found - refresh and try again.' };
return { ok: true, source: 'GJ', entryId: p.entryId, rows: rows, firstRow: rows[0], legs: legs, date: meta0.date, ref: meta0.ref, desc: meta0.desc, status: meta0.status, party: { name: '', tin: '', branch: '', last: '', first: '', middle: '', addr1: '', addr2: '' } };
}
var sheetName = bookSheetName_(src);
if (!sheetName) return { __error: 'Unknown book: ' + src };
var sh = ss.getSheetByName(sheetName);
if (!sh) return { __error: 'Book sheet not found: ' + sheetName };
var L = entryParseColumnar_(sh); if (!L || L.lastAmtRel < 0) return { __error: 'Book layout invalid (' + sheetName + ').' };
if (!('entryId' in L.meta)) return { __error: 'This book has no Entry ID column - cannot locate the entry.' };
var row = entryResolveRow_(sh, L.meta.entryId, p.entryId, p.row, 8);
if (!row) return { __error: 'Entry not found - refresh and try again.' };
var rv = sh.getRange(row, 2, 1, L.width).getValues()[0];
var legs2 = [];
for (var a = 0; a < L.amtCols.length; a++) { var ac = L.amtCols[a], val = parseFloat(rv[ac.idx]); if (!val || Math.abs(val) < 0.005) continue; legs2.push({ account: ac.header, side: ac.drcr, amount: Math.round(val * 100) / 100, tag: ac.tag }); }
function gm(f) { return (f in L.meta) ? String(rv[L.meta[f]] == null ? '' : rv[L.meta[f]]).trim() : ''; }
return {
ok: true, source: src, sheet: sheetName, row: row, entryId: p.entryId, legs: legs2,
date: ('date' in L.meta) ? entryFmtDate_(rv[L.meta.date]) : '', ref: gm('ref'), desc: gm('desc'), status: gm('status') || 'Posted',
party: { name: gm('regName'), tin: gm('tin'), branch: gm('branchCode'), last: gm('lastName'), first: gm('firstName'), middle: gm('middleName'), addr1: gm('addr1'), addr2: gm('addr2') }
};
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
}
function updateTransaction(ssId, p) {
var lock = LockService.getUserLock();
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
if (!p || !p.entryId) return { __error: 'No entry id.' };
var src = String(p.source || p.book || '').toUpperCase();
var lines = (p.lines || []).filter(function (l) { return l && l.account && (parseFloat(l.amount) || 0) !== 0; });
if (lines.length < 2) return { __error: 'Need at least two lines (a debit and a credit).' };
var dr0 = 0, cr0 = 0;
for (var q = 0; q < lines.length; q++) { var a0 = Math.round((parseFloat(lines[q].amount) || 0) * 100) / 100; if (String(lines[q].side).toUpperCase() === 'DR') dr0 += a0; else cr0 += a0; }
if (Math.abs(Math.round((dr0 - cr0) * 100) / 100) > 0.01) return { __error: 'Not balanced: DR ' + dr0.toFixed(2) + ' vs CR ' + cr0.toFixed(2) + '.' };
try { lock.waitLock(15000); } catch (e) { return { __error: 'The workbook is busy - please try again.' }; }
var ss = SpreadsheetApp.openById(ssId);
var M = null; try { var coa = readCOA_(ss); M = coaResolveMap_(coa); } catch (e) { M = null; }
if (src === 'GJ') {
var gsh = ss.getSheetByName('General_Journal'); if (!gsh) return { __error: 'General_Journal sheet not found.' };
var glr = gsh.getLastRow(), oldRows = [];
if (glr >= 7) { var idv = gsh.getRange(7, 2, glr - 6, 1).getValues(); for (var r = 0; r < idv.length; r++) if (String(idv[r][0] || '').trim() === String(p.entryId).trim()) oldRows.push(7 + r); }
if (!oldRows.length) return { __error: 'Entry not found - refresh and try again.' };
for (var v = 0; v < lines.length; v++) { var acn = String(lines[v].account).trim(); if (M) { var rru = coaResolve_(M, acn); if (rru.error) return coaResolveErr_(acn, rru.suggestion); lines[v].account = rru.title; } }
var dObj = ''; if (/^\d{4}-\d{2}-\d{2}$/.test(String(p.date || ''))) { var d2 = String(p.date).split('-'); dObj = new Date(parseInt(d2[0], 10), parseInt(d2[1], 10) - 1, parseInt(d2[2], 10)); }
var out = [];
for (var li = 0; li < lines.length; li++) { var ln = lines[li], sd = String(ln.side).toUpperCase(), amt = Math.round((parseFloat(ln.amount) || 0) * 100) / 100; out.push([String(p.entryId).trim(), dObj, String(p.ref || ''), String(p.desc || ''), String(ln.account).trim(), sd === 'DR' ? amt : '', sd === 'CR' ? amt : '', 'Posted']); }
var firstRow = oldRows[0];
var nReuse = Math.min(out.length, oldRows.length);
// reuse existing rows one-by-one (safe even if non-contiguous), do NOT overrun into other entries
for (var w = 0; w < nReuse; w++) gsh.getRange(oldRows[w], 2, 1, 8).setValues([out[w]]);
if (out.length > oldRows.length) {
// append the extra legs at the first truly-blank GJ row (by Date col C=3)
var glr2 = gsh.getLastRow(), appendRow = 0;
if (glr2 >= 7) { var cv = gsh.getRange(7, 3, glr2 - 6, 1).getValues(); for (var k = 0; k < cv.length; k++) { if (String(cv[k][0] == null ? '' : cv[k][0]).trim() === '') { appendRow = 7 + k; break; } } }
if (!appendRow) appendRow = Math.max(7, glr2 + 1);
var extra = out.slice(oldRows.length);
gsh.getRange(appendRow, 2, extra.length, 8).setValues(extra);
} else if (out.length < oldRows.length) {
for (var ob = out.length; ob < oldRows.length; ob++) gsh.getRange(oldRows[ob], 2, 1, 8).clearContent();
}
SpreadsheetApp.flush();
return { ok: true, book: 'GJ', sheet: 'General_Journal', row: firstRow, entryId: p.entryId, legs: out.length, dr: Math.round(dr0 * 100) / 100 };
}
var sheetName = bookSheetName_(src); if (!sheetName) return { __error: 'Unknown book: ' + src };
var sh = ss.getSheetByName(sheetName); if (!sh) return { __error: 'Book sheet not found: ' + sheetName };
var L = entryParseColumnar_(sh); if (!L || L.lastAmtRel < 0) return { __error: 'Book layout invalid (' + sheetName + ').' };
if (!('entryId' in L.meta)) return { __error: 'This book has no Entry ID column.' };
var row = entryResolveRow_(sh, L.meta.entryId, p.entryId, p.row, 8);
if (!row) return { __error: 'Entry not found - refresh and try again.' };
var maxR = sh.getMaxRows(), srcAbs = L.lastAmtRel + 2, width = L.width;
var freePtr = L.lastAmtRel + 1, created = [];
function findFree_() { while (freePtr < width && (String(L.titleRow[freePtr] == null ? '' : L.titleRow[freePtr]).trim() !== '' || String(L.drcrRow[freePtr] == null ? '' : L.drcrRow[freePtr]).trim() !== '')) freePtr++; if (freePtr >= width) { width = freePtr + 1; L.titleRow[freePtr] = ''; L.drcrRow[freePtr] = ''; L.tagRow[freePtr] = ''; } return freePtr; }
var place = [];
for (var li2 = 0; li2 < lines.length; li2++) {
var ln2 = lines[li2], amt2 = Math.round((parseFloat(ln2.amount) || 0) * 100) / 100, side2 = String(ln2.side).toUpperCase();
var acct2 = String(ln2.account).trim(), tag2 = String(ln2.vatTag || ln2.tag || '').trim();
if (M) { var rr2 = coaResolve_(M, acct2); if (rr2.error) return coaResolveErr_(acct2, rr2.suggestion); acct2 = rr2.title; }
var col = -1;
for (var c2 = 0; c2 < L.amtCols.length; c2++) { var h2 = (M ? (coaResolve_(M, L.amtCols[c2].header).title || L.amtCols[c2].header) : L.amtCols[c2].header); if (h2 === acct2 && L.amtCols[c2].drcr === side2 && (!tag2 || L.amtCols[c2].tag === tag2)) { col = L.amtCols[c2].idx; break; } }
if (col < 0) { var fc = findFree_(); ntxnCreateCol_(sh, fc + 2, srcAbs, tag2, acct2, side2, maxR); L.titleRow[fc] = acct2; L.drcrRow[fc] = side2; L.tagRow[fc] = tag2; L.amtCols.push({ idx: fc, header: acct2, tag: tag2, drcr: side2 }); created.push({ account: acct2, side: side2, tag: tag2, col: fc + 2 }); col = fc; freePtr = fc + 1; }
place.push({ col: col, amt: amt2 });
}
var finalWidth = width; for (var pc = 0; pc < place.length; pc++) if (place[pc].col + 1 > finalWidth) finalWidth = place[pc].col + 1;
if (finalWidth < 13) finalWidth = 13;
var rowVals = []; for (var z = 0; z < finalWidth; z++) rowVals.push('');
for (var pp = 0; pp < place.length; pp++) rowVals[place[pp].col] = place[pp].amt;
function setMeta(field, val) { if (field in L.meta) rowVals[L.meta[field]] = val; }
setMeta('status', 'Posted');
setMeta('entryId', String(p.entryId).trim());
setMeta('ref', String(p.ref || ''));
setMeta('desc', String(p.desc || ''));
var _isIndiv = !!(String(p.lastName || '').trim() || String(p.firstName || '').trim()); // individual if name parts given -> Registered Name column stays blank (either/or per BIR layout)
setMeta('regName', _isIndiv ? '' : String(p.name || '').trim());
setMeta('tin', String(p.tin || '').trim());
setMeta('branchCode', String(p.branchCode || '').trim());
setMeta('lastName', _isIndiv ? String(p.lastName || '').trim() : '');
setMeta('firstName', _isIndiv ? String(p.firstName || '').trim() : '');
setMeta('middleName', _isIndiv ? String(p.middleName || '').trim() : '');
setMeta('addr1', String(p.addr1 || '').trim());
setMeta('addr2', String(p.addr2 || '').trim());
if ('date' in L.meta && /^\d{4}-\d{2}-\d{2}$/.test(String(p.date || ''))) { var dp = String(p.date).split('-'); rowVals[L.meta.date] = new Date(parseInt(dp[0], 10), parseInt(dp[1], 10) - 1, parseInt(dp[2], 10)); }
if ('tin' in L.meta && rowVals[L.meta.tin]) sh.getRange(row, L.meta.tin + 2).setNumberFormat('@');
if ('branchCode' in L.meta && rowVals[L.meta.branchCode]) sh.getRange(row, L.meta.branchCode + 2).setNumberFormat('@');
sh.getRange(row, 2, 1, finalWidth).setValues([rowVals]);
SpreadsheetApp.flush();
return { ok: true, book: src, sheet: sheetName, row: row, entryId: p.entryId, dr: Math.round(dr0 * 100) / 100, created: created };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
finally { try { lock.releaseLock(); } catch (e) {} }
}
function voidTransaction(ssId, p) {
var lock = LockService.getUserLock();
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
if (!p || !p.entryId) return { __error: 'No entry id.' };
var newStatus = (String(p.status || 'Void').toLowerCase() === 'posted') ? 'Posted' : 'Void';
var src = String(p.source || '').toUpperCase();
try { lock.waitLock(15000); } catch (e) { return { __error: 'The workbook is busy - please try again.' }; }
var ss = SpreadsheetApp.openById(ssId);
if (src === 'GJ') {
var gsh = ss.getSheetByName('General_Journal'); if (!gsh) return { __error: 'General_Journal sheet not found.' };
var glr = gsh.getLastRow(), n = 0;
if (glr >= 7) { var idv = gsh.getRange(7, 2, glr - 6, 1).getValues(); for (var r = 0; r < idv.length; r++) if (String(idv[r][0] || '').trim() === String(p.entryId).trim()) { gsh.getRange(7 + r, 9).setValue(newStatus); n++; } }
if (!n) return { __error: 'Entry not found - refresh and try again.' };
SpreadsheetApp.flush();
return { ok: true, book: 'GJ', entryId: p.entryId, status: newStatus, rows: n };
}
var sheetName = bookSheetName_(src); if (!sheetName) return { __error: 'Unknown book: ' + src };
var sh = ss.getSheetByName(sheetName); if (!sh) return { __error: 'Book sheet not found: ' + sheetName };
var L = entryParseColumnar_(sh); if (!L) return { __error: 'Book layout invalid.' };
if (!('entryId' in L.meta) || !('status' in L.meta)) return { __error: 'This book has no Entry ID / Status column.' };
var row = entryResolveRow_(sh, L.meta.entryId, p.entryId, p.row, 8);
if (!row) return { __error: 'Entry not found - refresh and try again.' };
sh.getRange(row, L.meta.status + 2).setValue(newStatus);
SpreadsheetApp.flush();
return { ok: true, book: src, entryId: p.entryId, status: newStatus, row: row };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
finally { try { lock.releaseLock(); } catch (e) {} }
}
/* ============================================================
CHART OF ACCOUNTS + SETTINGS management (WebApp-side CRUD).
COA sheet 'Chart_of_Accounts' data row 7+, cols B..J:
B code | C title | D type | E fsGroup | F normal | G active | H taxLine | I noteGroup | J ptTaxLine
============================================================ */
var COA_SHEET_ = 'Chart_of_Accounts';
/* titleLower -> usage count across books + GJ (an account is "used" if referenced anywhere). */
function coaUsage_(ss) {
var used = {};
function bump(t, n) { t = String(t || '').trim().toLowerCase(); if (t) used[t] = (used[t] || 0) + (n || 1); }
var books = ['Cash_Receipts_Book', 'Cash_Disbursements_Book', 'Sales_Journal', 'Purchase_Journal'];
for (var b = 0; b < books.length; b++) {
var sh = ss.getSheetByName(books[b]); if (!sh) continue;
var lc = sh.getLastColumn(), lr = sh.getLastRow(); if (lc < 2 || lr < 8) continue;
var width = lc - 1;
var titleRow = sh.getRange(6, 2, 1, width).getValues()[0];
var drcrRow = sh.getRange(7, 2, 1, width).getValues()[0];
var dataVals = sh.getRange(8, 2, lr - 7, width).getValues();
for (var c = 0; c < width; c++) {
var dc = String(drcrRow[c] || '').trim().toUpperCase(); if (dc !== 'DR' && dc !== 'CR') continue;
var title = String(titleRow[c] || '').trim(); if (!title) continue;
var n = 0; for (var r = 0; r < dataVals.length; r++) { var v = parseFloat(dataVals[r][c]); if (v && Math.abs(v) > 0.0001) n++; }
if (n > 0) bump(title, n);
}
}
var gsh = ss.getSheetByName('General_Journal');
if (gsh) { var glr = gsh.getLastRow(); if (glr >= 7) { var gv = gsh.getRange(7, 6, glr - 6, 1).getValues(); for (var r2 = 0; r2 < gv.length; r2++) bump(gv[r2][0]); } }
return used;
}
function coaFindRow_(sh, code) {
var lr = sh.getLastRow(); if (lr < 7) return 0;
var codes = sh.getRange(7, 2, lr - 6, 1).getValues();
for (var r = 0; r < codes.length; r++) { if (String(codes[r][0] || '').trim() === String(code).trim()) return 7 + r; }
return 0;
}
/* Normalize each classification value to the EXACT casing/spelling in the SoT validation list
(e.g. "Yes" -> "YES") so the restored dropdown never flags the cell as invalid. */
function coaNormRow_(sh, rowVals) {
var lists; try { lists = coaClassLists_(sh, readCOA_(sh.getParent())); } catch (e) { lists = {}; }
var map = { 2: 'type', 3: 'fsGroup', 4: 'normal', 5: 'active', 6: 'taxLine', 7: 'noteGroup', 8: 'ptTaxLine' };
for (var idx in map) {
var key = map[idx], val = String(rowVals[idx] || '').trim(); if (!val) continue;
var list = lists[key] || [];
for (var i = 0; i < list.length; i++) { if (String(list[i]).trim().toLowerCase() === val.toLowerCase()) { rowVals[idx] = list[i]; break; } }
}
return rowVals;
}
/* Write a COA row B..J bypassing strict ("reject input") data validation:
save -> clear -> write -> restore the dropdowns, so a programmatic write never gets blocked. */
function coaWriteRow_(sh, row, rowVals) {
rowVals = coaNormRow_(sh, rowVals);
var rng = sh.getRange(row, 2, 1, 9);
var dvs = null; try { dvs = rng.getDataValidations(); } catch (e) {}
try { rng.clearDataValidations(); } catch (e) {}
rng.setValues([rowVals]);
if (dvs) { try { rng.setDataValidations(dvs); } catch (e) {} }
}
/* Read the dropdown options for a COA classification column straight from its data-validation
(VALUE_IN_LIST literal, or VALUE_IN_RANGE pointing at a _Helpers range). Probes rows 7 & 8. */
function coaDvList_(sh, col, lastRow) {
var rows = [7]; if (lastRow >= 8) rows.push(8);
for (var i = 0; i < rows.length; i++) {
var dv; try { dv = sh.getRange(rows[i], col).getDataValidation(); } catch (e) { dv = null; }
if (!dv) continue;
var t = dv.getCriteriaType(), vals = dv.getCriteriaValues();
if (t === SpreadsheetApp.DataValidationCriteria.VALUE_IN_LIST) return vals[0] || [];
if (t === SpreadsheetApp.DataValidationCriteria.VALUE_IN_RANGE) {
var rng = vals[0]; if (rng) { var v = rng.getValues(), out = []; for (var r = 0; r < v.length; r++) for (var c = 0; c < v[r].length; c++) { var s = String(v[r][c] || '').trim(); if (s) out.push(s); } return out; }
}
}
return [];
}
/* Per-classification option lists = SoT data-validation values, merged with distinct values already in the COA. */
function coaClassLists_(sh, coa) {
var cols = { type: 4, fsGroup: 5, normal: 6, active: 7, taxLine: 8, noteGroup: 9, ptTaxLine: 10 };
var lr = sh.getLastRow(), out = {};
for (var key in cols) {
var dv = coaDvList_(sh, cols[key], lr), seen = {}, list = [];
for (var i = 0; i < dv.length; i++) { var s = String(dv[i] || '').trim(); if (s && !seen[s.toLowerCase()]) { seen[s.toLowerCase()] = 1; list.push(s); } }
for (var j = 0; j < coa.length; j++) { var v = String(coa[j][key] || '').trim(); if (v && !seen[v.toLowerCase()]) { seen[v.toLowerCase()] = 1; list.push(v); } }
out[key] = list;
}
return out;
}
function coaList(ssId) {
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
var ss = SpreadsheetApp.openById(ssId);
var sh = ss.getSheetByName(COA_SHEET_); if (!sh) return { __error: 'Chart_of_Accounts sheet not found.' };
var coa = readCOA_(ss), used = coaUsage_(ss);
var groups = {}, types = {};
for (var i = 0; i < coa.length; i++) {
var u = used[String(coa[i].title).trim().toLowerCase()] || 0; coa[i].used = u > 0; coa[i].txnCount = u;
if (coa[i].noteGroup) groups[String(coa[i].noteGroup).trim()] = 1;
if (coa[i].type) types[String(coa[i].type).trim()] = 1;
}
return { ok: true, accounts: coa, lists: coaClassLists_(sh, coa), noteGroups: Object.keys(groups).sort(), types: Object.keys(types).sort() };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
}
function addAccount(ssId, p) {
var lock = LockService.getUserLock();
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
var code = String(p.code || '').trim(), title = String(p.title || '').trim();
if (!code || !title) return { __error: 'Code and account title are required.' };
if (!String(p.type || '').trim()) return { __error: 'Account type is required.' };
var normal = String(p.normal || '').trim(); if (normal !== 'Debit' && normal !== 'Credit') return { __error: 'Normal balance must be Debit or Credit.' };
try { lock.waitLock(15000); } catch (e) { return { __error: 'The workbook is busy - please try again.' }; }
var ss = SpreadsheetApp.openById(ssId);
var sh = ss.getSheetByName(COA_SHEET_); if (!sh) return { __error: 'Chart_of_Accounts sheet not found.' };
var coa = readCOA_(ss);
for (var i = 0; i < coa.length; i++) {
if (String(coa[i].code).trim() === code) return { __error: 'Code "' + code + '" already exists.' };
if (String(coa[i].title).trim().toLowerCase() === title.toLowerCase()) return { __error: 'Account title "' + title + '" already exists.' };
}
var lr = sh.getLastRow(), nextRow = 0;
if (lr >= 7) { var cv = sh.getRange(7, 2, lr - 6, 1).getValues(); for (var k = 0; k < cv.length; k++) { if (String(cv[k][0] || '').trim() === '') { nextRow = 7 + k; break; } } }
if (!nextRow) nextRow = Math.max(7, lr + 1);
var rowVals = [code, title, String(p.type || '').trim(), String(p.fsGroup || '').trim(), normal, String(p.active || 'Yes').trim() || 'Yes', String(p.taxLine || '').trim(), String(p.noteGroup || '').trim(), String(p.ptTaxLine || '').trim()];
coaWriteRow_(sh, nextRow, rowVals);
SpreadsheetApp.flush();
return { ok: true, code: code, row: nextRow };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
finally { try { lock.releaseLock(); } catch (e) {} }
}
function updateAccount(ssId, p) {
var lock = LockService.getUserLock();
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
var code = String(p.code || '').trim(); if (!code) return { __error: 'No account code.' };
try { lock.waitLock(15000); } catch (e) { return { __error: 'The workbook is busy - please try again.' }; }
var ss = SpreadsheetApp.openById(ssId);
var sh = ss.getSheetByName(COA_SHEET_); if (!sh) return { __error: 'Chart_of_Accounts sheet not found.' };
var row = coaFindRow_(sh, code); if (!row) return { __error: 'Account not found - refresh and try again.' };
var cur = sh.getRange(row, 2, 1, 9).getValues()[0];
var curTitle = String(cur[1] || '').trim();
var newTitle = String(p.title != null ? p.title : curTitle).trim() || curTitle;
var newNormal = String(p.normal || '').trim(); if (newNormal && newNormal !== 'Debit' && newNormal !== 'Credit') return { __error: 'Normal balance must be Debit or Credit.' };
if (newTitle.toLowerCase() !== curTitle.toLowerCase()) {
var used = coaUsage_(ss);
if (used[curTitle.toLowerCase()]) return { __error: 'Cannot rename "' + curTitle + '" - it is already used in the books (' + used[curTitle.toLowerCase()] + ' entries). Create a new account, or deactivate this one instead.' };
var coa = readCOA_(ss);
for (var i = 0; i < coa.length; i++) { if (String(coa[i].title).trim().toLowerCase() === newTitle.toLowerCase() && String(coa[i].code).trim() !== code) return { __error: 'Account title "' + newTitle + '" already exists.' }; }
}
var rowVals = [code, newTitle, String(p.type != null ? p.type : cur[2] || '').trim(), String(p.fsGroup != null ? p.fsGroup : cur[3] || '').trim(), (newNormal || cur[4] || '').trim(), String(p.active != null ? p.active : cur[5] || '').trim(), String(p.taxLine != null ? p.taxLine : cur[6] || '').trim(), String(p.noteGroup != null ? p.noteGroup : cur[7] || '').trim(), String(p.ptTaxLine != null ? p.ptTaxLine : cur[8] || '').trim()];
coaWriteRow_(sh, row, rowVals);
SpreadsheetApp.flush();
return { ok: true, code: code, row: row };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
finally { try { lock.releaseLock(); } catch (e) {} }
}
function setAccountActive(ssId, p) {
var lock = LockService.getUserLock();
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
var code = String(p.code || '').trim(); if (!code) return { __error: 'No account code.' };
try { lock.waitLock(15000); } catch (e) { return { __error: 'The workbook is busy - please try again.' }; }
var ss = SpreadsheetApp.openById(ssId);
var sh = ss.getSheetByName(COA_SHEET_); if (!sh) return { __error: 'Chart_of_Accounts sheet not found.' };
var row = coaFindRow_(sh, code); if (!row) return { __error: 'Account not found.' };
var active = (String(p.active || '').toLowerCase() === 'no' || p.active === false) ? 'No' : 'Yes';
sh.getRange(row, 7).setValue(active);
SpreadsheetApp.flush();
return { ok: true, code: code, active: active };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
finally { try { lock.releaseLock(); } catch (e) {} }
}
function deleteAccount(ssId, p) {
var lock = LockService.getUserLock();
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
var code = String(p.code || '').trim(); if (!code) return { __error: 'No account code.' };
try { lock.waitLock(15000); } catch (e) { return { __error: 'The workbook is busy - please try again.' }; }
var ss = SpreadsheetApp.openById(ssId);
var sh = ss.getSheetByName(COA_SHEET_); if (!sh) return { __error: 'Chart_of_Accounts sheet not found.' };
var row = coaFindRow_(sh, code); if (!row) return { __error: 'Account not found.' };
var title = String(sh.getRange(row, 3).getValue() || '').trim();
var used = coaUsage_(ss);
if (used[title.toLowerCase()]) return { __error: 'Cannot delete "' + title + '" - it has ' + used[title.toLowerCase()] + ' transaction(s). Deactivate it instead (set Active = No).' };
sh.deleteRow(row);
SpreadsheetApp.flush();
return { ok: true, code: code, deleted: true };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
finally { try { lock.releaseLock(); } catch (e) {} }
}
/* Settings field -> cell (mirrors readSettings_). FY month lives in _Helpers!E13. */
var SETTINGS_CELLMAP_ = {
taxpayerClass: 'C5', regType: 'C7', tin: 'C9', branchCode: 'C11', rdo: 'C13',
registeredNameNonInd: 'C17', ownerLast: 'C19', ownerFirst: 'C21', ownerMiddle: 'C23',
businessName: 'C25', principalActivity: 'C29', category: 'C31', taxableYear: 'C33',
addr1: 'C39', addr2: 'C41', zipCode: 'C43', contactNumber: 'C45', email: 'C47',
repName: 'C51', repTitle: 'C53', repTin: 'C55', preparedBy: 'C59',
dateOfBirth: 'C61', citizenship: 'C63',
dateOfIncorporation: 'C75', secRegNo: 'C77', authorizedShares: 'C79', parValue: 'C81', issuedShares: 'C83',
booksStartDate: 'C87'
};
function settingsGet(ssId) {
try { if (!ssId) return { __error: 'No spreadsheet id.' }; var ss = SpreadsheetApp.openById(ssId); return { ok: true, settings: readSettings_(ss) }; }
catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
}
/* Read the option list (if any) from a cell's data validation. Returns [] if none. */
function settingsDvOpts_(rng) {
var dv; try { dv = rng.getDataValidation(); } catch (e) { dv = null; }
if (!dv) return [];
var t = dv.getCriteriaType(), vals = dv.getCriteriaValues();
if (t === SpreadsheetApp.DataValidationCriteria.VALUE_IN_LIST) return vals[0] || [];
if (t === SpreadsheetApp.DataValidationCriteria.VALUE_IN_RANGE && vals[0]) { var v = vals[0].getValues(), out = []; for (var r = 0; r < v.length; r++) for (var c = 0; c < v[r].length; c++) { var s = String(v[r][c] || '').trim(); if (s) out.push(s); } return out; }
return [];
}
/* Auto-detect which Settings fields have a dropdown list in the SoT -> {field: [options]}. */
function settingsLists(ssId) {
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
var ss = SpreadsheetApp.openById(ssId); var sh = ss.getSheetByName('Settings'); if (!sh) return { __error: 'Settings sheet not found.' };
var out = {};
for (var f in SETTINGS_CELLMAP_) { var opts = settingsDvOpts_(sh.getRange(SETTINGS_CELLMAP_[f])); if (opts && opts.length) out[f] = opts; }
return { ok: true, lists: out };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
}
/* Write a Settings cell: normalize to the exact DV option (e.g. "Vat" -> "VAT Registered"),
and bypass strict validation (save -> clear -> write -> restore) so it never gets blocked. */
function settingsWriteCell_(sh, cell, val) {
var rng = sh.getRange(cell);
var dv = null; try { dv = rng.getDataValidation(); } catch (e) {}
if (dv && !(val instanceof Date)) {
var opts = settingsDvOpts_(rng);
var matched = false;
for (var i = 0; i < opts.length; i++) { if (String(opts[i]).trim().toLowerCase() === String(val).trim().toLowerCase()) { val = opts[i]; matched = true; break; } }
if (matched) {
// Value is a valid dropdown option: plain write, keep the validation (and its dropdown
// list) INTACT. Do NOT clear then re-apply the rule - clearing+re-adding it is what blanks
// the dropdown options in the user's already-open Sheet tab until they refresh.
rng.setValue(val);
return;
}
// Unknown value (not in the list): write around the rule, then restore it.
try { rng.clearDataValidations(); } catch (e) {}
rng.setValue(val);
try { rng.setDataValidation(dv); } catch (e) {}
return;
}
rng.setValue(val);
}
function updateSettings(ssId, p) {
var lock = LockService.getUserLock();
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
if (!p || typeof p !== 'object') return { __error: 'No settings.' };
if (p.fyStartMonth != null && String(p.fyStartMonth) !== '') { var fm = parseInt(p.fyStartMonth, 10); if (!(fm >= 1 && fm <= 12)) return { __error: 'Fiscal year start month must be 1-12.' }; }
var dateFields = { dateOfBirth: 1, dateOfIncorporation: 1, booksStartDate: 1 };
for (var df in dateFields) { if (p[df] != null && String(p[df]).trim() !== '' && !/^\d{4}-\d{2}-\d{2}$/.test(String(p[df]).trim())) return { __error: df + ' must be a valid date (YYYY-MM-DD).' }; }
try { lock.waitLock(15000); } catch (e) { return { __error: 'The workbook is busy - please try again.' }; }
var ss = SpreadsheetApp.openById(ssId);
var sh = ss.getSheetByName('Settings'); if (!sh) return { __error: 'Settings sheet not found.' };
var changed = [];
for (var f in SETTINGS_CELLMAP_) {
if (!(f in p)) continue;
var cell = SETTINGS_CELLMAP_[f], val = p[f];
if (dateFields[f] && String(val).trim() !== '') { var dp = String(val).split('-'); val = new Date(parseInt(dp[0], 10), parseInt(dp[1], 10) - 1, parseInt(dp[2], 10)); }
if ((f === 'tin' || f === 'branchCode' || f === 'repTin') && String(p[f]).trim() !== '') sh.getRange(cell).setNumberFormat('@');
settingsWriteCell_(sh, cell, val);
changed.push(f);
}
if (p.fyStartMonth != null && String(p.fyStartMonth) !== '') { var hp = ss.getSheetByName('_Helpers'); if (hp) hp.getRange('E13').setValue(parseInt(p.fyStartMonth, 10)); changed.push('fyStartMonth'); }
SpreadsheetApp.flush();
return { ok: true, changed: changed };
} catch (e) { return { __error: (e && e.message) ? e.message : String(e) }; }
finally { try { lock.releaseLock(); } catch (e) {} }
}
/* Open items for payments. kind 'ar' = unpaid invoices (SJ charges - CRB collections),
'ap' = unpaid bills (PJ charges - CDB payments). Keyed by party + reference, FIFO net.
Returns { items:[{ref, party, date, original, balance}] } (balance > 0). */
function ntxnAccumControl_(book, acctTitle, side) {
var out = {}; // key party|ref -> {party, ref, date, amt}
if (!book || !book.entries) return out;
var idxs = [];
for (var c = 0; c < book.headers.length; c++) { if (String(book.headers[c]).trim() === acctTitle && String(book.drCr[c]).trim().toUpperCase() === side) idxs.push(c); }
if (!idxs.length) return out;
for (var e = 0; e < book.entries.length; e++) {
var en = book.entries[e]; if (en.status && String(en.status).toLowerCase() !== 'posted') continue;
var amt = 0; for (var j = 0; j < idxs.length; j++) amt += (en.amounts && en.amounts[idxs[j]]) || 0;
if (Math.abs(amt) < 0.005) continue;
var party = (en.regName || en.name || '').trim() || '(No Name)';
var ref = String(en.ref || '').trim();
var key = party + '|' + ref;
if (!out[key]) out[key] = { party: party, ref: ref, date: en.date || '', amt: 0 };
out[key].amt += amt;
}
return out;
}
function openItems(ssId, kind) {
try {
if (!ssId) return { __error: 'No spreadsheet id.' };
var ss = SpreadsheetApp.openById(ssId);
var charges, pays, acct;
if (kind === 'ap') {
acct = 'Accounts Payable';
charges = ntxnAccumControl_(readTaggedBook_(ss, 'PJ'), acct, 'CR');
pays = ntxnAccumControl_(readTaggedBook_(ss, 'Cash_Disbursements_Book'), acct, 'DR');
} else {
acct = 'Accounts Receivable';
charges = ntxnAccumControl_(readTaggedBook_(ss, 'SJ'), acct, 'DR');
pays = ntxnAccumControl_(readTaggedBook_(ss, 'Cash_Receipts_Book'), acct, 'CR');
}
// FIFO per party (matches the Subsidiary Ledger): apply each party's total payments to its
// oldest open charges. Charges/pays are keyed party|ref, so a collection with a different
// reference still reduces the party's oldest invoice (previously it was matched by exact ref).
var byParty = {};
for (var k in charges) { if (!charges.hasOwnProperty(k)) continue; var ch = charges[k]; var pA = ch.party || '(No Name)'; if (!byParty[pA]) byParty[pA] = { charges: [], pay: 0 }; byParty[pA].charges.push(ch); }
for (var k2 in pays) { if (!pays.hasOwnProperty(k2)) continue; var py = pays[k2]; var pB = py.party || '(No Name)'; if (!byParty[pB]) byParty[pB] = { charges: [], pay: 0 }; byParty[pB].pay += py.amt; }
var items = [];
for (var pp in byParty) { if (!byParty.hasOwnProperty(pp)) continue; var g = byParty[pp];
g.charges.sort(function (a, b) { if (a.date !== b.date) return a.date < b.date ? -1 : 1; return (a.ref || '') < (b.ref || '') ? -1 : 1; });
var pay = g.pay;
for (var ci = 0; ci < g.charges.length; ci++) { var c0 = g.charges[ci]; var bal = c0.amt;
if (pay > 0.005) { var ap = Math.min(bal, pay); bal = Math.round((bal - ap) * 100) / 100; pay = Math.round((pay - ap) * 100) / 100; }
if (bal > 0.005) items.push({ ref: c0.ref, party: (pp === '(No Name)' ? '' : pp), date: c0.date, original: Math.round(c0.amt * 100) / 100, balance: bal });
}
}
items.sort(function (a, b) { return (a.date < b.date ? -1 : (a.date > b.date ? 1 : 0)); });
return { ok: true, kind: kind, items: items };
} catch (e) {
return { __error: (e && e.message) ? e.message : String(e) };
}
}
/* ═══════════════════════════════════════════
BOOK META (for Bookkeeping Made Easy paste-ready previews)
Per book: headers/drCr/vatTags + absolute column positions (if the
launcher sends amtIdx/metaIdx), next Entry ID, and next empty row.
═══════════════════════════════════════════ */
function nextEntryId_(ids, prefix) {
var bestPrefix = null, bestNum = 0, width = 4;
for (var i = 0; i < ids.length; i++) {
var m = String(ids[i] || '').trim().match(/^(.*?)(\d+)\s*$/);
if (!m) continue;
var n = parseInt(m[2], 10);
if (n >= bestNum) { bestNum = n; bestPrefix = m[1]; width = m[2].length; }
}
if (bestPrefix === null) return prefix + '-0001';
var nx = String(bestNum + 1);
while (nx.length < width) nx = '0' + nx;
return bestPrefix + nx;
}
/* ═══════════════════════════════════════════
CONTACTS DIRECTORY (for New Transaction searchable picker)
Derived from the existing book rows (CRB/CDB/SJ/PJ) - NO separate Contacts
sheet. Identity key = normalized display name + 9-digit TIN, so the same name
with a different TIN shows as separate candidates (user picks the right one).
Within a key, the most-recent non-blank fields win; count + side accumulate.
Included in DATA (doPost + apiRefresh) so the picker is instant and always
fresh - a newly-saved contact reappears in search on the next refresh.
═══════════════════════════════════════════ */
function buildContacts_(data) {
var map = {}, order = [];
function norm(s) { return String(s == null ? '' : s).replace(/\s+/g, ' ').trim().toUpperCase(); }
function tin9(s) { var d = String(s == null ? '' : s).replace(/[^0-9]/g, ''); return d.length > 9 ? d.substring(0, 9) : d; }
function disp(e) {
var rn = String(e.regName || '').trim();
if (rn) return rn;
var ln = String(e.lastName || '').trim(), fn = String(e.firstName || '').trim(), mn = String(e.middleName || '').trim();
if (ln || fn || mn) { var fm = [fn, mn].filter(function (x) { return x; }).join(' '); return ln ? (fm ? ln + ', ' + fm : ln) : fm; }
return String(e.name || '').trim();
}
function nb(cur, val) { var v = String(val == null ? '' : val).trim(); return v ? v : cur; }
function scan(book, side) {
if (!book || !book.entries) return;
for (var i = 0; i < book.entries.length; i++) {
var e = book.entries[i];
var name = disp(e);
if (!name) continue;
var key = norm(name) + '|' + tin9(e.tin);
var d = String(e.date || '');
var c = map[key];
if (!c) {
c = map[key] = { name: name, regName: String(e.regName || '').trim(), last: String(e.lastName || '').trim(),
first: String(e.firstName || '').trim(), middle: String(e.middleName || '').trim(), tin: String(e.tin || '').trim(),
branch: String(e.branchCode || '').trim(), addr1: String(e.addr1 || '').trim(), addr2: String(e.addr2 || '').trim(),
side: side, count: 0, lastDate: d };
order.push(key);
}
c.count++;
if (c.side !== side && c.side !== 'both') c.side = 'both';
if (d >= c.lastDate) {
c.lastDate = d; c.name = name;
c.regName = nb(c.regName, e.regName); c.last = nb(c.last, e.lastName); c.first = nb(c.first, e.firstName);
c.middle = nb(c.middle, e.middleName); c.tin = nb(c.tin, e.tin); c.branch = nb(c.branch, e.branchCode);
c.addr1 = nb(c.addr1, e.addr1); c.addr2 = nb(c.addr2, e.addr2);
} else {
c.regName = c.regName || String(e.regName || '').trim(); c.last = c.last || String(e.lastName || '').trim();
c.first = c.first || String(e.firstName || '').trim(); c.middle = c.middle || String(e.middleName || '').trim();
c.tin = c.tin || String(e.tin || '').trim(); c.branch = c.branch || String(e.branchCode || '').trim();
c.addr1 = c.addr1 || String(e.addr1 || '').trim(); c.addr2 = c.addr2 || String(e.addr2 || '').trim();
}
}
}
scan(data.crb, 'customer'); scan(data.sj, 'customer');
scan(data.cdb, 'supplier'); scan(data.pj, 'supplier');
var out = [];
for (var k = 0; k < order.length; k++) out.push(map[order[k]]);
out.sort(function (a, b) { return b.count - a.count; });
return out;
}
function buildBookMeta_(data) {
function tagged(book, prefix, label) {
book = book || {};
var maxRow = 7, ids = [];
(book.entries || []).forEach(function (e) {
if (e.sourceRow && e.sourceRow > maxRow) maxRow = e.sourceRow;
if (e.entryId) ids.push(e.entryId);
});
return {
label: label, headers: book.headers || [], drCr: book.drCr || [], vatTags: book.vatTags || [],
amtIdx: book.amtIdx || null, metaIdx: book.metaIdx || null,
nextRow: (maxRow + 1 < 8) ? 8 : maxRow + 1, nextId: nextEntryId_(ids, prefix)
};
}
var gjIds = [], gjMax = 6;
(data.gj || []).forEach(function (r) {
if (r.sourceRow && r.sourceRow > gjMax) gjMax = r.sourceRow;
if (r.entryId) gjIds.push(r.entryId);
});
return {
crb: tagged(data.crb, 'CR', 'Cash Receipts Book'),
cdb: tagged(data.cdb, 'CD', 'Cash Disbursements Book'),
sj: tagged(data.sj, 'SJ', 'Sales Journal'),
pj: tagged(data.pj, 'PJ', 'Purchase Journal'),
gj: { label: 'General Journal', nextRow: (gjMax + 1 < 7) ? 7 : gjMax + 1, nextId: nextEntryId_(gjIds, 'JE') }
};
}
/* ═══════════════════════════════════════════
OPTIMIZATION: Minify + Gzip helpers
Toggle via OPTIM_FLAGS at the top of doPost.
═══════════════════════════════════════════ */
var OPTIM_FLAGS = {
minify: true, // strip whitespace+comments from CSS/JS
gzip: false, // gzip+base64 the DATA payload (DIAGNOSTIC: disabled 2026-05-13 to isolate blank-page issue)
lazyChat: true // defer chat engines to a separate fetch
};
function minifyCSS_(s) {
if (!s) return '';
try {
return s
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\s*\n\s*/g, '')
.replace(/\s*([{}:;,>+~])\s*/g, '$1')
.replace(/;}/g, '}')
.trim();
} catch (e) { return s; }
}
function minifyJS_(s) {
if (!s) return '';
try {
var lines = s.split('\n');
var out = [];
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var trimmed = line.replace(/^\s+/, '').replace(/\s+$/, '');
if (!trimmed) continue;
// Skip pure-comment lines (// ... or /* ... */)
if (trimmed.indexOf('//') === 0) continue;
if (trimmed.indexOf('/*') === 0 && trimmed.indexOf('*/') === trimmed.length - 2) continue;
out.push(trimmed);
}
return out.join('\n');
} catch (e) { return s; }
}
function compressDATA_(payload) {
try {
var json = JSON.stringify(payload);
var blob = Utilities.newBlob(json, 'application/json', 'data.json');
var gzipped = Utilities.gzip(blob).getBytes();
return Utilities.base64Encode(gzipped);
} catch (e) {
return null;
}
}
/* Multi-branch (Phase 1): convert the HO/default books (branch "00") plus any
branch-suffixed book sets (data.branchBooks["01"] etc.). Every jeRow carries
a `branch` field. Single-branch clients have no branchBooks -> all rows = "00".
The branch field is inert to existing reports (they ignore it) until the
branch selector + filtering phase wires it in. */
function convertBooksToJE_(data) {
var all = convertOneBranchSet_(data, '00'); // HO / default
if (data.branchBooks) {
for (var bc in data.branchBooks) {
if (!data.branchBooks.hasOwnProperty(bc)) continue;
all = all.concat(convertOneBranchSet_(data.branchBooks[bc], bc));
}
}
return all;
}
function convertOneBranchSet_(d, branch) {
var all = [];
if (!d) return all;
// CRB: tagged when vatTags present (new layout); else old convBook_ (backward-compatible)
if (d.crb && d.crb.entries) all = all.concat(d.crb.vatTags ? convertTaggedBookToJE_(d.crb, 'CRB') : convBook_(d.crb, 'CRB'));
// SJ: credit sales (tagged book)
if (d.sj && d.sj.entries) all = all.concat(convertTaggedBookToJE_(d.sj, 'SJ'));
// CDB: tagged when vatTags present (new purchase layout); else old convBook_ (backward-compatible)
if (d.cdb && d.cdb.entries) all = all.concat(d.cdb.vatTags ? convertTaggedBookToJE_(d.cdb, 'CDB') : convBook_(d.cdb, 'CDB'));
// PJ: credit purchases (tagged book)
if (d.pj && d.pj.entries) all = all.concat(convertTaggedBookToJE_(d.pj, 'PJ'));
if (d.gj) { for (var i = 0; i < d.gj.length; i++) { var g = d.gj[i]; if (g.status !== 'Posted' || !g.account) continue; all.push({source:'GJ',date:g.date,ref:g.ref||'',desc:g.desc||'',entryId:g.entryId||'',account:String(g.account).trim(),debit:g.debit||0,credit:g.credit||0,name:g.name||g.desc||''}); } }
for (var k = 0; k < all.length; k++) all[k].branch = branch;
return all;
}
function convBook_(book, src) {
var entries = [];
for (var r = 0; r < book.entries.length; r++) {
var row = book.entries[r]; if (row.status !== 'Posted') continue;
for (var c = 0; c < book.headers.length; c++) {
var acct = String(book.headers[c]).trim(); if (!acct) continue;
var amt = row.amounts[c] || 0; if (amt === 0) continue;
var dir = String(book.drCr[c]).trim().toUpperCase(); var dr = 0, cr = 0;
if (dir === 'DR') { if (amt > 0) dr = amt; else cr = Math.abs(amt); }
else if (dir === 'CR') { if (amt > 0) cr = amt; else dr = Math.abs(amt); }
entries.push({source:src,date:row.date,ref:row.ref||'',desc:row.desc||'',entryId:'',account:acct,debit:dr,credit:cr,name:row.name||''});
}
}
return entries;
}
/* Voided-only legs (status set & != Posted) for the Transactions register "Show voided"
view. Self-contained — does NOT touch the Posted converters (zero risk to reports). */
function convertBooksToVoided_(data) {
var out = [];
function legsCol(bk, src, branch) {
if (!bk || !bk.entries || !bk.headers || !bk.drCr) return;
for (var r = 0; r < bk.entries.length; r++) {
var row = bk.entries[r]; var st = String(row.status || '').trim();
if (!(st && st.toLowerCase() !== 'posted')) continue;
for (var c = 0; c < bk.headers.length; c++) {
var acct = String(bk.headers[c]).trim(); if (!acct) continue;
var amt = (row.amounts && row.amounts[c]) || 0; if (amt === 0) continue;
var dir = String(bk.drCr[c]).trim().toUpperCase(); var dr = 0, cr = 0;
if (dir === 'DR') { if (amt > 0) dr = amt; else cr = Math.abs(amt); }
else if (dir === 'CR') { if (amt > 0) cr = amt; else dr = Math.abs(amt); }
else continue;
out.push({ source: src, date: row.date, ref: row.ref || '', desc: row.desc || '', entryId: row.entryId || '', account: acct, debit: dr, credit: cr, name: row.name || '', tin: row.tin || '', branchCode: row.branchCode || '', sourceRow: row.sourceRow || '', branch: branch, voided: true });
}
}
}
function oneSet(d, branch) {
if (!d) return;
legsCol(d.crb, 'CRB', branch); legsCol(d.sj, 'SJ', branch);
legsCol(d.cdb, 'CDB', branch); legsCol(d.pj, 'PJ', branch);
if (d.gj) { for (var i = 0; i < d.gj.length; i++) { var g = d.gj[i]; var st = String(g.status || '').trim(); if (!(st && st.toLowerCase() !== 'posted') || !g.account) continue; out.push({ source: 'GJ', date: g.date, ref: g.ref || '', desc: g.desc || '', entryId: g.entryId || '', account: String(g.account).trim(), debit: g.debit || 0, credit: g.credit || 0, name: g.name || g.desc || '', branch: branch, voided: true, sourceRow: g.sourceRow || g.row || '' }); } }
}
oneSet(data, '00');
if (data.branchBooks) { for (var bc in data.branchBooks) { if (!data.branchBooks.hasOwnProperty(bc)) continue; oneSet(data.branchBooks[bc], bc); } }
return out;
}
/* ════════════════════════ SALES-SIDE VAT ENGINE ════════════════════════
Reusable tagged-book engine: CRB + SJ -> JE legs + SSJ + SLS + 2550Q sales
summary + validation. VAT classification is driven ONLY by the VAT Tag row
(never by account name). Keeps convBook_ (CDB) untouched. */
/* JE converter for tagged books (carries tin/branch/sourceRow). */
function convertTaggedBookToJE_(book, src) {
var entries = [];
if (!book || !book.entries) return entries;
for (var r = 0; r < book.entries.length; r++) {
var row = book.entries[r];
if (row.status !== 'Posted') continue;
for (var c = 0; c < book.headers.length; c++) {
var acct = String(book.headers[c]).trim();
if (!acct) continue;
var amt = (row.amounts && row.amounts[c]) || 0;
if (amt === 0) continue;
var dir = String(book.drCr[c]).trim().toUpperCase();
var dr = 0, cr = 0;
if (dir === 'DR') { if (amt > 0) dr = amt; else cr = Math.abs(amt); }
else if (dir === 'CR') { if (amt > 0) cr = amt; else dr = Math.abs(amt); }
else continue;
entries.push({
source: src, date: row.date, ref: row.ref || '', desc: row.desc || '', entryId: row.entryId || '',
account: acct, debit: dr, credit: cr, name: row.name || '',
tin: row.tin || '', branchCode: row.branchCode || '', sourceRow: row.sourceRow || '',
tag: (book.vatTags && book.vatTags[c] != null) ? String(book.vatTags[c]).trim() : ''
});
}
}
return entries;
}
function normalizeVatTag_(raw) {
var t = String(raw == null ? '' : raw).trim().toLowerCase();
if (!t) return 'NONE';
if (t === 'exempt') return 'EXEMPT';
if (t === 'zero-rated' || t === 'zero rated' || t === 'zerorated') return 'ZERO_RATED';
if (t === 'vatable' || t === 'taxable') return 'VATABLE';
if (t === 'output tax' || t === 'output vat') return 'OUTPUT_TAX';
return 'NONE';
}
function normalizeTin_(raw) {
var digits = String(raw == null ? '' : raw).replace(/[^0-9]/g, '');
return digits.length > 9 ? digits.substring(0, 9) : digits;
}
function rnd2_(n) { return Math.round((n || 0) * 100) / 100; }
/* BIRALPHA74 valname: distinct invalid characters in a text field ('' = clean).
Rejects ASCII <=31, " $ & ' , \ ` | (34,36,38,39,44,92,96,124), and
126-163 / 166-208 / 210-240 / 242-255. Allows 164,165,209(N-tilde),241(n-tilde). */
function birBadChars_(s) {
s = String(s == null ? '' : s); var bad = '';
for (var i = 0; i < s.length; i++) {
var a = s.charCodeAt(i);
if (a <= 31 || a === 34 || a === 36 || a === 38 || a === 39 || a === 44 || a === 92 || a === 96 || a === 124 ||
(a >= 126 && a <= 163) || (a >= 166 && a <= 208) || (a >= 210 && a <= 240) || (a >= 242 && a <= 255)) {
var ch = s.charAt(i); if (bad.indexOf(ch) < 0) bad += ch;
}
}
return bad;
}
/* BIRALPHA74 TIN check on normalized 9-digit value. '' = no issue. (222222222 NOT rejected — matches source.) */
/* Server-side mirror of the client DAT writer cleanup (slsSan_): strips
non-printable/high-ASCII, removes " . , | , collapses spaces, uppercases.
Validation runs on THIS (the actual DAT output), so auto-fixable characters
(commas etc.) no longer raise errors; only unfixable ones do. */
function birSanForDat_(s) {
return String(s == null ? '' : s).replace(/[^\x20-\x7E]/g, '').replace(/[".,|]/g, '').replace(/\s+/g, ' ').trim().toUpperCase();
}
function birTinIssue_(rawTin, ownerNorm) {
var n = normalizeTin_(rawTin);
if (!n) return ''; // absent — caller decides if required
if (n.length !== 9) return 'TIN must be 9 digits';
if (/^(0{9}|1{9}|3{9}|4{9}|5{9}|6{9}|7{9}|8{9}|9{9})$/.test(n)) return 'invalid TIN (repeated digits)';
if (ownerNorm && n === ownerNorm) return 'TIN equals owner TIN';
return '';
}
/* A row is sales-reportable only if it has a non-zero amount under a
VAT-tagged column. Non-sales rows (owner investment, loans, AR collection,
CWT, platform fees, Cash, Capital — all blank-tagged) are excluded. */
function extractSalesReportableRows_(book, source) {
var out = [];
if (!book || !book.entries || !book.vatTags) return out;
var tags = book.vatTags.map(normalizeVatTag_);
for (var r = 0; r < book.entries.length; r++) {
var e = book.entries[r];
if (e.status !== 'Posted') continue;
var exempt = 0, zero = 0, vatable = 0, outputVAT = 0;
for (var c = 0; c < tags.length; c++) {
var amt = (e.amounts && e.amounts[c]) || 0;
if (!amt) continue;
// Sales are CR-normal: a DR column = contra-revenue (Sales Discount / Sales Returns) -> subtract from the VAT base.
if (String((book.drCr && book.drCr[c]) || '').trim().toUpperCase() === 'DR') amt = -amt;
var tg = tags[c];
if (tg === 'EXEMPT') exempt += amt;
else if (tg === 'ZERO_RATED') zero += amt;
else if (tg === 'VATABLE') vatable += amt;
else if (tg === 'OUTPUT_TAX') outputVAT += amt;
}
if (exempt === 0 && zero === 0 && vatable === 0 && outputVAT === 0) continue;
out.push({
source: source, date: e.date, taxMonth: String(e.date || '').substring(0, 7), ref: e.ref || '',
regName: e.regName || '', lastName: e.lastName || '', firstName: e.firstName || '', middleName: e.middleName || '',
tin: e.tin || '', normalizedTin: normalizeTin_(e.tin), branchCode: e.branchCode || '',
addr1: e.addr1 || '', addr2: e.addr2 || '', desc: e.desc || '', name: e.name || '',
exempt: rnd2_(exempt), zero: rnd2_(zero), vatable: rnd2_(vatable), outputVAT: rnd2_(outputVAT),
grossTaxable: rnd2_(vatable + outputVAT),
totalInvoice: rnd2_(exempt + zero + vatable + outputVAT),
sourceRow: e.sourceRow || ''
});
}
return out;
}
/* SSJ: one row per sales-reportable transaction (CRB + SJ combined), sorted. */
function buildSSJRows_(salesRows) {
var rows = salesRows.slice();
rows.sort(function (a, b) {
if (a.date !== b.date) return a.date < b.date ? -1 : 1;
return (a.ref || '') < (b.ref || '') ? -1 : 1;
});
return rows;
}
/* SLS: group SSJ by Tax Month + TIN + Branch + Registered Name. */
function buildSLSRows_(ssjRows) {
var map = {}, order = [];
for (var i = 0; i < ssjRows.length; i++) {
var s = ssjRows[i];
var key = [s.taxMonth, s.normalizedTin, s.branchCode, (s.regName || s.name || '')].join('|');
if (!map[key]) {
map[key] = {
taxMonth: s.taxMonth, tin: s.tin, normalizedTin: s.normalizedTin, branchCode: s.branchCode,
regName: s.regName, lastName: s.lastName, firstName: s.firstName, middleName: s.middleName,
addr1: s.addr1, addr2: s.addr2,
exempt: 0, zero: 0, vatable: 0, outputVAT: 0, grossTaxable: 0, totalSales: 0,
sourceCount: 0, sourceRefs: []
};
order.push(key);
}
var g = map[key];
g.exempt += s.exempt; g.zero += s.zero; g.vatable += s.vatable; g.outputVAT += s.outputVAT;
g.sourceCount += 1;
if (s.ref) g.sourceRefs.push(s.ref);
}
var out = [];
for (var k = 0; k < order.length; k++) {
var r = map[order[k]];
r.exempt = rnd2_(r.exempt); r.zero = rnd2_(r.zero); r.vatable = rnd2_(r.vatable); r.outputVAT = rnd2_(r.outputVAT);
r.grossTaxable = rnd2_(r.vatable + r.outputVAT);
r.totalSales = rnd2_(r.exempt + r.zero + r.vatable + r.outputVAT);
r.sourceRefs = r.sourceRefs.join(', ');
out.push(r);
}
return out;
}
function build2550QSalesSummary_(ssjRows) {
var ex = 0, ze = 0, va = 0, ov = 0;
for (var i = 0; i < ssjRows.length; i++) {
ex += ssjRows[i].exempt; ze += ssjRows[i].zero; va += ssjRows[i].vatable; ov += ssjRows[i].outputVAT;
}
return {
totalExemptSales: rnd2_(ex), totalZeroRatedSales: rnd2_(ze), totalVatableSales: rnd2_(va),
totalOutputVAT: rnd2_(ov), totalGrossTaxable: rnd2_(va + ov), totalSales: rnd2_(ex + ze + va + ov)
};
}
/* Validation: row balance + VAT sanity + missing fields + duplicates. */
function validateTaggedSalesBooks_(bookSpecs, ownerNorm) {
var errors = [], warnings = [];
var dupSeen = {};
for (var b = 0; b < bookSpecs.length; b++) {
var book = bookSpecs[b].book, src = bookSpecs[b].source;
if (!book || !book.entries || !book.vatTags) continue;
var tags = book.vatTags.map(normalizeVatTag_);
var drcr = book.drCr.map(function (d) { return String(d || '').trim().toUpperCase(); });
for (var r = 0; r < book.entries.length; r++) {
var e = book.entries[r];
if (e.status !== 'Posted') continue;
var rowLabel = src + ' row ' + (e.sourceRow || '?') + ' (' + (e.ref || 'no ref') + ')';
var dr = 0, cr = 0;
for (var c = 0; c < (e.amounts || []).length; c++) {
var amt = e.amounts[c] || 0; if (!amt) continue;
if (drcr[c] === 'DR') { if (amt > 0) dr += amt; else cr += Math.abs(amt); }
else if (drcr[c] === 'CR') { if (amt > 0) cr += amt; else dr += Math.abs(amt); }
}
if (Math.abs(rnd2_(dr - cr)) > 0.01) {
errors.push({ source: src, row: e.sourceRow, msg: rowLabel + ': not balanced (DR ' + rnd2_(dr) + ' vs CR ' + rnd2_(cr) + ')' });
}
var exempt = 0, zero = 0, vatable = 0, outputVAT = 0;
for (var c2 = 0; c2 < tags.length; c2++) {
var a2 = (e.amounts && e.amounts[c2]) || 0; if (!a2) continue;
if (tags[c2] === 'EXEMPT') exempt += a2;
else if (tags[c2] === 'ZERO_RATED') zero += a2;
else if (tags[c2] === 'VATABLE') vatable += a2;
else if (tags[c2] === 'OUTPUT_TAX') outputVAT += a2;
}
var isSales = (exempt || zero || vatable || outputVAT);
if (!isSales) continue;
if (vatable > 0 && outputVAT === 0) warnings.push({ source: src, row: e.sourceRow, msg: rowLabel + ': VATable sales with no Output VAT' });
if (outputVAT > 0 && vatable === 0) warnings.push({ source: src, row: e.sourceRow, msg: rowLabel + ': Output VAT with no VATable sales' });
if (outputVAT > 0 && (exempt > 0 || zero > 0) && vatable === 0) warnings.push({ source: src, row: e.sourceRow, msg: rowLabel + ': Output VAT on exempt/zero-rated row' });
// SLS (BIRALPHA74): customer TIN is OPTIONAL — flag only if present and malformed.
var slsTinIssue = birTinIssue_(e.tin, ownerNorm);
if (slsTinIssue) errors.push({ source: src, row: e.sourceRow, msg: rowLabel + ': customer ' + slsTinIssue });
// Name "empty" only if reg+last+first+middle+name ALL blank. Such rows are consolidated as VARIOUS CUSTOMERS in the DAT (BIR-valid), so this is informational, not a blocker.
var slsNameAll = String((e.regName || '') + (e.lastName || '') + (e.firstName || '') + (e.middleName || '') + (e.name || '')).trim();
if (!slsNameAll) warnings.push({ source: src, row: e.sourceRow, msg: rowLabel + ': no customer name — will be consolidated as VARIOUS CUSTOMERS in DAT' });
// valname char check — SLS DAT exports only the name fields (customer address is NOT written to SLS detail), so don't char-check addresses here.
// Runs on the SANITIZED value (what the DAT actually contains) — commas/periods/pipes are auto-removed by the writer and no longer flagged.
var slsFields = ['regName', 'lastName', 'firstName', 'middleName'];
for (var sf = 0; sf < slsFields.length; sf++) { var sbc = birBadChars_(birSanForDat_(e[slsFields[sf]])); if (sbc) errors.push({ source: src, row: e.sourceRow, msg: rowLabel + ': invalid character(s) "' + sbc + '" in ' + slsFields[sf] + ' (cannot be auto-removed - please edit)' }); }
if (!String(e.ref || '').trim()) warnings.push({ source: src, row: e.sourceRow, msg: rowLabel + ': no reference/invoice number' });
var totalInv = rnd2_(exempt + zero + vatable + outputVAT);
var dupKey = normalizeTin_(e.tin) + '|' + String(e.ref || '').trim() + '|' + totalInv;
if (e.ref && dupSeen[dupKey]) warnings.push({ source: src, row: e.sourceRow, msg: rowLabel + ': possible duplicate of ' + dupSeen[dupKey] });
else if (e.ref) dupSeen[dupKey] = rowLabel;
}
}
return { errors: errors, warnings: warnings };
}
/* Orchestrator: build the salesVat payload section (never throws). */
function buildSalesVat_(data) {
try {
var crbRows = (data.crb && data.crb.vatTags) ? extractSalesReportableRows_(data.crb, 'CRB') : [];
var sjRows = (data.sj && data.sj.vatTags) ? extractSalesReportableRows_(data.sj, 'SJ') : [];
var ssjRows = buildSSJRows_(crbRows.concat(sjRows));
var slsRows = buildSLSRows_(ssjRows);
var vatSummary = build2550QSalesSummary_(ssjRows);
var ownerNorm = normalizeTin_((data.settings && data.settings.tin) || '');
var validation = validateTaggedSalesBooks_([
{ book: data.crb, source: 'CRB' },
{ book: data.sj, source: 'SJ' }
], ownerNorm);
return { ssjRows: ssjRows, slsRows: slsRows, vatSummary: vatSummary, validation: validation };
} catch (err) {
return { ssjRows: [], slsRows: [], vatSummary: {}, validation: { errors: [{ msg: 'Sales VAT engine error: ' + err.message }], warnings: [] } };
}
}
/* ════════════════════════ PURCHASE-SIDE VAT ENGINE ═════════════════════
CDB + PJ -> SPJ + SLP + 2550Q purchase summary + validation. VAT
classification driven ONLY by VAT Tag row. Tags: Exempt / Zero-Rated /
VATable Services / VATable Capital Goods / VATable Goods Other than
Capital Goods / Input Tax. */
function normalizePurchaseVatTag_(raw) {
var t = String(raw == null ? '' : raw).trim().toLowerCase();
if (!t) return 'NONE';
if (t === 'exempt') return 'EXEMPT';
if (t === 'zero-rated' || t === 'zero rated' || t === 'zerorated') return 'ZERO_RATED';
if (t === 'vatable services' || t === 'taxable services' || t === 'services') return 'VATABLE_SERVICES';
if (t === 'vatable capital goods' || t === 'capital goods') return 'VATABLE_CAPITAL';
if (t === 'vatable goods other than capital goods' || t === 'goods other than capital goods' || t === 'goods other') return 'VATABLE_GOODS';
if (t === 'input tax' || t === 'input vat') return 'INPUT_TAX';
return 'NONE';
}
/* A row is purchase-reportable only if it has a non-zero amount under a
VAT-tagged column. Non-purchase rows (Cash, A/P, EWT payable, etc. — blank
tag) are excluded automatically. */
function extractPurchaseReportableRows_(book, source) {
var out = [];
if (!book || !book.entries || !book.vatTags) return out;
var tags = book.vatTags.map(normalizePurchaseVatTag_);
for (var r = 0; r < book.entries.length; r++) {
var e = book.entries[r];
if (e.status !== 'Posted') continue;
var exempt = 0, zero = 0, services = 0, capital = 0, goods = 0, inputTax = 0;
for (var c = 0; c < tags.length; c++) {
var amt = (e.amounts && e.amounts[c]) || 0;
if (!amt) continue;
// Purchases are DR-normal: a CR column = contra (Purchase Returns / Discounts) -> subtract from the input VAT base.
if (String((book.drCr && book.drCr[c]) || '').trim().toUpperCase() === 'CR') amt = -amt;
var tg = tags[c];
if (tg === 'EXEMPT') exempt += amt;
else if (tg === 'ZERO_RATED') zero += amt;
else if (tg === 'VATABLE_SERVICES') services += amt;
else if (tg === 'VATABLE_CAPITAL') capital += amt;
else if (tg === 'VATABLE_GOODS') goods += amt;
else if (tg === 'INPUT_TAX') inputTax += amt;
}
if (exempt === 0 && zero === 0 && services === 0 && capital === 0 && goods === 0 && inputTax === 0) continue;
var taxableNet = rnd2_(services + capital + goods);
out.push({
source: source, date: e.date, taxMonth: String(e.date || '').substring(0, 7), ref: e.ref || '',
regName: e.regName || '', lastName: e.lastName || '', firstName: e.firstName || '', middleName: e.middleName || '',
tin: e.tin || '', normalizedTin: normalizeTin_(e.tin), branchCode: e.branchCode || '',
addr1: e.addr1 || '', addr2: e.addr2 || '', desc: e.desc || '', name: e.name || '',
exempt: rnd2_(exempt), zero: rnd2_(zero), services: rnd2_(services), capital: rnd2_(capital), goods: rnd2_(goods),
inputTax: rnd2_(inputTax), taxableNet: taxableNet,
grossTaxable: rnd2_(taxableNet + inputTax),
totalPurchases: rnd2_(exempt + zero + taxableNet),
totalInvoice: rnd2_(exempt + zero + taxableNet + inputTax),
sourceRow: e.sourceRow || ''
});
}
return out;
}
function buildSPJRows_(rows) {
var r = rows.slice();
r.sort(function (a, b) { if (a.date !== b.date) return a.date < b.date ? -1 : 1; return (a.ref || '') < (b.ref || '') ? -1 : 1; });
return r;
}
function buildSLPRows_(spjRows) {
var map = {}, order = [];
for (var i = 0; i < spjRows.length; i++) {
var s = spjRows[i];
var key = [s.taxMonth, s.normalizedTin, s.branchCode, (s.regName || s.name || '')].join('|');
if (!map[key]) {
map[key] = { taxMonth: s.taxMonth, tin: s.tin, normalizedTin: s.normalizedTin, branchCode: s.branchCode,
regName: s.regName, lastName: s.lastName, firstName: s.firstName, middleName: s.middleName, addr1: s.addr1, addr2: s.addr2,
exempt: 0, zero: 0, services: 0, capital: 0, goods: 0, inputTax: 0, taxableNet: 0, grossTaxable: 0, totalPurchases: 0,
sourceCount: 0, sourceRefs: [] };
order.push(key);
}
var g = map[key];
g.exempt += s.exempt; g.zero += s.zero; g.services += s.services; g.capital += s.capital; g.goods += s.goods; g.inputTax += s.inputTax;
g.sourceCount += 1; if (s.ref) g.sourceRefs.push(s.ref);
}
var out = [];
for (var k = 0; k < order.length; k++) {
var g2 = map[order[k]];
g2.exempt = rnd2_(g2.exempt); g2.zero = rnd2_(g2.zero); g2.services = rnd2_(g2.services); g2.capital = rnd2_(g2.capital); g2.goods = rnd2_(g2.goods); g2.inputTax = rnd2_(g2.inputTax);
g2.taxableNet = rnd2_(g2.services + g2.capital + g2.goods);
g2.grossTaxable = rnd2_(g2.taxableNet + g2.inputTax);
g2.totalPurchases = rnd2_(g2.exempt + g2.zero + g2.taxableNet);
g2.sourceRefs = g2.sourceRefs.join(', ');
out.push(g2);
}
return out;
}
function build2550QPurchaseSummary_(spjRows) {
var ex = 0, ze = 0, sv = 0, ca = 0, go = 0, it = 0;
for (var i = 0; i < spjRows.length; i++) { var s = spjRows[i]; ex += s.exempt; ze += s.zero; sv += s.services; ca += s.capital; go += s.goods; it += s.inputTax; }
var net = rnd2_(sv + ca + go);
return { totalExempt: rnd2_(ex), totalZeroRated: rnd2_(ze), totalServices: rnd2_(sv), totalCapital: rnd2_(ca), totalGoods: rnd2_(go),
totalInputTax: rnd2_(it), totalTaxableNet: net, totalGrossTaxable: rnd2_(net + it), totalPurchases: rnd2_(ex + ze + net) };
}
function validatePurchaseBooks_(bookSpecs, ownerNorm) {
var errors = [], warnings = [], dupSeen = {};
for (var b = 0; b < bookSpecs.length; b++) {
var book = bookSpecs[b].book, src = bookSpecs[b].source;
if (!book || !book.entries || !book.vatTags) continue;
var tags = book.vatTags.map(normalizePurchaseVatTag_);
var drcr = book.drCr.map(function (d) { return String(d || '').trim().toUpperCase(); });
for (var r = 0; r < book.entries.length; r++) {
var e = book.entries[r];
if (e.status !== 'Posted') continue;
var rowLabel = src + ' row ' + (e.sourceRow || '?') + ' (' + (e.ref || 'no ref') + ')';
var dr = 0, cr = 0;
for (var c = 0; c < (e.amounts || []).length; c++) { var amt = e.amounts[c] || 0; if (!amt) continue; if (drcr[c] === 'DR') { if (amt > 0) dr += amt; else cr += Math.abs(amt); } else if (drcr[c] === 'CR') { if (amt > 0) cr += amt; else dr += Math.abs(amt); } }
if (Math.abs(rnd2_(dr - cr)) > 0.01) errors.push({ source: src, row: e.sourceRow, msg: rowLabel + ': not balanced (DR ' + rnd2_(dr) + ' vs CR ' + rnd2_(cr) + ')' });
var taxable = 0, inputTax = 0, anyVat = 0;
for (var c2 = 0; c2 < tags.length; c2++) { var a2 = (e.amounts && e.amounts[c2]) || 0; if (!a2) continue; var t2 = tags[c2]; if (t2 === 'VATABLE_SERVICES' || t2 === 'VATABLE_CAPITAL' || t2 === 'VATABLE_GOODS') taxable += a2; if (t2 === 'INPUT_TAX') inputTax += a2; if (t2 !== 'NONE') anyVat += a2; }
if (!anyVat) continue;
if (taxable > 0 && inputTax === 0) warnings.push({ source: src, row: e.sourceRow, msg: rowLabel + ': VATable purchase with no Input Tax' });
if (inputTax > 0 && taxable === 0) warnings.push({ source: src, row: e.sourceRow, msg: rowLabel + ': Input Tax with no VATable purchase' });
// SLP (BIRALPHA74): supplier TIN is REQUIRED (BIR errors if blank).
var slpTinNorm = normalizeTin_(e.tin);
if (!slpTinNorm) errors.push({ source: src, row: e.sourceRow, msg: rowLabel + ': supplier TIN is required (SLP)' });
else { var slpTinIssue = birTinIssue_(e.tin, ownerNorm); if (slpTinIssue) errors.push({ source: src, row: e.sourceRow, msg: rowLabel + ': supplier ' + slpTinIssue }); }
// SLP: supplier name REQUIRED (reg+last+first+middle+name all blank = error).
var slpNameAll = String((e.regName || '') + (e.lastName || '') + (e.firstName || '') + (e.middleName || '') + (e.name || '')).trim();
if (!slpNameAll) errors.push({ source: src, row: e.sourceRow, msg: rowLabel + ': supplier name is required (SLP)' });
// SLP: supplier address REQUIRED (addr1+addr2 both blank = error).
var slpAddrAll = String((e.addr1 || '') + (e.addr2 || '')).trim();
if (!slpAddrAll) errors.push({ source: src, row: e.sourceRow, msg: rowLabel + ': supplier address is required (SLP)' });
// valname char check on each present text field (BIRALPHA74).
// Runs on the SANITIZED value (what the DAT actually contains) — commas/periods/pipes are auto-removed by the writer and no longer flagged.
var slpFields = ['regName', 'lastName', 'firstName', 'middleName', 'addr1', 'addr2'];
for (var pf = 0; pf < slpFields.length; pf++) { var pbc = birBadChars_(birSanForDat_(e[slpFields[pf]])); if (pbc) errors.push({ source: src, row: e.sourceRow, msg: rowLabel + ': invalid character(s) "' + pbc + '" in ' + slpFields[pf] + ' (cannot be auto-removed - please edit)' }); }
if (!String(e.ref || '').trim()) warnings.push({ source: src, row: e.sourceRow, msg: rowLabel + ': no reference/invoice number' });
var dupKey = normalizeTin_(e.tin) + '|' + String(e.ref || '').trim();
if (e.ref && dupSeen[dupKey]) warnings.push({ source: src, row: e.sourceRow, msg: rowLabel + ': possible duplicate of ' + dupSeen[dupKey] }); else if (e.ref) dupSeen[dupKey] = rowLabel;
}
}
return { errors: errors, warnings: warnings };
}
function buildPurchaseVat_(data) {
try {
var cdbRows = (data.cdb && data.cdb.vatTags) ? extractPurchaseReportableRows_(data.cdb, 'CDB') : [];
var pjRows = (data.pj && data.pj.vatTags) ? extractPurchaseReportableRows_(data.pj, 'PJ') : [];
var spjRows = buildSPJRows_(cdbRows.concat(pjRows));
var slpRows = buildSLPRows_(spjRows);
var vatSummary = build2550QPurchaseSummary_(spjRows);
var ownerNorm = normalizeTin_((data.settings && data.settings.tin) || '');
var validation = validatePurchaseBooks_([{ book: data.cdb, source: 'CDB' }, { book: data.pj, source: 'PJ' }], ownerNorm);
return { spjRows: spjRows, slpRows: slpRows, vatSummary: vatSummary, validation: validation };
} catch (err) {
return { spjRows: [], slpRows: [], vatSummary: {}, validation: { errors: [{ msg: 'Purchase VAT engine error: ' + err.message }], warnings: [] } };
}
}
function buildHTML_(payload) {
var bizName = payload.settings.businessName || 'EC TAXPH';
var genDate = ''; try { genDate = Utilities.formatDate(new Date(payload.generatedAt), 'Asia/Manila', 'MMMM dd, yyyy hh:mm a'); } catch(e) {}
// Build CSS (optionally minified)
var css = getCSS_();
if (OPTIM_FLAGS.minify) css = minifyCSS_(css);
// Build JS (optionally minified)
var js = getJS_();
// Inject the client canonical lists from the single server source (REF_LISTS_).
js = "var COA_FIX_=" + JSON.stringify(REF_LISTS_.coa) + ";var SET_FIX_=" + JSON.stringify(REF_LISTS_.settings) + ";" + js;
if (OPTIM_FLAGS.minify) js = minifyJS_(js);
// Append sales-side JS (SSJ book + SLS RELIEF + DAT views)
var salesJs = getSalesJS_();
if (OPTIM_FLAGS.minify) salesJs = minifyJS_(salesJs);
js = js + "\n" + salesJs;
// Append 2551Q (Quarterly Percentage Tax) JS
var ptJs = getReturns2551JS_();
if (OPTIM_FLAGS.minify) ptJs = minifyJS_(ptJs);
js = js + "\n" + ptJs;
// Append Subsidiary Ledgers + Aging JS
var slJs = getSubLedgerJS_();
if (OPTIM_FLAGS.minify) slJs = minifyJS_(slJs);
js = js + "\n" + slJs;
// Append New Transaction (write-back) JS
var ntJs = getNewTxnJS_();
if (OPTIM_FLAGS.minify) ntJs = minifyJS_(ntJs);
js = js + "\n" + ntJs;
// Build DATA payload (optionally gzipped)
var dataLiteral;
if (OPTIM_FLAGS.gzip) {
var b64 = compressDATA_(payload);
if (b64) {
dataLiteral = '{__gz:true,b64:' + JSON.stringify(b64) + '}';
} else {
dataLiteral = JSON.stringify(payload);
}
} else {
dataLiteral = JSON.stringify(payload);
}
// Compose the bootstrap+initialize call as a separate string for readability
// Detailed catch handler so we see message+stack instead of silent blank.
var bootstrapCall = 'document.documentElement.setAttribute("data-boot","main-script-parsed");'
+ 'console.log("BOOTSTRAP START");'
+ 'bootstrapDATA(__RAW_DATA).then(function(d){'
+ 'console.log("DATA LOADED",d);'
+ 'DATA=d;__RAW_DATA=null;'
+ 'document.documentElement.setAttribute("data-boot","data-loaded");'
+ 'console.log("INITIALIZE START");'
+ 'initialize();'
+ 'document.documentElement.setAttribute("data-boot","initialize-complete");'
+ '}).catch(function(e){'
+ 'console.error("BOOTSTRAP CATCH",e);'
+ 'var msg=(e&&e.message)?e.message:String(e);'
+ 'var stk=(e&&e.stack)?e.stack:"(no stack)";'
+ 'document.body.innerHTML=\'<pre style="padding:24px;color:#b91c1c;font-family:Consolas,Monaco,monospace;white-space:pre-wrap;background:#fff;border:1px solid #fecaca;border-radius:6px;margin:16px;font-size:13px;line-height:1.5;"><strong>BOOTSTRAP ERROR</strong>\\n\\n\'+msg+\'\\n\\nStack:\\n\'+stk+\'\\n\\ngzip flag: \'+(__RAW_DATA&&__RAW_DATA.__gz?\'true\':\'false\')+\'\\nDecompressionStream support: \'+(typeof DecompressionStream===\'function\'?\'yes\':\'NO\')+\'\\ndata-boot: \'+document.documentElement.getAttribute(\'data-boot\')+\'</pre>\';'
+ '});';
return '<!DOCTYPE html>\n<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">'
+ '<title>' + esc_(bizName) + ' - Reports</title><style>' + css + '</style></head><body data-boot="html-loaded">'
+ getBootScript_()
+ '<div id="topbar"></div>'
+ '<aside class="sidebar-v3" id="sidebar"></aside>'
+ '<main class="main-v3" id="main">'
+ '<div id="chatPanel" class="conv-panel"><div class="conv-inner" id="convInner"></div></div>'
+ '<div id="reportPanel">'
+ '<div id="controls"></div>'
+ '<div id="report"></div>'
+ ''
+ '</div>'
+ '</main>'
+ '<div class="chatbar-v3" id="chatbar"></div>'
+ '<script>var __RAW_DATA=' + dataLiteral + ';\nvar DATA=null;\n' + js + '\n' + bootstrapCall + '</script></body></html>';
}
function esc_(s) { return s ? String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"') : ''; }
/* ═══════════════════════════════════════════
BOOT SCRIPT - global error handlers
Loaded BEFORE main script so it catches even
parse errors in the main script tag.
Added 2026-05-13 to diagnose blank-page issue.
═══════════════════════════════════════════ */
function getBootScript_() {
// Output JS uses single quotes; GAS string uses double quotes outer.
// \\n in source -> \n in output JS string literal -> newline in rendered HTML.
return '<script>'
+ 'document.documentElement.setAttribute("data-boot","script-tag-parsed");'
+ 'window.__BOOT_ERRORS=[];'
+ 'function __renderError(label,msg,stack){'
+ 'try{'
+ 'var html="<pre style=\\"padding:24px;color:#b91c1c;font-family:Consolas,Monaco,monospace;white-space:pre-wrap;background:#fff;border:1px solid #fecaca;border-radius:6px;margin:16px;font-size:13px;line-height:1.5;\\">"'
+ '+"<strong>"+label+"</strong>\\n\\n"'
+ '+msg'
+ '+(stack?"\\n\\nStack:\\n"+stack:"")'
+ '+"\\n\\n<em style=\\"color:#6b7280;font-style:italic;\\">data-boot: "+document.documentElement.getAttribute("data-boot")+"</em>"'
+ '+"</pre>";'
+ 'if(document.body){document.body.innerHTML=html;}'
+ 'else{document.addEventListener("DOMContentLoaded",function(){document.body.innerHTML=html;});}'
+ '}catch(e){}'
+ '}'
+ 'window.onerror=function(message,source,lineno,colno,error){'
+ 'window.__BOOT_ERRORS.push({type:"error",message:message,line:lineno,col:colno,stack:error&&error.stack});'
+ '__renderError("JS ERROR (window.onerror)",'
+ 'message+"\\nLine: "+lineno+":"+colno,'
+ 'error&&error.stack?error.stack:""'
+ ');'
+ 'return false;'
+ '};'
+ 'window.onunhandledrejection=function(event){'
+ 'var reason=event.reason;'
+ 'var msg=reason&&reason.message?reason.message:String(reason);'
+ 'var stk=reason&&reason.stack?reason.stack:"";'
+ 'window.__BOOT_ERRORS.push({type:"promise",message:msg,stack:stk});'
+ '__renderError("PROMISE ERROR (unhandledrejection)",msg,stk);'
+ '};'
+ 'document.documentElement.setAttribute("data-boot","error-handlers-installed");'
+ '</script>';
}
/* ═══════════════════════════════════════════
CSS
═══════════════════════════════════════════ */
function getCSS_() {
return ':root{--navy:#1F3864;--blue:#2E75B6;--gold:#C9A227;--gold-fill:#FFF2CC;--gold-soft:#FDF8E8;--gray-light:#E8EEF4;--subtotal:#DAE3F3;--border:#D0D0D0;--report-width:850px;--container-width:1400px;}'
+ '*{box-sizing:border-box;margin:0;padding:0;}'
+ 'body{font-family:Roboto,-apple-system,Arial,sans-serif;background:#F5F5F5;color:#222;line-height:1.4;font-size:13px;}'
+ '#topbar{background:var(--navy);padding:0;}'
+ '.topbar-inner{max-width:1400px;margin:0 auto;padding:0 16px;display:flex;justify-content:space-between;align-items:center;}'
+ '.topbar-left{display:flex;align-items:stretch;gap:0;}'
+ '.topbar-logo{color:white;font-size:14px;font-weight:700;letter-spacing:.5px;padding:10px 16px 10px 0;display:flex;align-items:center;}'
+ '.topbar-sep{width:1px;background:rgba(255,255,255,.12);margin:4px 8px;}'
+ '.tab-btn{padding:10px 14px;font-size:14px;border:none;cursor:pointer;background:transparent;color:rgba(255,255,255,.55);border-bottom:3px solid transparent;font-family:inherit;font-weight:400;}'
+ '.tab-btn:hover{color:rgba(255,255,255,.8);}'
+ '.tab-btn.active{color:white;font-weight:700;border-bottom-color:var(--gold);background:rgba(255,255,255,.06);}'
+ '.tab-btn.disabled{color:rgba(255,255,255,.2);cursor:not-allowed;}'
+ '.tab-btn.disabled:hover{color:rgba(255,255,255,.2);background:transparent;}'
// Group buttons (Row 1) - primary section selectors
+ '.group-btn{padding:10px 16px;font-size:14px;border:none;cursor:pointer;background:transparent;color:rgba(255,255,255,.65);border-bottom:3px solid transparent;font-family:inherit;font-weight:500;white-space:nowrap;}'
+ '.group-btn:hover{color:white;}'
+ '.group-btn.active{color:white;font-weight:700;border-bottom-color:var(--gold);background:rgba(255,255,255,.08);}'
// Subnav bar (Row 2) - white bg, navy tabs, gold active underline
+ '.subnav-bar{background:white;border-bottom:1px solid #E0E6ED;}'
+ '.subnav-inner{max-width:1400px;margin:0 auto;padding:0 16px;display:flex;align-items:stretch;gap:0;overflow-x:auto;}'
+ '.subnav-inner .tab-btn{padding:9px 14px;font-size:13px;color:#6B7280;border-bottom:2px solid transparent;background:transparent;}'
+ '.subnav-inner .tab-btn:hover{color:#111827;background:#F9FAFB;}'
+ '.subnav-inner .tab-btn.active{color:#1F3864;font-weight:700;border-bottom-color:var(--gold);background:transparent;}'
+ '.subnav-inner .tab-btn.disabled{color:#D1D5DB;cursor:not-allowed;}'
+ '.subnav-inner .tab-btn.disabled:hover{color:#D1D5DB;background:transparent;}'
+ '.topbar-right{display:flex;gap:6px;padding:8px 0;}'
+ '.topbar-btn{background:rgba(255,255,255,.15);color:white;border:1px solid rgba(255,255,255,.3);border-radius:4px;padding:4px 12px;font-size:13px;cursor:pointer;font-weight:600;font-family:inherit;}'
+ '.topbar-btn:hover{background:rgba(255,255,255,.3);}'
+ '.container{max-width:var(--container-width);margin:0 auto;padding:12px 16px;}'
+ '.ctrl-bar{background:white;border-radius:6px;padding:10px 14px;margin:0 auto 6px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;text-align:left;max-width:var(--report-width);}'
+ '.ctrl-group{display:flex;flex-direction:column;gap:3px;}'
+ '.ctrl-group label{font-size:9px;color:#999;font-weight:700;letter-spacing:.5px;}'
// BS-only: lighter label weight + normal case (proper case rendering per Ed's feedback)
+ '.bs-adv-controls .ctrl-group label{font-weight:500;color:#888;letter-spacing:0.2px;font-size:10px;text-transform:none;}'
+ '.ctrl-group input,.ctrl-group select{padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit;}'
+ '.ctrl-dropdown-btn{padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit;cursor:pointer;background:white;min-width:100px;text-align:left;line-height:1.4;box-sizing:border-box;}'
+ '.ctrl-btn{padding:5px 10px;border:1px solid #D0D0D0;border-radius:4px;background:white;color:var(--navy);font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;}'
+ '.ctrl-btn:hover:not(:disabled){border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.28);}'
+ '.ctrl-btn:active:not(:disabled){transform:scale(.98);}'
+ '.ctrl-btn.on{background:var(--blue);color:white;}'
+ '.more-btn-active{display:inline-flex;align-items:center;gap:6px;padding:5px 4px 5px 10px;border:1px solid var(--gold);border-radius:4px;background:var(--gold-fill);color:var(--navy);font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;}'
+ '.more-btn-active-x{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:3px;font-size:10px;background:rgba(201,162,39,.2);cursor:pointer;}'
+ '.more-btn-active-x:hover{background:rgba(201,162,39,.45);}'
+ '.ctrl-spacer{flex:1;}'
+ '.toggle-wrap{display:flex;align-items:center;gap:8px;}'
+ '.toggle-label{font-size:12px;color:#555;font-weight:500;}'
+ '.toggle-switch{position:relative;display:inline-block;width:36px;height:20px;}'
+ '.toggle-switch input{display:none;}'
+ '.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#CCC;border-radius:20px;transition:.2s;}'
+ '.toggle-slider::before{content:"";position:absolute;height:16px;width:16px;left:2px;bottom:2px;background:white;border-radius:50%;transition:.2s;box-shadow:0 1px 2px rgba(0,0,0,.2);}'
+ '.toggle-switch input:checked + .toggle-slider{background:var(--gold);}'
+ '.toggle-switch input:checked + .toggle-slider::before{transform:translateX(16px);}'
// More panel row layout
+ '.more-row{display:flex;align-items:center;justify-content:space-between;padding:8px 2px;border-bottom:1px solid #F3F4F6;}'
+ '.more-row:last-child{border-bottom:none;}'
+ '.more-row-label{font-size:12px;color:var(--navy);font-weight:600;}'
+ '.more-action-btn{padding:4px 10px;border:1px solid #D0D0D0;border-radius:4px;background:white;color:var(--navy);font:inherit;font-size:11px;cursor:pointer;}'
+ '.more-action-btn:hover{background:#FAFBFC;}'
// Bilateral toggle (labels on both sides of switch)
+ '.toggle-bilateral{display:flex;align-items:center;gap:6px;}'
+ '.toggle-bilateral .bi-label{font-size:11px;color:#999;font-weight:500;transition:color .15s;}'
+ '.toggle-bilateral .bi-label.active{color:var(--navy);font-weight:600;}'
// Cash Flow statement
+ '.report-card{background:white;border:1px solid #E0E6ED;border-radius:10px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.03);margin-bottom:16px;}'
+ '.report-hdr{padding:14px 20px;border-bottom:1px solid #E0E6ED;}'
+ '.report-biz{color:var(--navy);font-size:11px;font-weight:500;letter-spacing:1px;}'
+ '.report-title{color:var(--navy);font-size:11px;font-weight:500;letter-spacing:.5px;margin:2px 0 6px;}'
+ '.report-date{color:#111827;font-size:12px;}'
+ '.report-currency{color:#6B7280;font-size:11px;font-style:italic;margin-top:2px;}'
+ '.cf-table{width:100%;border-collapse:separate;border-spacing:0;font-size:13px;color:#111827;}'
+ '.cf-table thead th{padding:10px 14px;font-size:12px;font-weight:600;color:#6B7280;border-bottom:1px solid #E0E6ED;}'
+ '.cf-table th.cf-descr{text-align:left;}'
+ '.cf-table th.cf-amount-h{text-align:right;width:160px;}'
+ '.cf-section-hdr td{padding:14px 14px 6px;font-size:13px;font-weight:600;color:#111827;}'
+ '.cf-line{padding:5px 14px 5px 26px;font-size:13px;}'
+ '.cf-indent{padding-left:38px;}'
+ '.cf-amount{padding:5px 14px;text-align:right;font-variant-numeric:tabular-nums;font-size:13px;}'
+ '.cf-drill{color:#2563EB;cursor:pointer;}'
+ '.cf-drill:hover{text-decoration:underline;}'
+ '.cf-muted{color:#6B7280;}'
+ '.cf-grp-label{padding:10px 14px 4px 26px;font-size:11px;color:#6B7280;font-style:italic;}'
+ '.cf-subtotal td{padding:8px 14px;font-size:13px;font-weight:500;color:#111827;background:#F5F7FA;border-top:1px solid #E0E6ED;border-bottom:1px solid #E0E6ED;}'
+ '.cf-subtotal .cf-amount{font-weight:500;}'
+ '.cf-total td{padding:10px 14px;font-size:13px;font-weight:500;color:#111827;border-top:1px solid #111827;}'
+ '.cf-total.cf-grand td{border-top:none;border-bottom:1px solid #111827;}'
+ '.cf-warning{background:#FEF3C7;border:1px solid #F59E0B;color:#78350F;padding:8px 14px;margin:0 20px 12px;border-radius:6px;font-size:12px;}'
// SCE-specific styles
+ '.sce-bal td{padding:8px 14px;font-size:13px;color:#111827;font-weight:500;}'
+ '.sce-bal-lbl{padding:8px 14px;font-size:13px;font-weight:500;color:#111827;}'
+ '.sce-loss{color:#DC2626!important;}'
// Financial Statements Package - B&W production-ready styling
+ '.fs-doc{font-family:"Times New Roman",Times,serif;color:#000;margin:16px auto;padding:0;}'
+ '.fs-page{width:8.5in;min-height:13in;padding:0.75in;margin:0 auto 16px;page-break-before:always;background:white;color:#000;box-shadow:0 1px 6px rgba(0,0,0,.18);box-sizing:border-box;}'
+ '.fs-page:first-child{page-break-before:auto;}'
+ '.fs-cover{min-height:600px;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;}'
+ '.fs-cover-content{margin-top:120px;}'
+ '.fs-entity-name{font-size:24px;font-weight:bold;letter-spacing:1px;margin-bottom:30px;text-transform:uppercase;}'
+ '.fs-doc-title{font-size:18px;letter-spacing:2px;margin-bottom:14px;}'
+ '.fs-period{font-size:14px;margin-bottom:8px;}'
+ '.fs-compare-note{font-size:13px;font-style:italic;}'
+ '.fs-section-title-block{text-align:center;margin-bottom:20px;}'
+ '.fs-smr-title{font-weight:bold;font-size:13px;margin-bottom:24px;}'
+ '.fs-smr-body{font-size:12px;line-height:1.7;text-align:justify;}'
+ '.fs-smr-body p{margin:0 0 10px;page-break-inside:avoid;}'
+ '.fs-smr-list{padding-left:30px;font-size:12px;line-height:1.7;}'
+ '.fs-smr-list li{margin-bottom:8px;text-align:justify;page-break-inside:avoid;}'
+ '.fs-sig-block{margin-top:60px;}'
+ '.fs-sig{margin-bottom:50px;page-break-inside:avoid;}'
+ '.fs-sig-line{border-bottom:1px solid #000;width:340px;height:30px;}'
+ '.fs-sig-label{font-size:11px;margin-top:2px;}'
+ '.fs-stmt-hdr{text-align:center;margin-bottom:16px;}'
+ '.fs-stmt-entity{font-size:12px;font-weight:bold;}'
+ '.fs-stmt-title{font-size:14px;font-weight:bold;letter-spacing:1px;margin:4px 0;}'
+ '.fs-stmt-period{font-size:11px;}'
+ '.fs-stmt-currency{font-size:10px;font-style:italic;color:#444;}'
+ '.fs-stmt-method{text-align:center;font-size:10px;font-style:italic;color:#444;margin-top:-10px;margin-bottom:14px;}'
+ '.fs-table{width:100%;border-collapse:collapse;font-size:11px;}'
+ '.fs-table th{font-weight:bold;}'
+ '.fs-th-left{text-align:left;padding:6px 0;border-bottom:1px solid #000;}'
+ '.fs-th-note{text-align:right;padding:6px 6px;border-bottom:1px solid #000;width:50px;}'
+ '.fs-th-amt{text-align:right;padding:6px 6px;border-bottom:1px solid #000;width:110px;}'
+ '.fs-section td.fs-section-lbl{padding:10px 0 4px;font-weight:bold;font-size:12px;}'
+ '.fs-subsection td.fs-subsection-lbl{padding:6px 0 2px 12px;font-style:italic;font-size:10px;}'
+ '.fs-line{padding:3px 0 3px 24px;}'
+ '.fs-line-bold{padding:6px 0;font-weight:bold;}'
+ '.fs-section-row td{padding-top:6px;}'
+ '.fs-indent{padding-left:36px;font-size:11px;}'
+ '.fs-note-num{text-align:right;padding:3px 6px;}'
+ '.fs-amt{text-align:right;padding:3px 6px;font-variant-numeric:tabular-nums;}'
+ '.fs-bold{font-weight:bold;}'
+ '.fs-subtotal td{padding:5px 6px;font-weight:bold;border-top:1px solid #000;}'
+ '.fs-subtotal td.fs-subtotal-lbl{padding-left:12px;}'
+ '.fs-total td{padding:6px;font-weight:bold;border-top:1px solid #000;}'
+ '.fs-total td.fs-total-lbl{padding-left:0;}'
+ '.fs-grand td{padding:8px 6px;font-weight:bold;border-top:2px solid #000;border-bottom:2px solid #000;}'
+ '.fs-grand td.fs-grand-lbl{padding-left:0;}'
+ '.fs-notes-placeholder{font-size:12px;line-height:1.7;padding:20px;}'
+ '.fs-notes-placeholder ul{padding-left:24px;}'
+ '.fs-notes-placeholder li{margin-bottom:4px;}'
+ '.fs-note{margin:20px 0;page-break-inside:avoid;}'
+ '.fs-note-title{font-size:12px;font-weight:bold;letter-spacing:.5px;margin-bottom:8px;border-bottom:1px solid #000;padding-bottom:4px;}'
+ '.fs-note-body{font-size:11px;line-height:1.6;text-align:justify;}'
+ '.fs-note-body p{margin:0 0 8px;}'
+ '.fs-note-body p b{font-weight:bold;}'
+ '.fs-note-table{width:100%;border-collapse:collapse;font-size:11px;margin:8px 0;}'
+ '.fs-note-table th{font-weight:bold;}'
+ '.fs-note-table .fs-th-left{text-align:left;padding:5px 0;border-bottom:1px solid #000;}'
+ '.fs-note-table .fs-th-amt{text-align:right;padding:5px 6px;border-bottom:1px solid #000;width:110px;}'
+ '.fs-note-table .fs-line{padding:3px 0 3px 12px;}'
+ '.fs-note-table .fs-amt{text-align:right;padding:3px 6px;font-variant-numeric:tabular-nums;}'
+ '.fs-note-table .fs-grand td{padding:5px 6px;font-weight:bold;border-top:2px solid #000;border-bottom:2px solid #000;}'
+ '.fs-note-table .fs-grand td.fs-grand-lbl{padding-left:0;}'
+ '.fs-note-table .fs-subtotal td{padding:4px 6px;font-weight:bold;border-top:1px solid #000;}'
+ '.fs-note-table .fs-subtotal td.fs-subtotal-lbl{padding-left:0;font-style:italic;}'
+ '.fs-note-table .fs-section-row td.fs-line-bold{padding:6px 0 3px;font-weight:bold;font-style:italic;}'
+ '.fs-ppe-table{font-size:10px;}'
+ '.fs-ppe-table .fs-th-amt{width:auto;}'
// Report document-page (Word-doc look) - screen (defined BEFORE print so print overrides win by source order)
+ '.rpt-doc{margin:16px auto;padding:0;}'
+ '.rpt-page{width:8.5in;min-height:13in;padding:0.75in;margin:0 auto 16px;background:white;box-shadow:0 1px 6px rgba(0,0,0,.18);box-sizing:border-box;}'
+ '.rpt-page .fs-header{box-shadow:none;padding:0 0 10px;margin-bottom:12px;border-bottom:2px solid var(--navy);border-radius:0;}'
+ '.rpt-page .table-wrap{max-width:100%;margin:0;overflow-x:auto;}'
// Print-specific
+ '@media print{'
+ ' body{background:white!important;}'
+ ' .topbar,.ctrl-bar,.tab-btn{display:none!important;}'
+ ' .fs-doc{max-width:100%;padding:0;margin:0;zoom:1 !important;}'
+ ' .fs-page{page:fsPage;page-break-before:always;padding:0;width:auto;min-height:0;margin:0;box-shadow:none;}'
+ ' .fs-page:first-child{page-break-before:auto;}'
+ ' .fs-cover{page-break-after:always;}'
+ ' table.fs-table{page-break-inside:auto;}'
+ ' table.fs-table tr{page-break-inside:avoid;page-break-after:auto;}'
+ ' table.fs-table thead{display:table-header-group;}'
+ ' table.fs-table tfoot{display:table-footer-group;}'
+ ' @page fsPage{size:8.5in 13in;margin:0.75in;@bottom-center{content:"Page " counter(page) " of " counter(pages);font-family:"Times New Roman",serif;font-size:9px;color:#666;}}'
+ ' @page fsPage:first{@bottom-center{content:none;}}'
+ ' .rpt-doc{padding:0;margin:0;zoom:1 !important;}'
+ ' .rpt-page{page:fsPage;page-break-before:auto;padding:0;width:auto;min-height:0;margin:0;box-shadow:none;}'
+ ' .rpt-page .table-wrap{overflow:visible;max-width:100%;}'
+ ' .rpt-page .tb-table{box-shadow:none;width:100%;}'
+ ' .rpt-page .tb-table th{position:static;}'
+ '}'
+ '.report-block{max-width:var(--report-width);margin:0 auto;text-align:left;}'
+ '.table-wrap{max-width:var(--report-width);margin:0 auto;overflow-x:auto;}'
+ '.fs-header{padding:14px 18px 10px;background:white;border-radius:6px;margin-bottom:6px;box-shadow:0 1px 3px rgba(0,0,0,.05);}'
+ '.fs-biz-name{font-size:15px;font-weight:700;color:var(--navy);text-transform:uppercase;letter-spacing:.5px;}'
+ '.fs-title{font-size:13px;font-weight:700;color:var(--navy);margin-top:3px;letter-spacing:1px;}'
+ '.fs-date{font-size:12px;color:#555;margin-top:2px;}'
+ '.fs-currency{font-size:12px;color:#999;margin-top:1px;font-style:italic;}'
+ '.tb-table{width:max-content;min-width:100%;border-collapse:separate;border-spacing:0;text-align:left;background:white;box-shadow:0 1px 3px rgba(0,0,0,.06);border-radius:6px;}'
+ '.tb-table th:first-child{border-top-left-radius:6px;}'
+ '.tb-table th:last-child{border-top-right-radius:6px;}'
+ '.tb-table th{position:sticky;top:0;z-index:10;background:var(--navy);padding:6px 10px;color:white;font-size:12px;font-weight:600;text-align:left;white-space:nowrap;}'
+ '.tb-table th.num{text-align:right;}'
+ '.tb-table th.sortable{cursor:pointer;user-select:none;}'
+ '.tb-table th.sortable:hover{background:#2a4a7a;}'
+ '.sort-arrow{margin-left:3px;font-size:9px;}'
+ '.sort-arrow.active{color:var(--gold);}'
+ '.sort-arrow.inactive{color:rgba(255,255,255,.25);font-size:8px;}'
+ '.row-even td{padding:4px 10px;font-size:13px;border-bottom:1px solid #F0F0F0;}'
+ '.row-odd td{padding:4px 10px;font-size:13px;background:#FAFAFA;border-bottom:1px solid #F0F0F0;}'
+ '.clickable-row{cursor:pointer;}.clickable-row:hover td{background:#EEF4FF!important;}'
+ 'td.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;}'
+ '.section-row td{background:var(--blue)!important;color:white;padding:5px 10px;font-size:13px;font-weight:700;}'
+ '.subtotal-row td{background:var(--subtotal)!important;padding:4px 10px;font-size:13px;border-bottom:1px solid var(--border);}'
+ '.subtotal-label{text-align:right;padding-right:14px!important;font-weight:600;color:var(--navy);}'
+ '.subtotal-num{font-weight:600;color:var(--navy);text-align:right;font-variant-numeric:tabular-nums;}'
+ '.total-row td{background:var(--navy)!important;padding:6px 10px;font-size:13px;}'
+ '.total-row td:first-child{border-bottom-left-radius:6px;}'
+ '.total-row td:last-child{border-bottom-right-radius:6px;}'
+ '.total-label{text-align:right;padding-right:14px!important;font-weight:700;color:white;}'
+ '.total-num{font-weight:700;color:white!important;font-size:13px;text-align:right;font-variant-numeric:tabular-nums;}'
+ '.gl-acct-hdr td{background:var(--blue)!important;color:white;padding:7px 10px;font-size:13px;font-weight:700;}'
+ '.gl-opening td{background:var(--gray-light);padding:5px 10px;font-size:13px;font-weight:600;color:var(--navy);}'
+ '.gl-period td{background:var(--subtotal)!important;padding:5px 10px;font-size:13px;font-weight:600;color:var(--navy);}'
+ '.gl-closing td{background:var(--navy)!important;padding:6px 10px;font-size:13px;font-weight:700;color:white;}'
+ '.gl-closing td:first-child{border-bottom-left-radius:6px;}'
+ '.gl-closing td:last-child{border-bottom-right-radius:6px;}'
+ '.gl-empty td{padding:20px 10px;text-align:center;color:#999;font-style:italic;}'
// Panels (account picker, filter, more, columns)
+ '.panel{position:absolute;background:white;border:1px solid var(--gold);border-radius:6px;padding:12px;box-shadow:0 4px 16px rgba(0,0,0,.15);z-index:50;text-align:left;}'
+ '.panel-hdr{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;}'
+ '.panel-title{font-weight:700;color:var(--navy);font-size:12px;}'
+ '.panel-close{border:none;background:none;cursor:pointer;font-size:16px;color:#999;font-family:inherit;}'
+ '.panel-search{width:100%;padding:5px 8px;border:1px solid #DDD;border-radius:4px;font-size:11px;margin-bottom:8px;font-family:inherit;}'
+ '.panel-search:hover{border-color:var(--gold);}'
+ '.panel-search:focus{border-color:var(--gold);outline:none;box-shadow:0 0 0 2px rgba(201,162,39,.22);}'
+ '.acct-type-hdr{font-size:10px;font-weight:700;color:var(--blue);padding:3px 0;border-bottom:1px solid var(--gray-light);margin-bottom:2px;margin-top:6px;}'
+ '.acct-check{display:flex;align-items:center;gap:5px;padding:3px 0 3px 8px;cursor:pointer;font-size:11px;}'
+ '.acct-check-code{color:#888;font-size:10px;min-width:34px;}'
+ '.pill-btn{padding:3px 8px;font-size:9px;border:1px solid #D0D0D0;border-radius:12px;background:white;color:var(--navy);cursor:pointer;font-weight:500;font-family:inherit;}'
+ '.pill-btn:hover{border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.28);}'
+ '.back-btn{font-size:11px;color:var(--navy);border:1px solid #D0D0D0;border-radius:4px;padding:4px 10px;background:white;cursor:pointer;margin-bottom:8px;font-family:inherit;display:inline-block;}'
+ '.back-btn:hover{border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.28);}'
// Filter panel
+ '.filter-row{display:flex;gap:6px;align-items:center;margin-bottom:6px;}'
+ '.filter-row select,.filter-row input{padding:4px 6px;border:1px solid #DDD;border-radius:3px;font-size:11px;font-family:inherit;color:var(--navy);}'
+ '.filter-row select:hover,.filter-row input:hover{border-color:var(--gold);}'
+ '.filter-row select:focus,.filter-row input:focus{border-color:var(--gold);outline:none;box-shadow:0 0 0 2px rgba(201,162,39,.22);}'
+ '.filter-remove{border:none;background:none;cursor:pointer;color:#C62828;font-size:14px;}'
// Columns panel
+ '.col-section{font-size:10px;font-weight:700;color:var(--blue);padding:4px 0 2px;border-bottom:1px solid var(--gray-light);margin-top:6px;}'
+ '.col-check{display:flex;align-items:center;gap:5px;padding:2px 0 2px 4px;cursor:pointer;font-size:11px;}'
+ '.col-check.locked{color:#999;cursor:default;}'
// More panel inline
+ '.more-panel{background:white;border:1px solid var(--gold);border-radius:6px;padding:10px 14px;margin:0 auto 6px;display:flex;gap:20px;flex-wrap:wrap;align-items:center;text-align:left;max-width:var(--report-width);}'
+ '.more-check{display:flex;align-items:center;gap:4px;cursor:pointer;font-size:12px;color:#555;}'
// Pagination
+ '.pager{display:flex;justify-content:center;align-items:center;gap:8px;padding:10px 0;font-size:11px;color:#666;}'
+ '.pager-btn{padding:3px 8px;border:1px solid var(--border);border-radius:3px;background:white;cursor:pointer;font-size:11px;font-family:inherit;color:var(--navy);}'
+ '.pager-btn:hover{border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.22);}'
+ '.pager-btn.active{background:var(--navy);color:white;border-color:var(--navy);}'
+ '.pager-btn:disabled{opacity:.4;cursor:default;}'
+ '.drill-hint{font-size:10px;color:#999;text-align:center;padding:8px;}'
+ '.footer{text-align:center;padding:12px 0;font-size:12px;color:#CCC;}'
// IS-specific styles
+ '.is-ctrl-divider{width:1px;height:32px;background:var(--gray-light);margin:0 4px;align-self:center;}'
+ '.is-sec-row{background:var(--blue);color:white;cursor:pointer;user-select:none;}'
+ '.is-sec-row:hover{background:#2a5f94;}'
+ '.is-sec-row td{padding:7px 12px;font-size:13px;font-weight:600;}'
+ '.is-sec-row td:last-child{padding-right:14px;text-align:right;font-variant-numeric:tabular-nums;}'
+ '.is-chev{display:inline-block;width:12px;font-size:10px;margin-right:6px;color:rgba(255,255,255,.95);}'
+ '.is-acct-row td{padding:4px 12px 4px 36px;font-size:13px;border-bottom:1px solid #F0F0F0;}'
+ '.is-acct-row td:last-child{padding-right:14px;text-align:right;font-variant-numeric:tabular-nums;}'
+ '.is-acct-row.row-alt td{background:#FAFAFA;}'
+ '.is-subtotal-row td{background:var(--subtotal)!important;padding:6px 12px 6px 36px;font-size:13px;font-weight:600;color:var(--navy);border-top:1px solid var(--border);}'
+ '.is-subtotal-row td:last-child{padding-right:14px;text-align:right;font-variant-numeric:tabular-nums;}'
+ '.is-key-total td{background:var(--gray-light)!important;padding:8px 12px;font-size:13px;font-weight:700;color:var(--navy);border-top:1px solid var(--navy);border-bottom:1px solid var(--navy);letter-spacing:.3px;}'
+ '.is-key-total td:last-child{padding-right:14px;text-align:right;font-variant-numeric:tabular-nums;}'
+ '.is-ni-row td{background:var(--navy)!important;color:white;padding:10px 12px;font-size:13px;font-weight:700;letter-spacing:.5px;}'
+ '.is-ni-row td:last-child{padding-right:14px;text-align:right;font-variant-numeric:tabular-nums;}'
+ '.is-ni-row td:first-child{border-bottom-left-radius:6px;}'
+ '.is-ni-row td:last-child{border-bottom-right-radius:6px;}'
+ '.is-empty-row td{padding:30px;text-align:center;color:#999;font-style:italic;}'
+ '.is-pct-col{color:#888;font-size:13px;font-variant-numeric:tabular-nums;text-align:right;padding-right:14px;}'
+ '.is-meta-col{font-size:11px;color:#666;}'
+ '.is-hint{font-size:10px;color:#999;text-align:center;padding:8px;font-style:italic;}'
// BS-only styles
// Type row (Asset/Liability/Equity) - plain text, top hierarchy
+ '.bs-type-row td{padding:12px 12px 4px;font-size:13px;font-weight:700;color:var(--navy);background:white;border-top:2px solid var(--border);text-transform:none;letter-spacing:0.3px;}'
// FSGroup sub-header (Current Assets / Non-Current Assets / etc.) - plain text, indented
+ '.bs-fsg-row td{padding:6px 12px 4px 24px;font-size:13px;font-weight:600;color:var(--navy);background:white;font-style:italic;}'
// FSGroup subtotal (Total Current Assets) - lighter background, indented
+ '.bs-fsg-sub td{padding:6px 12px 6px 24px;font-size:13px;font-weight:600;color:var(--navy);background:#F5F7FA;border-top:1px solid #E0E6ED;}'
+ '.bs-fsg-sub td:last-child{padding-right:14px;text-align:right;font-variant-numeric:tabular-nums;}'
// Balance check indicator
+ '.bs-check-ok td{padding:6px 12px;font-size:11px;color:#2E7D32;text-align:right;font-style:italic;}'
+ '.bs-check-bad td{padding:8px 12px;font-size:12px;font-weight:600;text-align:center;background:#FFEBEE;color:#C62828;border:1px solid #FFCDD2;border-radius:4px;}'
// Advanced mode chrome
+ '.bs-adv-controls{background:white;border:1px solid #E0E6ED;border-radius:10px;margin:0 auto 14px;box-shadow:0 1px 3px rgba(0,0,0,.03);max-width:var(--report-width);}'
+ '.bs-adv-controls .ctrl-bar{border-radius:0;box-shadow:none;background:transparent;}'
+ '.bs-adv-controls .ctrl-bar + .ctrl-bar{border-top:1px dashed #EEE;}'
+ '.gold-active{background:var(--gold-fill)!important;border-color:var(--gold)!important;color:var(--navy)!important;font-weight:600;}'
// ═══ Airy report (scoped to .airy-report wrapper - applies to BS + IS, not TB/GL) ═══
+ '.airy-report{background:white;border:1px solid #E0E6ED;border-radius:10px;box-shadow:0 1px 3px rgba(0,0,0,.03);}'
+ '.airy-report .tb-table{box-shadow:none;border-radius:0;background:transparent;}'
+ '.airy-report .tb-table th{background:white!important;color:#6B7280!important;font-size:12px;font-weight:600;padding:10px 14px;border-bottom:1px solid #E0E6ED;}'
+ '.airy-report .tb-table th:first-child{border-top-left-radius:10px;}'
+ '.airy-report .tb-table th:last-child{border-top-right-radius:10px;}'
+ '.airy-report .tb-table th.sortable:hover{background:#F9FAFB;color:#111827;}'
+ '.airy-report .sort-arrow.active{color:#111827;}'
+ '.airy-report .sort-arrow.inactive{color:#D1D5DB;}'
// TB airy rows (replaces legacy section-row, subtotal-row, total-row, row-even/odd)
+ '.airy-report .tb-section-row td{padding:12px 14px 6px;font-size:13px;font-weight:700;color:#111827;background:white;border-top:1px solid #F3F4F6;text-align:left;}'
+ '.airy-report .tb-acct-row td{padding:5px 14px;font-size:13px;color:#111827;background:white;border-bottom:1px solid #F3F4F6;}'
+ '.airy-report .tb-acct-row td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.tb-acct-amt{color:#2563EB!important;cursor:pointer;}'
+ '.tb-acct-amt:hover{text-decoration:underline;}'
+ '.airy-report .tb-meta-col{color:#6B7280;font-size:12px;font-style:italic;}'
+ '.airy-report .tb-subtotal-row td{padding:10px 14px;font-size:13px;font-weight:700;color:#111827;background:transparent;border-top:1px solid #E0E6ED;}'
+ '.airy-report .tb-subtotal-row td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .tb-subtotal-row td:first-child{text-align:left;}'
+ '.airy-report .tb-total-row td{padding:12px 14px;font-size:13px;font-weight:700;color:#111827;background:white;border-top:1px solid #111827;border-bottom:1px solid #111827;}'
+ '.airy-report .tb-total-row td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .tb-total-row td:first-child{text-align:left;}'
// TB new features: section interactive, chevrons, sort arrows, nested, synthetic
+ '.airy-report .tb-section-row{cursor:pointer;}'
+ '.airy-report .tb-section-row:hover td{background:#F9FAFB;}'
+ '.airy-report .tb-section-row td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .tb-nested-hdr td{padding-left:32px !important;}'
+ '.airy-report .tb-nested-acct td{padding-left:44px !important;}'
+ '.airy-report .tb-nested-sub td{padding-left:32px !important;background:#FAFBFC;}'
+ '.airy-report .tb-ni-subtotal td{background:#F5F7FA;border-top:1px solid #E0E6ED;}'
+ '.airy-report .chev{color:#6B7280;font-size:11px;margin-right:8px;display:inline-block;width:12px;}'
+ '.airy-report th.sortable{cursor:pointer;user-select:none;}'
+ '.airy-report th.sortable:hover{background:#F9FAFB;color:#111827;}'
+ '.airy-report .sort-arrow{color:#D1D5DB;margin-left:4px;font-size:9px;}'
+ '.airy-report .sort-arrow.active{color:#111827;}'
+ '.airy-report .tb-code{color:#6B7280;font-size:12px;}'
+ '.airy-report .tb-synth td{color:#111827;}'
// GL airy rows
+ '.airy-report .gl-acct-hdr td{padding:12px 14px 8px;font-size:12px;font-weight:700;color:#1F3864!important;background:white!important;border-bottom:1px solid #E0E6ED;}'
+ '.airy-report .gl-opening td{padding:8px 14px;font-size:12px;font-weight:500;font-style:italic;color:#6B7280!important;background:white!important;border-bottom:1px solid #F3F4F6;}'
+ '.airy-report .gl-opening td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .gl-entry-row td{padding:5px 14px;font-size:12px;font-weight:500;color:#111827;background:white;border-bottom:1px solid #F3F4F6;}'
+ '.airy-report .gl-entry-row td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.gl-ref{color:#2563EB!important;font-weight:500;}'
+ '.airy-report .gl-meta-col{color:#6B7280;font-size:12px;}'
+ '.airy-report .gl-bal-col{font-weight:600;color:#111827;}'
+ '.airy-report .gl-entryid-col{font-size:10px;color:#9CA3AF;}'
+ '.airy-report .gl-period td{padding:8px 14px;font-size:12px;font-weight:700;color:#111827!important;background:white!important;border-top:1px solid #E0E6ED;border-bottom:none!important;}'
+ '.airy-report .gl-period td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .gl-closing td{padding:12px 14px;font-size:12px;font-weight:700;color:#111827!important;background:transparent!important;border-top:1.5px solid #111827;border-bottom:none;}'
+ '.airy-report .gl-closing td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .gl-empty td{padding:20px 14px;text-align:center;color:#9CA3AF;font-style:italic;background:white;}'
// ══ BS Xero-inspired structure ══
// Type row (What You Own, What You Owe, Your Net Worth) - clickable, bold
+ '.airy-report .bs-type-row{cursor:pointer;}'
+ '.airy-report .bs-type-row:hover td{background:#F3F4F6!important;}'
+ '.airy-report .bs-type-row td{padding:14px 14px 10px;font-size:13px;font-weight:700;color:#111827;background:white;border-top:2px solid #E0E6ED;}'
+ '.airy-report .bs-type-row td.num{text-align:right!important;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .bs-type-row td:first-child{text-align:left;}'
// Type total row - bold black, amounts right
+ '.airy-report .bs-type-total td{padding:12px 14px;font-size:13px;font-weight:700;color:#111827;background:transparent;border-top:1.5px solid #111827;}'
+ '.airy-report .bs-type-total td:last-child{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .bs-type-total td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
// FSGroup row (Current Assets, Fixed Assets) - clickable, bold
+ '.airy-report .bs-fsg-row{cursor:pointer;}'
+ '.airy-report .bs-fsg-row:hover td{background:#F9FAFB!important;}'
+ '.airy-report .bs-fsg-row td{padding:10px 14px 6px 28px;font-size:13px;font-weight:600;color:#111827;background:white;border-top:1px solid #F3F4F6;}'
+ '.airy-report .bs-fsg-row td.num{text-align:right!important;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .bs-fsg-row td:first-child{text-align:left;}'
// FSGroup total row - bold, subtle bg
+ '.airy-report .bs-fsg-total td{padding:10px 14px 10px 28px;font-size:13px;font-weight:600;color:#111827;background:transparent;border-top:1px solid #E0E6ED;}'
+ '.airy-report .bs-fsg-total td:last-child{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .bs-fsg-total td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
// Account row - title black normal, amounts blue drillable
+ '.airy-report .bs-acct-row td{padding:5px 14px 5px 44px;font-size:13px;color:#111827;background:white;border-bottom:1px solid #F3F4F6;}'
+ '.airy-report .bs-acct-row td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
// Drillable amount cell - blue, clickable, underline on hover
+ '.bs-acct-amt{color:#2563EB!important;cursor:pointer;}'
+ '.bs-acct-amt:hover{text-decoration:underline;}'
+ '.bs-acct-amt.not-drillable{color:#111827!important;cursor:default;text-decoration:none;}'
+ '.bs-acct-amt.not-drillable:hover{text-decoration:none;color:#111827!important;}'
// Chevron
+ '.airy-report .bs-chev{color:#6B7280;font-size:11px;margin-right:8px;display:inline-block;width:12px;}'
// Meta column (NoteGroup)
+ '.airy-report .bs-meta-col{color:#6B7280;font-size:12px;font-style:italic;padding:5px 10px;}'
// Percent col
+ '.airy-report .bs-pct{color:#9CA3AF;font-size:12px;font-variant-numeric:tabular-nums;text-align:right;padding-right:14px;}'
// Spacer between Type blocks
+ '.airy-report .bs-spacer-row td{padding:0;background:white;border:none;}'
// Legacy IS classes kept for IS tab - Xero-style plain section header, no blue bar
+ '.airy-report .is-sec-row{background:white;color:#111827;cursor:pointer;}'
+ '.airy-report .is-sec-row:hover td{background:#F9FAFB;}'
+ '.airy-report .is-sec-row td{padding:12px 14px 6px 14px;font-size:13px;font-weight:700;color:#111827;background:white;border-top:1px solid #F3F4F6;text-align:left;}'
+ '.airy-report .is-sec-row td.num{text-align:right!important;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .is-sec-row td:first-child{text-align:left!important;}'
+ '.airy-report .is-sec-row .is-chev{color:#6B7280;font-size:11px;margin-right:8px;display:inline-block;width:12px;}'
// Section total (inline with accounts, same indent) - bold black, plain white bg, 1px border top
+ '.airy-report .is-sec-total td{padding:10px 14px 10px 44px;font-size:13px;font-weight:700;color:#111827;background:transparent;border-top:1px solid #111827;}'
+ '.airy-report .is-sec-total td:first-child{text-align:left!important;}'
+ '.airy-report .is-sec-total td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;padding-left:14px;}'
// Account row - title black, amount cell blue drillable
+ '.airy-report .is-acct-row td{padding:5px 14px 5px 44px;font-size:13px;color:#111827;background:white;border-bottom:1px solid #F3F4F6;}'
+ '.airy-report .is-acct-row.row-alt td{background:white;}'
+ '.airy-report .is-acct-row td:last-child{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
+ '.airy-report .is-acct-row td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
// Drillable amount cell (on account rows)
+ '.is-acct-amt{color:#2563EB!important;cursor:pointer;}'
+ '.is-acct-amt:hover{text-decoration:underline;}'
// Key total (Gross Profit, NIBT) - plain bold, 2px top/bottom border, no box
+ '.airy-report .is-key-total td{background:white!important;padding:10px 14px;font-size:13px;font-weight:700;color:#111827;border-top:1px solid #111827;border-bottom:1px solid #111827;}'
+ '.airy-report .is-key-total td:first-child{text-align:left!important;}'
+ '.airy-report .is-key-total td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
// NI row (final Net Income) - same plain bold + 2px borders
+ '.airy-report .is-ni-row td{background:white!important;color:#111827!important;padding:12px 14px;font-size:13px;font-weight:700;border-top:1px solid #111827;border-bottom:1px solid #111827;}'
+ '.airy-report .is-ni-row td:first-child{text-align:left!important;}'
+ '.airy-report .is-ni-row td.num{text-align:right;font-variant-numeric:tabular-nums;padding-right:16px;}'
// Empty + hint + spacer
+ '.airy-report .is-empty-row td{padding:32px;text-align:center;color:#9CA3AF;font-style:italic;}'
+ '.airy-report .is-hint{font-size:10px;color:#9CA3AF;text-align:center;padding:10px;}'
+ '.airy-report .spacer-row td{padding:0;background:white;border:none;}'
// Balance check
+ '.airy-report .bs-check-ok td{padding:10px 14px;font-size:11px;color:#6B7280;text-align:right;font-style:italic;background:white;border-top:1px solid #F3F4F6;}'
+ '.airy-report .bs-check-bad td{padding:10px 14px;font-size:12px;font-weight:600;text-align:center;background:#FEF2F2;color:#991B1B;border-top:1px solid #FECACA;border-bottom:1px solid #FECACA;}'
// % col + variance
+ '.airy-report .is-pct-col{color:#9CA3AF;font-size:13px;font-variant-numeric:tabular-nums;text-align:right;padding-right:16px!important;}'
+ '.airy-report .var-cell{background:white!important;}'
// Variance toggle "on" state - neutral, no gold
+ '.ctrl-btn.on{background:#111827;color:white;border-color:#111827;}'
+ '@media print{body{background:white;-webkit-print-color-adjust:exact;print-color-adjust:exact;}'
+ '#topbar,.ctrl-bar,.back-btn,.drill-hint,.panel,.more-panel,.pager{display:none!important;}'
+ '.container{margin:0;padding:0;max-width:100%;text-align:left;}'
+ '.report-block{min-width:auto;}'
+ '.tb-table{box-shadow:none;width:100%;}'
+ '.tb-table th{position:static;}'
+ '.tb-table tr{page-break-inside:avoid;}'
+ '.section-row td,.gl-acct-hdr td{background:var(--blue)!important;}'
+ '.subtotal-row td,.gl-period td{background:var(--subtotal)!important;}'
+ '.total-row td,.gl-closing td{background:var(--navy)!important;}'
+ '.is-sec-row{background:var(--blue)!important;cursor:default!important;}'
+ '.is-subtotal-row td{background:var(--subtotal)!important;}'
+ '.is-key-total td{background:var(--gray-light)!important;}'
+ '.is-ni-row td{background:var(--navy)!important;color:white!important;}'
// Airy-report overrides for print (higher specificity wins): keep them white in airy BS/IS
+ '.airy-report .is-sec-row{background:white!important;cursor:default!important;}'
+ '.airy-report .is-sec-row td{background:white!important;color:#111827!important;}'
+ '.airy-report .is-subtotal-row td{background:#F5F7FA!important;color:#374151!important;}'
+ '.airy-report .is-key-total td{background:white!important;color:#111827!important;}'
+ '.airy-report .is-ni-row td{background:white!important;color:#111827!important;}'
+ '.airy-report .bs-type-row td{background:white!important;color:#111827!important;}'
+ '.airy-report .tb-table th{background:white!important;color:#6B7280!important;border-bottom:1px solid #E0E6ED!important;}' /* PATCH 11: GL print parity */
+ '.airy-report .gl-acct-hdr td{background:white!important;color:#111827!important;border-bottom:1px solid #E0E6ED!important;}' /* PATCH 11 */
+ '.airy-report .gl-opening td{background:#F5F7FA!important;color:#111827!important;}' /* PATCH 11 */
+ '.airy-report .gl-period td{background:#FAFBFC!important;color:#111827!important;border-top:1px solid #E0E6ED!important;border-bottom:1px solid #E0E6ED!important;}' /* PATCH 11 */
+ '.airy-report .gl-closing td{background:white!important;color:#111827!important;border-top:1px solid #111827!important;border-bottom:1px solid #111827!important;}' /* PATCH 11 */
+ '.airy-report .sort-arrow{display:none!important;}' /* PATCH 11: hide sort arrows in print */
+ '.fs-header{padding:10px 0 6px;background:none;box-shadow:none;}'
+ '.row-even td,.row-odd td{font-size:11px;padding:2px 8px;}'
+ '.tb-table th{font-size:10px;padding:4px 8px;}'
+ '}'
/* ═══════════════════════════════════════════════════════════
v3.0 - SIDEBAR LAYOUT + CHATBAR
═══════════════════════════════════════════════════════════ */
+ ':root{--sidebar-w:280px;--sidebar-collapsed-w:52px;--topbar-h:48px;--chatbar-h:78px;}'
/* Thin topbar (replaces existing two-row topbar) */
+ '.topbar-v3{position:fixed;top:0;left:0;right:0;height:48px;background:var(--navy);color:white;display:flex;align-items:center;padding:0 16px;z-index:30;box-shadow:0 1px 3px rgba(0,0,0,.1);}'
+ '.topbar-v3 .tb-burger{background:transparent;border:none;color:rgba(255,255,255,.85);padding:8px;cursor:pointer;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:18px;line-height:1;font-family:inherit;}'
+ '.topbar-v3 .tb-burger:hover{background:rgba(255,255,255,.1);}'
+ '.topbar-v3 .tb-brand{font-size:14px;font-weight:700;letter-spacing:.5px;margin-left:8px;}'
+ '.topbar-v3 .tb-org{font-size:12px;font-weight:600;color:#fff;background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.22);border-radius:12px;padding:3px 12px;margin-left:12px;letter-spacing:.3px;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}'
+ '.topbar-v3 .tb-spacer{flex:1;}'
+ '.topbar-v3 .tb-action{background:rgba(255,255,255,.12);color:white;border:1px solid rgba(255,255,255,.25);border-radius:4px;padding:5px 12px;font-size:12px;cursor:pointer;font-weight:600;font-family:inherit;margin-left:6px;}'
+ '.topbar-v3 .tb-action:hover{background:rgba(255,255,255,.22);}'
+ '.topbar-v3 .tb-action:disabled{cursor:default;background:rgba(255,255,255,.12);}'
+ '.ntxn-menu{position:fixed;background:#fff;border:1px solid #E5E7EB;border-radius:12px;box-shadow:0 12px 34px rgba(0,0,0,.16);overflow:hidden;z-index:120;width:312px;}'
+ '.ntxn-menu-hdr{padding:11px 14px 7px;font-size:11px;font-weight:600;color:#6B7280;}'
+ '.ntxn-menu-item{display:flex;align-items:center;gap:12px;padding:11px 14px;border-top:1px solid #F0F1F3;cursor:pointer;}'
+ '.ntxn-menu-item:hover{background:#F5F7FA;}'
+ '.ntxn-menu-ico{min-width:42px;text-align:center;padding:6px 4px;border-radius:6px;font-size:11px;font-weight:700;letter-spacing:.3px;flex-shrink:0;}'
+ '.ntxn-menu-t1{font-size:13px;font-weight:600;color:#1F3864;}'
+ '.ntxn-menu-t2{font-size:11.5px;color:#6B7280;}'
+ '.tb-dots{display:inline-flex;gap:5px;align-items:center;justify-content:center;}'
+ '.tb-dots i{width:7px;height:7px;border-radius:50%;background:#fff;display:inline-block;animation:typing-pulse 1.2s infinite ease-in-out;}'
+ '.tb-dots i:nth-child(2){animation-delay:.18s;}'
+ '.tb-dots i:nth-child(3){animation-delay:.36s;}'
+ '.fs-refresh-overlay{position:fixed;top:48px;left:0;right:0;bottom:0;background:#F5F5F5;z-index:60;display:flex;align-items:center;justify-content:center;}'
+ '.fs-refresh-dots{display:inline-flex;gap:8px;}'
+ '.fs-refresh-dots i{width:9px;height:9px;border-radius:50%;background:#6B7280;display:inline-block;animation:typing-pulse 1.2s infinite ease-in-out;}'
+ '.fs-refresh-dots i:nth-child(2){animation-delay:.18s;}'
+ '.fs-refresh-dots i:nth-child(3){animation-delay:.36s;}'
/* Sidebar */
+ '.sidebar-v3{position:fixed;top:48px;left:0;bottom:0;width:280px;background:white;border-right:1px solid #E5E7EB;display:flex;flex-direction:column;z-index:15;transition:width .2s;}'
+ '.sidebar-v3.collapsed{width:52px;}'
+ '.sidebar-v3.collapsed .sb-content{display:none;}'
+ '.sidebar-v3.collapsed .icon-strip{display:flex;}'
+ '.icon-strip{display:none;flex-direction:column;align-items:center;padding:14px 0;gap:8px;}'
+ '.icon-strip .icon-btn{width:36px;height:36px;border-radius:6px;background:transparent;border:none;display:flex;align-items:center;justify-content:center;font-size:17px;cursor:pointer;color:#6B7280;font-family:inherit;}'
+ '.icon-strip .icon-btn:hover{background:#F3F4F6;color:#111827;}'
+ '.icon-strip .icon-btn.active{background:#E5E7EB;color:#111827;}'
+ '.icon-strip .icon-strip-label{font-size:9px;color:#9CA3AF;text-align:center;margin-top:-4px;}'
+ '.sb-content{flex:1;overflow-y:auto;padding:8px 0;}'
+ '.sb-content::-webkit-scrollbar{width:6px;}'
+ '.sb-content::-webkit-scrollbar-thumb{background:#D1D5DB;border-radius:3px;}'
+ '.nav-section{padding:2px 0;}'
+ '.nav-section-hdr{padding:8px 16px;font-size:12px;font-weight:600;color:#6B7280;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;border-radius:4px;margin:0 8px;}'
+ '.nav-section-hdr:hover{background:#F3F4F6;color:var(--navy);}'
+ '.nav-section-hdr .nav-ico{font-size:13px;}'
+ '.nav-section-hdr .nav-chev{margin-left:auto;font-size:9px;color:#9CA3AF;transition:transform .15s;}'
+ '.nav-section.collapsed .nav-chev{transform:rotate(-90deg);}'
+ '.nav-section.collapsed .nav-section-body{display:none;}'
+ '.nav-section-body{padding:2px 0 6px;}'
+ '.nav-item{padding:7px 14px 7px 34px;font-size:13px;color:#374151;cursor:pointer;display:flex;align-items:center;gap:8px;transition:background .15s,color .15s;border-radius:8px;margin:1px 8px;}'
+ '.nav-item:hover{background:#F3F4F6;}'
+ '.nav-item.active{background:#E5E7EB;color:#111827;font-weight:600;}'
+ '.nav-item.disabled{color:#D1D5DB;cursor:not-allowed;}'
+ '.nav-item.disabled:hover{background:transparent;}'
+ '.nav-item-badge{font-size:9px;color:#9CA3AF;background:#F3F4F6;padding:1px 6px;border-radius:8px;margin-left:auto;}'
+ '.sb-divider{height:1px;background:#E5E7EB;margin:6px 16px;}'
+ '.history-item{padding:6px 16px 6px 36px;font-size:12px;color:#4B5563;cursor:pointer;border-left:3px solid transparent;display:flex;flex-direction:column;gap:2px;}'
+ '.history-item:hover{background:#F9FAFB;border-left-color:var(--blue);}'
+ '.history-item-text{font-weight:500;color:#111827;font-size:12px;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}'
+ '.history-item-meta{font-size:10px;color:#9CA3AF;}'
+ '.history-empty{padding:8px 16px 12px 36px;font-size:11px;color:#9CA3AF;font-style:italic;}'
/* Main content area */
+ '.main-v3{position:fixed;top:48px;left:280px;right:0;bottom:0;overflow-y:scroll;scrollbar-gutter:stable;padding:16px 24px 90px 24px;background:#F5F5F5;transition:left .2s;}'
+ '.main-v3.expanded{left:52px;}'
+ '.main-v3 .container{display:block !important;max-width:100% !important;padding:0 !important;margin:0 !important;}'
+ '.main-v3 .ctrl-bar,.main-v3 .report-block,.main-v3 .report-card,.main-v3 .more-panel,.main-v3 .bs-adv-controls,.main-v3 .table-wrap{max-width:850px !important;margin-left:auto;margin-right:auto;}'
/* Floating chatbar */
+ '.chatbar-v3{position:fixed;bottom:0;left:280px;right:0;padding:12px 24px 16px;background:linear-gradient(to bottom,rgba(245,245,245,0) 0%,rgba(245,245,245,.85) 30%,rgba(245,245,245,1) 100%);z-index:10;transition:left .2s;pointer-events:none;}'
+ '.chatbar-v3.expanded{left:52px;}'
+ '.chatbar-inner{max-width:850px;margin:0 auto;pointer-events:auto;}'
+ '.chat-input-wrap{position:relative;background:white;border:1px solid #D0D0D0;border-radius:12px;box-shadow:0 4px 16px rgba(31,56,100,.08),0 1px 3px rgba(0,0,0,.04);transition:border-color .15s,box-shadow .15s;}'
+ '.chat-input-wrap:hover{border-color:var(--gold);}'
+ '.ctrl-group input:hover:not(:disabled),.ctrl-group select:hover:not(:disabled),.ctrl-dropdown-btn:hover:not(:disabled),.more-action-btn:hover:not(:disabled),.more-btn-active:hover:not(:disabled){border-color:var(--gold)!important;transition:border-color .15s;}'
+ '.ctrl-group input:focus:not(:disabled),.ctrl-group select:focus:not(:disabled),.ctrl-dropdown-btn:focus:not(:disabled),.more-action-btn:focus:not(:disabled),.more-btn-active:focus:not(:disabled){border-color:var(--gold)!important;outline:none;box-shadow:0 0 0 2px rgba(201,162,39,.22);transition:border-color .15s,box-shadow .15s;}'
+ '.ctrl-group input:disabled,.ctrl-group select:disabled,.ctrl-dropdown-btn:disabled,.more-action-btn:disabled{border-color:#E5E7EB!important;background:#F9FAFB!important;color:#9CA3AF!important;cursor:not-allowed!important;box-shadow:none!important;}'
+ '.chat-input{width:100%;padding:14px 50px 14px 16px;border:none;border-radius:12px;font-size:14px;color:var(--navy);font-family:inherit;outline:none;background:transparent;}'
+ '.chat-input::placeholder{color:#9CA3AF;font-style:italic;}'
+ '.chat-send{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:var(--navy);border:none;color:white;width:32px;height:32px;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;font-family:inherit;}'
+ '.chat-send:hover{background:#152844;box-shadow:0 0 0 2px rgba(201,162,39,.45);}'
+ '.chat-feedback{font-size:11px;color:#6B7280;margin-top:6px;text-align:center;min-height:14px;font-family:inherit;}'
+ '.chat-feedback.success{color:#059669;font-weight:500;}'
+ '.chat-feedback.error{color:#DC2626;}'
/* ═══ v4.0 Conversational Bookkeeping - Layer 0 chassis styles ═══ */
+ '#chatPanel{position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;overflow-y:auto;padding:24px 24px 100px;background:#FAFAFA;}'
+ '#reportPanel{display:none;}'
+ 'body.mode-report #chatPanel{display:none;}'
+ 'body.mode-report #reportPanel{display:block;}'
+ '.conv-inner{max-width:850px;margin:0 auto;width:100%;display:flex;flex-direction:column;gap:16px;}'
+ '.msg-user{align-self:flex-end;max-width:75%;background:#F3F4F6;color:#111827;padding:10px 14px;border-radius:14px 14px 2px 14px;font-size:13px;}'
+ '.msg-system{align-self:flex-start;width:100%;display:flex;flex-direction:column;gap:8px;}'
+ '.card{background:white;border:1px solid #E5E7EB;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.03);overflow:hidden;}'
+ '.card-hdr{padding:12px 16px 8px;display:flex;align-items:center;gap:10px;border-bottom:1px solid #F3F4F6;}'
+ '.card-icon{width:24px;height:24px;border-radius:6px;background:#F3F4F6;color:#374151;display:flex;align-items:center;justify-content:center;font-size:13px;flex-shrink:0;}'
+ '.card-icon.green{background:#ECFDF5;color:#059669;}'
+ '.card-icon.amber{background:#FFFBEB;color:#D97706;}'
+ '.card-icon.red{background:#FEF2F2;color:#DC2626;}'
+ '.card-icon.blue{background:#EFF6FF;color:#1D4ED8;}'
+ '.card-title-block{flex:1;min-width:0;}'
+ '.card-title{font-size:13px;font-weight:600;color:#111827;}'
+ '.card-subtitle{font-size:11px;color:#6B7280;margin-top:1px;}'
+ '.card-body{padding:14px 16px;}'
+ '.card-tag{font-size:9px;padding:2px 7px;border-radius:8px;background:#F3F4F6;color:#6B7280;text-transform:uppercase;letter-spacing:.5px;font-weight:700;}'
+ '.kpi-big{font-size:32px;font-weight:700;color:#1F3864;line-height:1;letter-spacing:-0.5px;font-variant-numeric:tabular-nums;}'
+ '.kpi-label{font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:.5px;font-weight:700;margin-bottom:6px;}'
+ '.kpi-delta{display:inline-flex;align-items:center;gap:4px;margin-top:10px;font-size:12px;font-weight:600;}'
+ '.kpi-delta.up{color:#059669;}'
+ '.kpi-delta.down{color:#DC2626;}'
+ '.kpi-delta.neutral{color:#6B7280;}'
+ '.kpi-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:#E5E7EB;border-radius:8px;overflow:hidden;}'
+ '.kpi-cell{background:white;padding:14px;}'
+ '.kpi-cell .kpi-label{font-size:10px;}'
+ '.kpi-cell .kpi-big{font-size:20px;}'
+ '.kpi-cell .kpi-delta{font-size:11px;margin-top:4px;}'
+ '.tbl-card{font-size:12px;}'
+ '.tbl-card table{width:100%;border-collapse:collapse;}'
+ '.tbl-card th{text-align:left;padding:8px 16px;font-size:10px;color:#6B7280;text-transform:uppercase;letter-spacing:.5px;font-weight:700;background:#F9FAFB;border-bottom:1px solid #E5E7EB;}'
+ '.tbl-card th.num{text-align:right;}'
+ '.tbl-card td{padding:8px 16px;border-bottom:1px solid #F3F4F6;font-size:12px;color:#111827;}'
+ '.tbl-card td.num{text-align:right;font-variant-numeric:tabular-nums;}'
+ '.tbl-card tr:hover td{background:#F9FAFB;cursor:pointer;}'
+ '.tbl-card tr.tbl-row-total td{font-weight:700;background:transparent !important;color:#1F3864;border-top:1.5px solid #111827;padding:12px 16px;}'
+ '.tbl-meta{color:#6B7280;font-size:11px;}'
+ '.tbl-amt{color:#2563EB;cursor:pointer;}'
+ '.tbl-amt:hover{text-decoration:underline;}'
+ '.var-row{display:grid;grid-template-columns:140px 90px 90px 1fr 60px 70px;align-items:center;padding:7px 16px;gap:10px;border-bottom:1px solid #F3F4F6;font-size:12px;}'
+ '.var-row.hdr{font-size:10px;color:#6B7280;text-transform:uppercase;letter-spacing:.5px;font-weight:700;background:#F9FAFB;padding-top:8px;padding-bottom:8px;}'
+ '.var-bar-bg{height:6px;background:#F3F4F6;border-radius:3px;position:relative;overflow:hidden;}'
+ '.var-bar-fill{height:100%;background:#DC2626;border-radius:3px;}'
+ '.var-bar-fill.green{background:#059669;}'
+ '.var-bar-fill.amber{background:#D97706;}'
+ '.var-amount{text-align:right;font-variant-numeric:tabular-nums;}'
+ '.var-pct{text-align:right;font-variant-numeric:tabular-nums;font-weight:600;}'
+ '.var-pct.up{color:#DC2626;}'
+ '.var-pct.down{color:#059669;}'
+ '.pct-up::before{content:"\\25B2 ";font-size:9px;}'
+ '.pct-down::before{content:"\\25BC ";font-size:9px;}'
+ '.cmp-grid{display:grid;grid-template-columns:1fr 1fr 1fr 100px;border-radius:8px;overflow:hidden;border:1px solid #E5E7EB;}'
+ '.cmp-cell{padding:10px 12px;border-right:1px solid #E5E7EB;border-bottom:1px solid #E5E7EB;font-size:12px;}'
+ '.cmp-cell:nth-last-child(-n+4){border-bottom:none;}'
+ '.cmp-cell.hdr{background:#F9FAFB;font-size:10px;font-weight:700;color:#6B7280;text-transform:uppercase;letter-spacing:.5px;}'
+ '.cmp-cell.num{text-align:right;font-variant-numeric:tabular-nums;}'
+ '.cmp-cell.delta-up{color:#059669;font-weight:600;}'
+ '.cmp-cell.delta-down{color:#DC2626;font-weight:600;}'
+ '.alert-card{border-left:3px solid #D97706;background:#FFFBEB;border-radius:8px;padding:12px 14px;display:flex;gap:10px;align-items:flex-start;}'
+ '.alert-card.red{border-left-color:#DC2626;background:#FEF2F2;}'
+ '.alert-card.green{border-left-color:#059669;background:#F0FDF4;}'
+ '.alert-icon{font-size:16px;flex-shrink:0;}'
+ '.alert-body{flex:1;}'
+ '.alert-title{font-weight:600;font-size:12px;color:#111827;margin-bottom:2px;}'
+ '.alert-text{font-size:12px;color:#374151;}'
+ '.prose-card{padding:14px 16px;line-height:1.6;font-size:13px;}'
+ '.prose-card p{margin-bottom:8px;}'
+ '.prose-card p:last-child{margin-bottom:0;}'
+ '.prose-card b{color:#1F3864;font-weight:600;}'
+ '.prose-card .num-inline{font-variant-numeric:tabular-nums;font-weight:600;color:#1F3864;}'
+ '.chips{display:flex;flex-wrap:wrap;gap:6px;}'
+ '.chip{background:#F9FAFB;border:1px solid #E5E7EB;color:#374151;padding:6px 12px;border-radius:16px;font-size:12px;font-weight:500;cursor:pointer;transition:all .15s;font-family:inherit;}'
+ '.chip:hover{background:#F3F4F6;border-color:#D1D5DB;color:#111827;}'
+ '.welcome-card{background:white;border:1px solid #E5E7EB;border-top:3px solid #1F3864;padding:20px 22px;}'
+ '.welcome-greeting{font-size:18px;font-weight:700;color:#1F3864;margin-bottom:4px;letter-spacing:-0.3px;}'
+ '.welcome-sub{font-size:12px;color:#6B7280;margin-bottom:16px;}'
+ '.dl-card{margin:0 0 18px;background:white;border:1px solid #E5E7EB;border-left:5px solid #1F3864;border-radius:10px;padding:16px 20px;box-shadow:0 2px 8px rgba(31,56,100,.08);}'
+ '.dl-head{font-size:14px;font-weight:800;color:#1F3864;text-transform:uppercase;letter-spacing:.4px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px solid #EEF1F6;}'
+ '.dl-list{display:flex;flex-direction:column;gap:11px;}'
+ '.dl-row{display:flex;align-items:center;gap:14px;font-size:13.5px;}'
+ '.dl-date{flex:0 0 64px;font-weight:800;color:#1F3864;font-variant-numeric:tabular-nums;font-size:14px;}'
+ '.dl-info{flex:1;display:flex;flex-direction:column;line-height:1.35;}'
+ '.dl-form{font-weight:700;color:#111827;font-size:14px;}'
+ '.dl-desc{font-size:12px;color:#6B7280;}'
+ '.dl-tag{font-size:10.5px;font-weight:700;color:#9CA3AF;text-transform:uppercase;letter-spacing:.5px;}'
+ '.dl-cond{font-size:10px;color:#9CA3AF;font-style:italic;margin-left:3px;}'
+ '.taxcal-wrap{max-width:8.5in;margin:0 auto;padding:14px 6px 28px;}'
+ '.taxcal-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:4px;}'
+ '.taxcal-title{font-size:18px;font-weight:800;color:#1F3864;}'
+ '.tc-chip{display:inline-block;font-size:11px;font-weight:700;color:#1F3864;background:#EEF3FB;border-radius:6px;padding:3px 10px;margin-left:6px;}'
+ '.taxcal-sub{font-size:12px;color:#6B7280;margin:10px 0 6px;}'
+ '.tc-toggles{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:8px;}'
+ '.tc-toggle{font-size:12px;font-weight:600;padding:7px 14px;border-radius:16px;border:1px solid #E5E7EB;background:#F8FAFC;color:#9CA3AF;cursor:pointer;white-space:nowrap;}'
+ '.tc-toggle.on{background:#E7F4EC;border-color:#9BCFB0;color:#1B7A43;}'
+ '.tc-ico{display:inline-block;width:14px;text-align:center;}'
+ '.tc-forms{font-weight:400;font-size:10.5px;opacity:.72;margin-left:5px;}'
+ '.tc-label{font-size:12px;font-weight:700;color:#6B7280;text-transform:uppercase;letter-spacing:.4px;margin:16px 0 8px;}'
+ '.tc-upnext{display:flex;flex-direction:column;gap:8px;}'
+ '.tc-up{display:flex;align-items:center;gap:14px;background:white;border:1px solid #E5E7EB;border-radius:8px;padding:11px 14px;}'
+ '.tc-up-soon{border-left:3px solid #BA7517;border-radius:0;}'
+ '.tc-up-date{text-align:center;min-width:58px;}'
+ '.tc-up-d{font-size:15px;font-weight:700;color:#1F3864;}'
+ '.tc-up-cd{font-size:11px;color:#9CA3AF;}'
+ '.tc-soon{color:#BA7517;}'
+ '.tc-up-info{flex:1;}'
+ '.tc-month{font-weight:700;color:#1F3864;font-size:13px;margin:12px 0 4px;}'
+ '.tc-mlist{background:#F8FAFC;border-radius:10px;padding:4px 14px;}'
+ '.tc-summ{font-size:12px;color:#6B7280;margin:2px 0 14px;}'
+ '.tc-sec-red{font-size:12px;font-weight:700;color:#B00020;margin:16px 0 8px;}'
+ '.tc-row{background:white;border:1px solid #E5E7EB;border-left:3px solid #D7DBE0;border-radius:0 8px 8px 0;padding:9px 13px;margin-bottom:7px;}'
+ '.tc-row-top{display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;}'
+ '.tc-rdate{min-width:50px;text-align:center;font-size:13px;font-weight:700;color:#1F3864;}'
+ '.tc-rcd{font-size:10px;font-weight:400;margin-top:1px;}'
+ '.tc-rmain{flex:1;min-width:130px;}'
+ '.tc-rform{font-size:14px;font-weight:600;color:#111827;}'
+ '.tc-pend{font-size:11px;font-weight:600;color:#BA7517;}'
+ '.tc-rdesc{font-size:11px;color:#6B7280;}'
+ '.tc-ract{flex-shrink:0;display:flex;align-items:center;}'
+ '.tc-mark{font-size:12px;padding:4px 11px;border:1px solid #C9D2DE;background:#F8FAFC;color:#1F3864;border-radius:6px;cursor:pointer;font-weight:600;}'
+ '.tc-filed{font-size:12px;color:#1B7A43;font-weight:600;display:inline-flex;align-items:center;gap:5px;}'
+ '.tc-undo{font-size:10px;color:#9CA3AF;background:none;border:none;cursor:pointer;text-decoration:underline;padding:0 2px;}'
+ '.tc-panel{display:inline-flex;gap:6px;align-items:center;flex-wrap:wrap;}'
+ '.tc-panel input{font-size:12px;padding:3px 6px;border:1px solid #D1D5DB;border-radius:5px;}'
+ '.tc-panel input[type=text]{width:130px;}'
+ '.tc-save{font-size:12px;padding:3px 10px;background:#E7F4EC;color:#1B7A43;border:1px solid #9BCFB0;border-radius:5px;cursor:pointer;font-weight:600;}'
+ '.tc-cancel{font-size:12px;padding:3px 9px;background:none;border:1px solid #E5E7EB;border-radius:5px;cursor:pointer;color:#6B7280;}'
+ '.tc-att{display:flex;align-items:flex-start;gap:8px;margin:7px 0 0 46px;padding:7px 11px;background:#F8FAFC;border-radius:6px;}'
+ '.tc-att-ico{color:#9CA3AF;font-size:14px;line-height:1.2;}'
+ '.tc-att-main{flex:1;min-width:100px;}'
+ '.tc-att-name{font-size:13px;font-weight:600;color:#111827;}'
+ '.tc-att-tag{font-size:9px;font-weight:700;color:#9CA3AF;text-transform:uppercase;letter-spacing:.4px;}'
+ '.tc-later{display:flex;align-items:center;gap:12px;padding:6px 2px;border-bottom:1px solid #F0F2F5;}'
+ '.tc-rdate2{min-width:50px;text-align:center;font-size:12px;font-weight:600;color:#1F3864;}'
+ '.tc-lform{flex:1;font-size:13px;color:#111827;}'
+ '.tc-st-done{font-size:11px;color:#1B7A43;font-weight:600;}'
+ '.tc-st-part{font-size:11px;color:#BA7517;font-weight:600;}'
+ '.tc-st-up{font-size:11px;color:#9CA3AF;}'
+ '.dl-passed{opacity:.45;}'
+ '.dl-next{margin-top:13px;padding:9px 12px;background:#EEF3FB;border-radius:7px;font-size:12.5px;color:#1F3864;font-weight:700;}'
+ '.dl-note{margin-top:8px;font-size:10.5px;color:#9CA3AF;font-style:italic;}'
+ '.dl-empty{font-size:13px;color:#6B7280;}'
+ '.spark{width:80px;height:22px;}'
+ '.spark-up{stroke:#059669;}'
+ '.spark-down{stroke:#DC2626;}'
+ '@media print{'
+ ' .topbar-v3,.sidebar-v3,.chatbar-v3{display:none !important;}'
+ ' .main-v3{position:static !important;left:0 !important;padding:0 !important;}'
+ '}'
+ '/* Phase 3: source badges + account type badges + floating toast + col resize handles */'
+ '.je-modal-meta{display:flex;flex-wrap:wrap;gap:8px;align-items:center;font-size:11px;color:#6B7280;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid #E5E7EB;}'
+ '.je-source-badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:700;letter-spacing:.5px;}'
+ '.acct-type-badge{display:inline-block;padding:2px 7px;border-radius:4px;font-size:9px;font-weight:700;letter-spacing:.3px;text-transform:uppercase;}'
+ '.acct-type-badge.asset{background:#DBEAFE;color:#1E3A8A;}'
+ '.acct-type-badge.liability{background:#FEE2E2;color:#991B1B;}'
+ '.acct-type-badge.equity{background:#FEF3C7;color:#78350F;}'
+ '.acct-type-badge.revenue{background:#D1FAE5;color:#065F46;}'
+ '.acct-type-badge.expense{background:#FCE7F3;color:#831843;}'
+ '.je-net-effect{margin-top:14px;padding:10px 14px;background:#F0F9FF;border-left:3px solid #2563EB;border-radius:6px;font-size:12px;line-height:1.5;color:#1E3A8A;}'
+ '.ee-lbl{font-size:10.5px;font-weight:600;color:#6B7280;text-transform:uppercase;letter-spacing:.4px;margin-bottom:3px;}'
+ '.je-modal .ntxn-in{width:100%;box-sizing:border-box;}'
+ '.coa-tbl{width:100%;border-collapse:collapse;font-size:12.5px;table-layout:fixed;}'
+ '.coa-tbl td,.coa-tbl th{overflow:hidden;text-overflow:ellipsis;}'
+ '.coa-row{cursor:pointer;}'
+ '.coa-edit-hint{opacity:0;color:#2563EB;font-weight:600;font-size:12px;transition:opacity .12s;}'
+ '.coa-row:hover .coa-edit-hint{opacity:1;}'
+ '.coa-chip{padding:5px 12px;border-radius:14px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid #D0D0D0;background:#fff;color:#374151;transition:background .12s,border-color .12s,color .12s;}'
+ '.coa-chip:hover{background:#FAFBFC;border-color:var(--gold);}'
+ '.coa-chip.on{background:var(--navy);border-color:var(--navy);color:#fff;}'
+ '.coa-chip.on:hover{background:#152844;border-color:#152844;}'
+ '.txn-sep td{border-top:2px solid #E5E7EB;}'
+ '.coa-tbl .txn-num{text-align:right;font-variant-numeric:tabular-nums;}'
+ '.coa-tbl th{text-align:left;font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:#6B7280;padding:8px 12px;border-bottom:1px solid #E5E7EB;font-weight:700;}'
+ '.coa-tbl td{padding:8px 12px;border-bottom:1px solid #F3F4F6;color:#111827;vertical-align:middle;}'
+ '.coa-grp td{background:#F5F7FA;font-weight:700;color:#1F3864;cursor:pointer;border-top:1px solid #E5E7EB;}'
+ '.coa-grp:hover td{background:#EEF2F8;}'
+ '.coa-row:hover td{background:#F9FAFB;}'
+ '.coa-pill{display:inline-block;padding:2px 9px;border-radius:10px;font-size:10px;font-weight:700;}'
+ '.coa-pill.on{background:#D1FAE5;color:#065F46;}'
+ '.coa-pill.off{background:#E5E7EB;color:#6B7280;}'
+ '.ntxn-card{background:#fff;border:1px solid #E5E7EB;border-radius:10px;padding:14px 16px;margin-bottom:12px;}'
+ '.ntxn-in{padding:6px 9px;border:1px solid #D0D0D0;border-radius:6px;font-size:13px;color:var(--navy);font-family:inherit;box-sizing:border-box;width:100%;}'
+ '.ntxn-in:hover{border-color:var(--gold);}'
+ '.ntxn-in:focus{border-color:var(--gold);outline:none;box-shadow:0 0 0 2px rgba(201,162,39,.22);}'
+ 'input.ntxn-in[type=number]{text-align:right;font-variant-numeric:tabular-nums;}'
+ 'input.amt-in{text-align:right;font-variant-numeric:tabular-nums;}'
+ '.ss-panel{position:fixed;z-index:99999;background:#fff;border:1px solid #C9A227;border-radius:6px;max-height:262px;overflow-y:auto;box-shadow:0 6px 22px rgba(0,0,0,.14);font-size:13px;}'
+ '.ss-grp{padding:4px 10px;font-size:9.5px;font-weight:700;color:#9CA3AF;letter-spacing:.4px;text-transform:uppercase;background:#FAFAFA;}'
+ '.ss-opt{padding:6px 10px;cursor:pointer;color:var(--navy);display:flex;justify-content:space-between;gap:10px;align-items:center;}'
+ '.ss-opt:hover,.ss-opt.ss-active{background:#2563EB;color:#fff;}'
+ '.ss-opt:hover .ss-opt-meta,.ss-opt.ss-active .ss-opt-meta{color:#DBEAFE;}'
+ '.ss-opt-sel{font-weight:700;}'
+ '.ss-opt-meta{color:#9CA3AF;font-size:11px;white-space:nowrap;}'
+ '.ss-empty{padding:8px 10px;color:#9CA3AF;}'
+ 'input.ss-input{background-image:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%227%22 viewBox=%220 0 10 7%22%3E%3Cpath d=%22M1 1.5l4 4 4-4%22 fill=%22none%22 stroke=%22%236B7280%22 stroke-width=%221.6%22 stroke-linecap=%22round%22/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 9px center;padding-right:24px;}'
+ '.ec-dots{display:inline-flex;align-items:center;justify-content:center;gap:3px;}'
+ '.ec-dots i{width:5px;height:5px;border-radius:50%;background:#C9A227;display:inline-block;animation:ecdot 1s infinite;}'
+ '.ec-dots i:nth-child(2){animation-delay:.16s;}'
+ '.ec-dots i:nth-child(3){animation-delay:.32s;}'
+ '@keyframes ecdot{0%,80%,100%{opacity:.25;transform:translateY(0);}40%{opacity:1;transform:translateY(-3px);}}'
+ '.ec-done{color:inherit;font-weight:700;}.ec-done::before{content:"\\2713 ";color:#C9A227;}'
+ '.bme-rowclr{width:22px;height:22px;line-height:1;padding:0;border:1px solid #E5E7EB;border-radius:5px;background:#fff;color:#9CA3AF;cursor:pointer;font-size:13px;font-family:inherit;}'
+ '.bme-rowclr:hover{border-color:#C9A227;color:#92600A;background:#FFF8E6;}'
+ '.bme-tbl .bme-auto{display:inline-block;white-space:nowrap;}'
+ '.ec-in{padding:6px 9px;border:1px solid #D0D0D0;border-radius:6px;font-size:13px;color:var(--navy);font-family:inherit;box-sizing:border-box;width:100%;background:#fff;}'
+ '.ec-in:hover:not(:disabled){border-color:var(--gold);}'
+ '.ec-in:focus:not(:disabled){border-color:var(--gold);outline:none;box-shadow:0 0 0 2px rgba(201,162,39,.22);}'
+ '.ec-in:disabled{background:#F3F4F6;color:#9CA3AF;}'
+ '.ec-btn{padding:6px 14px;border:1px solid #D0D0D0;border-radius:6px;background:#fff;color:var(--navy);font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit;}'
+ '.ec-btn:hover:not(:disabled){border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.28);}'
+ '.ec-btn:active:not(:disabled){transform:scale(.98);}'
+ '.ec-btn:disabled{opacity:.55;cursor:default;box-shadow:none;}'
+ '.ec-btn-primary{background:var(--navy);color:#fff;border-color:var(--navy);}'
+ '.ec-btn-primary:hover:not(:disabled){background:#152844;border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.45);}'
+ '.chat-input-wrap:focus-within{border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.22),0 4px 16px rgba(31,56,100,.08);}'
+ '.ntxn-btn{padding:6px 14px;border:1px solid #D0D0D0;border-radius:6px;background:#fff;color:var(--navy);font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit;}'
+ '.ntxn-btn:hover:not(:disabled){border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.28);}'
+ '.ntxn-btn:active:not(:disabled){transform:scale(.98);}'
+ '.ntxn-save{background:var(--navy);color:#fff;border-color:var(--navy);}'
+ '.ntxn-save:hover{background:#152844;border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.45);}'
+ '.ntxn-save:disabled{opacity:.5;cursor:not-allowed;}'
+ '.ntxn-fb{font-size:12.5px;margin-top:8px;min-height:16px;}'
+ '.floating-toast{position:fixed;bottom:90px;right:24px;width:380px;max-width:calc(100vw - 48px);max-height:60vh;overflow-y:auto;background:white;border:1px solid #E5E7EB;border-radius:12px;box-shadow:0 10px 30px rgba(0,0,0,0.15);z-index:150;animation:toastSlideIn 280ms cubic-bezier(0.16,1,0.3,1);}'
+ '@keyframes toastSlideIn{from{opacity:0;transform:translateY(20px);}to{opacity:1;transform:translateY(0);}}'
+ '@keyframes toastSlideOut{from{opacity:1;transform:translateY(0);}to{opacity:0;transform:translateY(20px);}}'
+ '.floating-toast-hdr{display:flex;align-items:center;padding:10px 14px;border-bottom:1px solid #F3F4F6;background:#FAFBFC;border-radius:12px 12px 0 0;}'
+ '.floating-toast-label{flex:1;font-size:11px;font-weight:700;color:#1F3864;text-transform:uppercase;letter-spacing:.5px;}'
+ '.floating-toast-close{background:transparent;border:none;font-size:18px;color:#6B7280;cursor:pointer;line-height:1;padding:0 4px;font-family:inherit;}'
+ '.floating-toast-close:hover{color:#DC2626;}'
+ '.floating-toast-body{padding:12px;}'
+ '.col-resize-handle{position:absolute;right:-3px;top:0;bottom:0;width:7px;cursor:col-resize;background:transparent;z-index:5;}'
+ '.col-resize-handle:hover{background:rgba(201,162,39,0.4);}'
+ ' /* JE Detail Modal */'
+ '.je-modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;z-index:200;animation:jefadeIn 200ms ease;}'
+ '@keyframes jefadeIn{from{opacity:0;}to{opacity:1;}}'
+ '.je-modal{background:white;border-radius:12px;padding:22px 24px;max-width:680px;width:90%;max-height:80vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,0.3);animation:jeslideUp 240ms cubic-bezier(0.16,1,0.3,1);}'
+ '@keyframes jeslideUp{from{transform:translateY(20px);opacity:0;}to{transform:translateY(0);opacity:1;}}'
+ '.je-modal-hdr{display:flex;align-items:center;margin-bottom:8px;gap:12px;}'
+ '.je-modal-title{flex:1;font-size:15px;font-weight:700;color:#1F3864;}'
+ '.je-modal-close{background:transparent;border:none;font-size:22px;color:#6B7280;cursor:pointer;padding:0 8px;line-height:1;font-family:inherit;}'
+ '.je-modal-close:hover{color:#DC2626;}'
+ '.je-modal-meta{font-size:11px;color:#6B7280;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid #E5E7EB;}'
+ '.je-modal-table{width:100%;border-collapse:collapse;font-size:13px;}'
+ '.je-modal-table th{text-align:left;padding:8px 10px;font-size:10px;color:#6B7280;text-transform:uppercase;letter-spacing:.5px;font-weight:700;border-bottom:1px solid #E5E7EB;background:#F9FAFB;}'
+ '.je-modal-table th.num{text-align:right;width:120px;}'
+ '.je-modal-table td{padding:8px 10px;border-bottom:1px solid #F3F4F6;}'
+ '.je-modal-table td.num{text-align:right;font-variant-numeric:tabular-nums;}'
+ '.je-modal-table tr.je-modal-total td{font-weight:700;border-top:1.5px solid #111827;background:transparent;color:#1F3864;padding:12px 10px;}'
+ '.je-modal-actions{margin-top:16px;display:flex;gap:8px;justify-content:flex-end;}'
+ '/* GL column auto-fit */'
+ '.gl-fit-table{table-layout:fixed;width:100%;}'
+ '.gl-fit-table th,.gl-fit-table td{overflow:hidden;text-overflow:ellipsis;}'
+ '.gl-fit-table .gl-entry-row td{white-space:normal;word-wrap:break-word;word-break:break-word;}'
+ '/* GL ref/amount click affordance */'
+ '.gl-ref{cursor:pointer;}'
+ '.gl-ref:hover{text-decoration:underline;}'
+ '.gl-amt-clickable{color:#2563EB!important;cursor:pointer;}'
+ '.gl-amt-clickable:hover{text-decoration:underline;}'
+ '/* Phase 2: action buttons + customer drill */'
+ '.card-actions{display:flex;gap:8px;flex-wrap:wrap;padding:10px 14px;border-top:1px solid #F3F4F6;background:#FAFBFC;align-items:center;}'
+ '.action-btn{background:white;border:1px solid #E5E7EB;border-radius:6px;padding:5px 11px;font-size:12px;font-weight:500;color:#1F3864;cursor:pointer;font-family:inherit;transition:background .15s,border-color .15s;}'
+ '.action-btn:hover{background:#F9FAFB;border-color:#C9A227;}'
+ '.action-btn-primary{background:#1F3864;color:white;border-color:#1F3864;}'
+ '.action-btn-primary:hover{background:#152844;border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.45);}'
+ '.action-link{margin-left:auto;font-size:11px;color:#6B7280;cursor:pointer;font-family:inherit;background:transparent;border:none;padding:4px 6px;}'
+ '.action-link:hover{color:#2563EB;text-decoration:underline;}'
+ '.cust-list{padding:0;}'
+ '.cust-row{display:flex;align-items:center;gap:10px;padding:10px 16px;border-bottom:1px solid #F3F4F6;cursor:pointer;transition:background .15s;}'
+ '.cust-row:hover{background:#F9FAFB;}'
+ '.cust-row .cust-rank{font-size:11px;color:#9CA3AF;min-width:24px;font-weight:600;}'
+ '.cust-row .cust-name{flex:1;font-size:13px;color:#1F3864;font-weight:500;}'
+ '.cust-row .cust-amt{font-size:13px;color:#2563EB;font-weight:600;font-variant-numeric:tabular-nums;}'
+ '.cust-row .cust-toggle{color:#9CA3AF;font-size:11px;width:14px;text-align:center;}'
+ '.cust-detail-wrap{background:#F9FAFB;border-bottom:1px solid #F3F4F6;}'
+ '.cust-detail-table{width:100%;border-collapse:collapse;font-size:11px;}'
+ '.cust-detail-table thead th{padding:6px 12px 6px 50px;font-size:9px;color:#6B7280;text-transform:uppercase;letter-spacing:.5px;font-weight:700;background:#F3F4F6;border-bottom:1px solid #E5E7EB;text-align:left;}'
+ '.cust-detail-table thead th.num{text-align:right;padding-right:16px;}'
+ '.cust-detail-table tbody td{padding:5px 12px;border-bottom:1px solid #F3F4F6;color:#374151;}'
+ '.cust-detail-table tbody td:first-child{padding-left:50px;}'
+ '.cust-detail-table tbody td.num{text-align:right;padding-right:16px;font-variant-numeric:tabular-nums;}'
+ '.cust-detail-table tr.cust-total-row td{font-weight:700;background:transparent;color:#1F3864;border-top:1.5px solid #111827;padding:12px 10px;}'
+ '/* Fluid chat - slide-in / typing indicator */'
+ '.msg-user,.msg-system{animation:msg-slide-in 280ms cubic-bezier(0.16,1,0.3,1) both;}'
+ '@keyframes msg-slide-in{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);}}'
+ '.typing-wrap{width:fit-content;}'
+ '.typing-indicator{display:inline-flex;align-items:center;gap:5px;padding:12px 16px;background:white;border:1px solid #E5E7EB;border-radius:14px;box-shadow:0 1px 2px rgba(0,0,0,.03);}'
+ '.typing-indicator span{width:7px;height:7px;background:#9CA3AF;border-radius:50%;display:inline-block;animation:typing-pulse 1.2s infinite ease-in-out;}'
+ '.typing-indicator span:nth-child(2){animation-delay:.18s;}'
+ '.typing-indicator span:nth-child(3){animation-delay:.36s;}'
+ '@keyframes typing-pulse{0%,60%,100%{opacity:.3;transform:translateY(0);}30%{opacity:1;transform:translateY(-4px);}}'
/* === BIR Loose-Leaf Printable Books === */
+ '.book-wrap{max-width:850px;margin:0 auto;background:white;border:1px solid #E0E6ED;border-radius:10px;box-shadow:0 1px 3px rgba(0,0,0,.03);padding:18px 22px;}'
+ '.book-print{background:white;font-family:Roboto,Arial,sans-serif;color:#111827;}'
+ '.book-print .bk-pagehdr{text-align:center;padding:12px 8px 8px;}'
+ '.book-print .bk-title{font-size:18px;font-weight:700;color:#1F3864;letter-spacing:1.2px;text-transform:uppercase;margin-bottom:4px;}'
+ '.book-print .bk-entity{font-size:11px;color:#374151;line-height:1.5;}'
+ '.book-print .bk-period-line{text-align:center;font-size:12px;color:#374151;font-style:italic;margin-top:6px;margin-bottom:8px;}'
+ '.book-print table.bk-tbl{width:100%;border-collapse:collapse;margin-top:4px;border:1.5px solid #DC2626;}'
+ '.book-print .bk-tbl thead tr{background:white;color:#1F3864;}'
+ '.book-print .bk-tbl thead th{padding:6px 6px;font-size:11px;font-weight:700;text-align:center;color:#1F3864;background:white;border-right:1px solid #DC2626;border-bottom:1.5px solid #DC2626;letter-spacing:.4px;}'
+ '.book-print .bk-tbl thead th:last-child{border-right:none;}'
+ '.book-print .bk-tbl tbody td{padding:4px 6px;font-size:11px;border-bottom:1px dotted #D1D5DB;color:#111827;font-variant-numeric:tabular-nums;line-height:1.3;}'
+ '.book-print .bk-tbl tbody td:last-child{border-right:1.5px solid #DC2626;}'
+ '.book-print .bk-tbl td.bk-ln{text-align:center;color:#9CA3AF;font-weight:500;width:32px;border-right:1px solid #DC2626;}'
+ '.book-print .bk-tbl td.bk-mo{text-align:center;font-weight:700;font-style:italic;color:#1F3864;width:36px;border-right:1px solid #DC2626;}'
+ '.book-print .bk-tbl td.bk-day{text-align:center;font-weight:600;color:#1F3864;width:34px;border-right:1px solid #DC2626;}'
+ '.book-print .bk-tbl td.bk-particulars{text-align:left;padding-left:8px;border-right:1px dotted #D1D5DB;}'
+ '.book-print .bk-tbl td.bk-pr{text-align:center;color:#374151;width:50px;font-family:Consolas,monospace;font-size:10px;border-right:1px solid #DC2626;}'
+ '.book-print .bk-tbl td.bk-ref{text-align:center;color:#374151;width:90px;font-family:Consolas,monospace;font-size:10px;border-right:1px solid #DC2626;}'
+ '.book-print .bk-tbl td.bk-num{text-align:right;padding-right:10px;width:90px;border-right:1px solid #DC2626;}'
+ '.book-print .bk-tbl td.bk-bal{text-align:right;padding-right:10px;width:100px;font-weight:600;border-right:1.5px solid #DC2626;}'
+ '.book-print .bk-tbl thead th{border-right:1px solid #DC2626;}'
+ '.book-print .bk-tbl thead th:last-child{border-right:1.5px solid #DC2626;}'
+ '.book-print .bk-yr-row td{padding:6px 6px 4px;font-size:11px;font-weight:700;color:#1F3864;background:#FAFBFC;border-bottom:1px dotted #D1D5DB;}'
+ '.book-print .bk-yr-row td:last-child{border-right:1.5px solid #DC2626;}'
+ '.book-print .bk-acct-hdr td{padding:8px 6px;font-size:11px;font-weight:700;color:#1F3864;background:#F1F5F9;border-bottom:1.5px solid #DC2626;border-top:1.5px solid #DC2626;}'
+ '.book-print .bk-acct-hdr td:last-child{border-right:1.5px solid #DC2626;}'
+ '.book-print .bk-acct-hdr td.bk-acct-name{padding-left:8px;}'
+ '.book-print .bk-acct-hdr .bk-norm-pill{float:right;background:white;border:1px solid #1F3864;color:#1F3864;font-size:10px;font-weight:700;padding:1px 8px;border-radius:10px;letter-spacing:.5px;}'
+ '.book-print .bk-opening td{font-style:italic;color:#6B7280;background:#FAFBFC;}'
+ '.book-print .bk-closing td{font-weight:700;color:#1F3864;background:#F1F5F9;border-top:1.5px solid #1F3864;}'
+ '.book-print .bk-totals td{font-weight:700;color:#1F3864;background:#F1F5F9;border-top:2px solid #1F3864;}'
+ '.book-print .bk-empty td{text-align:center;color:#9CA3AF;font-style:italic;padding:14px;}'
+ '.book-print .bk-cover{padding:40px 20px;text-align:center;}'
+ '.book-print .bk-cover-title{font-size:24px;font-weight:700;color:#1F3864;letter-spacing:2px;text-transform:uppercase;margin:18px 0;}'
+ '.book-print .bk-cover-meta{font-size:13px;color:#374151;line-height:2;margin:30px auto;max-width:500px;}'
+ '.book-print .bk-cover-meta .bk-cover-row{display:flex;justify-content:space-between;border-bottom:1px solid #E5E7EB;padding:6px 0;}'
+ '.book-print .bk-cover-meta .bk-cover-lbl{font-weight:700;color:#1F3864;}'
+ '.book-print .bk-sworn{padding:30px 24px;font-size:12px;line-height:1.7;color:#111827;text-align:justify;}'
+ '.book-print .bk-sworn h3{text-align:center;font-size:14px;font-weight:700;color:#1F3864;letter-spacing:1px;text-transform:uppercase;margin-bottom:18px;}'
+ '.book-print .bk-sworn p{margin-bottom:12px;}'
+ '.book-print .bk-sworn .bk-sworn-sig{margin-top:36px;text-align:center;}'
+ '.book-print .bk-sworn .bk-sworn-line{display:inline-block;border-top:1px solid #111827;padding-top:4px;min-width:240px;font-weight:700;}'
+ '@media screen{.book-page-break{height:24px;}}'
+ '@media print{.topbar-v3,.sidebar-v3,.chatbar-v3,#chatPanel,.ctrl-bar,.book-print-actions,.fab,.toast{display:none!important;}*{background:transparent!important;box-shadow:none!important;}body{background:white!important;}html,body{overflow:visible!important;height:auto!important;}.main-v3{position:static!important;overflow:visible!important;height:auto!important;margin:0!important;padding:0!important;}#reportPanel{margin:0!important;padding:0!important;overflow:visible!important;}::-webkit-scrollbar{display:none!important;width:0!important;height:0!important;}.book-wrap{box-shadow:none;border:none;border-radius:0;padding:0;max-width:none;}.book-print{padding:0;font-size:9pt;}.book-print .bk-tbl thead{display:table-header-group;}.book-print .bk-tbl tr{page-break-inside:avoid;}.book-print .bk-acct-hdr,.book-print .bk-pagehdr{page-break-after:avoid;}.book-page-break,.book-print .bk-cover,.book-print .bk-sworn{page-break-after:always;}@page{size:A4 portrait;margin:14mm 12mm 16mm 12mm;}}'
+ '#reportPanel{transition:opacity 250ms ease;}'
+ '#reportPanel.skel-fade-in-start{opacity:0;}'
+ '';
}
/* ═══════════════════════════════════════════
JAVASCRIPT
═══════════════════════════════════════════ */
function getJS_() { return [
'/* === DATA bootstrap (handles gzip-compressed payload from server) === */',
'function bootstrapDATA(rawOrCompressed){',
' document.documentElement.setAttribute("data-boot","bootstrapDATA-entered");',
' if(typeof rawOrCompressed==="object"&&rawOrCompressed!==null&&!rawOrCompressed.__gz){',
' document.documentElement.setAttribute("data-boot","bootstrap-uncompressed-path");',
' return Promise.resolve(rawOrCompressed);',
' }',
' if(rawOrCompressed&&rawOrCompressed.__gz){',
' document.documentElement.setAttribute("data-boot","bootstrap-gzip-path");',
' var b64=rawOrCompressed.b64;',
' if(!b64){return Promise.reject(new Error("Gzip payload missing b64 field (payload corrupt?)"));}',
' if(typeof DecompressionStream!=="function"){',
' return Promise.reject(new Error("DecompressionStream not supported in this browser. Set OPTIM_FLAGS.gzip=false in Code.gs and redeploy, or switch to a Chromium-based browser."));',
' }',
' try{',
' var bytes=Uint8Array.from(atob(b64),function(c){return c.charCodeAt(0);});',
' var ds=new DecompressionStream("gzip");',
' var stream=new Blob([bytes]).stream().pipeThrough(ds);',
' return new Response(stream).text().then(function(txt){',
' document.documentElement.setAttribute("data-boot","gzip-decompressed");',
' try{return JSON.parse(txt);}',
' catch(je){throw new Error("Gzip decompressed but JSON.parse failed: "+je.message+" (first 200 chars: "+txt.slice(0,200)+")");}',
' });',
' }catch(e){return Promise.reject(new Error("Gzip decompression failed: "+(e&&e.message?e.message:String(e))));}',
' }',
' return Promise.resolve(rawOrCompressed);',
'}',
'/* ── COLUMN DEFINITIONS (17 variables) ── */',
'var GL_COLS=[',
' {id:"date",label:"Date",type:"text",core:true,on:true},',
' {id:"ref",label:"Reference",type:"text",core:false,on:true},',
' {id:"desc",label:"Description",type:"text",core:false,on:true},',
' {id:"name",label:"Name",type:"text",core:false,on:true},',
' {id:"dr",label:"Debit",type:"num",core:true,on:true},',
' {id:"cr",label:"Credit",type:"num",core:true,on:true},',
' {id:"bal",label:"Balance",type:"num",core:true,on:true},',
' {id:"source",label:"Source",type:"text",core:false,on:false},',
' {id:"entryId",label:"Entry ID",type:"text",core:false,on:false},',
' {id:"net",label:"Net Amount",type:"num",core:false,on:false},',
' {id:"acctType",label:"Account Type",type:"text",core:false,on:false},',
' {id:"fsGroup",label:"FS Group",type:"text",core:false,on:false},',
' {id:"taxLine",label:"Tax Line (1701)",type:"text",core:false,on:false},',
' {id:"noteGroup",label:"Note Group",type:"text",core:false,on:false},',
' {id:"taxLine2551",label:"Tax Line (2551Q)",type:"text",core:false,on:false},',
' {id:"month",label:"Month",type:"text",core:false,on:false},',
' {id:"quarter",label:"Quarter",type:"text",core:false,on:false}',
'];',
'',
'/* ── FILTER OPERATORS ── */',
'var FILTER_OPS={text:["contains","starts with","equals","not contains"],num:["equals","greater than","less than","between"],list:["is","is not"]};',
'var FILTER_VARS=[',
' {id:"desc",label:"Description",otype:"text"},',
' {id:"name",label:"Name",otype:"text"},',
' {id:"ref",label:"Reference",otype:"text"},',
' {id:"source",label:"Source",otype:"list",opts:["CRB","CDB","GJ"]},',
' {id:"entryId",label:"Entry ID",otype:"text"},',
' {id:"dr",label:"Debit Amount",otype:"num"},',
' {id:"cr",label:"Credit Amount",otype:"num"},',
' {id:"net",label:"Net Amount",otype:"num"},',
' {id:"acctCode",label:"Account Code",otype:"text"},',
' {id:"acctType",label:"Account Type",otype:"list",opts:["Asset","Liability","Equity","Revenue","Expense"]},',
' {id:"fsGroup",label:"FS Group",otype:"text"},',
' {id:"month",label:"Month",otype:"list",opts:["01","02","03","04","05","06","07","08","09","10","11","12"]},',
' {id:"quarter",label:"Quarter",otype:"list",opts:["Q1","Q2","Q3","Q4"]},',
' {id:"taxLine",label:"Tax Line (1701)",otype:"text"},',
' {id:"noteGroup",label:"Note Group",otype:"text"},',
' {id:"taxLine2551",label:"Tax Line (2551Q)",otype:"text"}',
'];',
'',
'/* ── State ── */',
'var ACTIVE_TAB="tb";',
'var ACTIVE_GROUP="reports";',
'var NAV_GROUPS=[',
' {key:"books",label:"Books",tabs:[',
' {key:"crb_book",label:"Cash Receipts Book"},',
' {key:"cdb_book",label:"Cash Disbursements Book"},',
' {key:"sj_book",label:"Sales Journal"},',
' {key:"pj_book",label:"Purchase Journal"},',
' {key:"gj_book",label:"General Journal"},',
' {key:"gl_book",label:"General Ledger"},',
' {key:"txnreg",label:"Transactions"},',
' {separator:true,disabled:true,label:"VAT Subsidiary Books (VAT-registered only)"},',
' {key:"ssj_book",label:"Subsidiary Sales Journal"},',
' {key:"spj_book",label:"Subsidiary Purchase Journal"}',
' ]},',
' {key:"reports",label:"Reports",tabs:[',
' {key:"tb",label:"Trial Balance"},',
' {key:"gl",label:"General Ledger"},',
' {key:"is",label:"Income Statement"},',
' {key:"bs",label:"Balance Sheet"},',
' {key:"sce",label:"Changes in Equity"},',
' {key:"cf",label:"Cash Flows"},',
' {key:"fs",label:"Financial Statements"},',
' {key:"subledger",label:"Subsidiary Ledgers"}',
' ]},',
' {key:"taxreturns",label:"Tax",tabs:[',
' {key:"1701q",label:"1701Q"},',
' {key:"1702q",label:"1702Q"},',
' {key:"2551q",label:"2551Q"},',
' {key:"2550q",label:"2550Q"},',
' {key:"slsp",label:"SLS"},',
' {key:"slp",label:"SLP"},',
' {key:"1701a",label:"1701A",disabled:true},',
' {key:"1701ms",label:"1701MS",disabled:true},',
' {key:"1701",label:"1701 (Jan 2018)",disabled:true}',
' ]},',
' {key:"setup",label:"Setup",tabs:[',
' {key:"coa",label:"Chart of Accounts"},',
' {key:"settings",label:"Settings"}',
' ]}',
'];',
'var APP_STATE={hideDecimals:true};',
'var BRANCH_VIEW="conso"; /* multi-branch view: conso | 00 (HO) | 01... */',
'var DRILL_CONTEXT=null;',
'var TB_STATE={view:"classic",netToggle:false,showVar:false,collapsed:{Asset:false,Liability:false,Equity:false,Income:false,Expense:false,NetIncome:true},sortCol:null,sortDir:"asc"};',
'var GL_STATE={selectedAccounts:{},sortCol:"date",sortDir:"asc",showPicker:false,showCols:false,showFilter:false,showMore:false,groupMode:"account",acctInclude:"with_trans",filters:[],showOC_ALE:true,showOC_RevExp:false,page:1,pageSize:100,showInactive:false};',
'var IS_STATE={from:"",to:"",preset:"tfy",compareMode:"none",customCompareDate:"",showVariance:false,collapsedSections:{Revenue:true,"Cost of Sales":true,"Other Income":true,"Operating Expenses":true,"Other Expenses":true,"Income Tax":true},showPctRevenue:false,showTaxLine1701:false,showTaxLine2551:false,showNoteGroup:false,showTrend:false,multiInterval:"monthly",multiCount:4,showIS_More:false,showIS_Cols:false};',
'var BS_STATE={asOfDate:"",preset:"latest",compareMode:"none",customCompareDate:"",showVariance:false,collapsedTypes:{},collapsedFSGroups:{},showPctTotalAssets:false,showTrend:false,showNoteGroup:false,multiInterval:"monthly",multiCount:4};',
'var CF_STATE={from:"",to:"",preset:"tfy",method:"direct",compareMode:"none",customCompareDate:"",showVariance:false,multiInterval:"monthly",multiCount:4};',
'var SCE_STATE={from:"",to:"",preset:"tfy",compareMode:"py",customCompareFrom:"",customCompareTo:"",showVariance:true};',
'var FS_STATE={year:"",compareYears:1,cfMethod:"indirect"};',
'var Q1701_STATE={taxYear:new Date().getFullYear(),viewMode:"Q1Q2Q3",filerType:"SINGLE_PROPRIETOR",taxpayerIncomeType:"PURE_BUSINESS",taxRateType:"GRADUATED",deductionMethod:"ITEMIZED",amendedReturn:false,numberOfSheetsAttached:0,claimingForeignTaxCredits:false,foreignTaxNumber:"",perQuarter:{Q1:{nonOperatingIncomeGraduated:0,nonOperatingIncome8pct:0,gppShare:0,priorYearExcessCredits:0,taxPaymentsPreviousQuarters:0,creditableTaxWithheldPrevQ:0,creditableTaxWithheldThisQ:0,taxPaidInReturnPreviouslyFiled:0,foreignTaxCredits:0,otherTaxCredits:0,surcharge:0,interest:0,compromise:0},Q2:{nonOperatingIncomeGraduated:0,nonOperatingIncome8pct:0,gppShare:0,priorYearExcessCredits:0,taxPaymentsPreviousQuarters:0,creditableTaxWithheldPrevQ:0,creditableTaxWithheldThisQ:0,taxPaidInReturnPreviouslyFiled:0,foreignTaxCredits:0,otherTaxCredits:0,surcharge:0,interest:0,compromise:0},Q3:{nonOperatingIncomeGraduated:0,nonOperatingIncome8pct:0,gppShare:0,priorYearExcessCredits:0,taxPaymentsPreviousQuarters:0,creditableTaxWithheldPrevQ:0,creditableTaxWithheldThisQ:0,taxPaidInReturnPreviouslyFiled:0,foreignTaxCredits:0,otherTaxCredits:0,surcharge:0,interest:0,compromise:0}},lastResults:null};',
'var DB_STATE={from:"",to:"",preset:"tfy"};',
'var BME_STATE={desc:"",date:"",legs:[{a:"",e:"",m:""},{a:"",e:"",m:""},{a:"",e:"",m:""},{a:"",e:"",m:""},{a:"",e:"",m:""},{a:"",e:"",m:""}]};',
'var LAST_PERIODS=[];',
'/* v3.0: Sidebar + Chat State */',
'var SIDEBAR_STATE={sectionsCollapsed:{books:true,reports:true,taxreturns:true,setup:true}};',
'var CHAT_HISTORY=[];',
'',
'/* ═══ v4.0: Conversational Bookkeeping (Layer 0 chassis) ═══ */',
'var CONV_STATE={messages:[],welcomeShown:false,mode:"chat"};',
'',
'function enterChatMode(){',
' CONV_STATE.mode="chat";',
' document.body.classList.remove("mode-report");',
' ACTIVE_TAB="";ACTIVE_GROUP="";',
' if(!CONV_STATE.welcomeShown){',
' if(typeof runAnomalyDaemon==="function"){try{runAnomalyDaemon();}catch(e){}}',
' renderWelcomeCardV2();',
' CONV_STATE.welcomeShown=true;',
' }',
' scrollToLatest();',
' if(typeof buildSidebar==="function")buildSidebar();',
'}',
'function enterReportMode(){',
' CONV_STATE.mode="report";',
' document.body.classList.add("mode-report");',
' ensureReportDOM();',
' if(typeof buildSidebar==="function")buildSidebar();',
'}',
'function ensureReportDOM(){var p=document.getElementById("reportPanel");if(!p){var m=document.getElementById("main")||document.body;if(!m)return;p=document.createElement("div");p.id="reportPanel";m.appendChild(p);}if(!document.getElementById("controls")){var c=document.createElement("div");c.id="controls";p.insertBefore(c,p.firstChild);}if(!document.getElementById("report")){var r=document.createElement("div");r.id="report";p.appendChild(r);}}',
'',
'function pushMessage(role,html){',
' var inner=document.getElementById("convInner");if(!inner)return;',
' var el=document.createElement("div");',
' el.className=(role==="user"?"msg-user":"msg-system");',
' el.innerHTML=html;',
' inner.appendChild(el);',
' CONV_STATE.messages.push({role:role,html:html,ts:Date.now()});',
' if(role==="system"&&typeof applyTypewriterReveal==="function"&&el.querySelector(".prose-card"))applyTypewriterReveal(el);',
' scrollToLatest();',
'}',
'function scrollToLatest(){',
' var p=document.getElementById("chatPanel");',
' if(p)setTimeout(function(){if(p.scrollTo){p.scrollTo({top:p.scrollHeight,behavior:"smooth"});}else{p.scrollTop=p.scrollHeight;}},10);',
'}',
'',
'function showTypingIndicator(){',
' var inner=document.getElementById("convInner");if(!inner)return null;',
' var el=document.createElement("div");',
' el.className="msg-system typing-wrap";',
' el.innerHTML=\'<div class="typing-indicator"><span></span><span></span><span></span></div>\';',
' inner.appendChild(el);',
' scrollToLatest();',
' return el;',
'}',
'',
'function removeTypingIndicator(el){',
' if(el&&el.parentNode)el.parentNode.removeChild(el);',
'}',
'',
'function applyTypewriterReveal(rootEl){',
' if(!rootEl)return;',
' var paras=rootEl.querySelectorAll(".prose-card p");',
' if(!paras||!paras.length)return;',
' for(var i=0;i<paras.length;i++){',
' var p=paras[i];',
' p.style.opacity="0";',
' p.style.transform="translateY(4px)";',
' p.style.transition="opacity 240ms ease, transform 240ms ease";',
' (function(p,i){setTimeout(function(){p.style.opacity="1";p.style.transform="translateY(0)";},80+i*200);})(p,i);',
' }',
'}',
'',
'/* ── 8 Card Render Functions (Patterns A-H) ── */',
'',
'function renderSparkline(dir,deltaCls){',
' var cls=(deltaCls==="up"?"spark-up":(deltaCls==="down"?"spark-down":""));',
' var pts=(dir==="up")?"0,16 16,15 32,14 48,12 64,10 80,8":"0,8 16,10 32,12 48,14 64,15 80,16";',
' var endY=(dir==="up")?"8":"16";',
' return "<svg class=\\"spark "+cls+"\\" viewBox=\\"0 0 80 22\\" fill=\\"none\\" style=\\"margin-top:6px\\"><polyline points=\\""+pts+"\\" stroke=\\"currentColor\\" stroke-width=\\"1.5\\"/><circle cx=\\"80\\" cy=\\""+endY+"\\" r=\\"2.5\\" fill=\\"currentColor\\"/></svg>";',
'}',
'',
'/* Pattern A - KPI Card */',
'function renderKPICard(d){',
' var iconCls=d.iconClass||"";',
' var icon=d.icon||"\\uD83D\\uDCB0";',
' var deltaCls=d.deltaClass||"neutral";',
' var h="<div class=\\"card\\"><div class=\\"card-hdr\\">";',
' h+="<div class=\\"card-icon "+iconCls+"\\">"+icon+"</div>";',
' h+="<div class=\\"card-title-block\\"><div class=\\"card-title\\">"+esc(d.title||"")+"</div>";',
' if(d.subtitle)h+="<div class=\\"card-subtitle\\">"+d.subtitle+"</div>";',
' h+="</div></div>";',
' h+="<div class=\\"card-body\\" style=\\"display:flex;align-items:center;gap:24px;padding:18px 16px\\">";',
' h+="<div><div class=\\"kpi-label\\">"+esc(d.label||"")+"</div>";',
' h+="<div class=\\"kpi-big\\">"+esc(d.value||"\\u2014")+"</div>";',
' if(d.delta)h+="<div class=\\"kpi-delta "+deltaCls+"\\">"+esc(d.delta)+"</div>";',
' h+="</div>";',
' if(d.compareLabel){',
' var cdc=d.compareDeltaClass||deltaCls;',
' h+="<div style=\\"flex:1;text-align:right\\">";',
' h+="<div class=\\"kpi-label\\">"+esc(d.compareLabel)+"</div>";',
' h+="<div class=\\"kpi-big\\" style=\\"font-size:20px\\">"+esc(d.compareValue||"")+"</div>";',
' h+="<div class=\\"kpi-delta "+cdc+"\\">"+esc(d.compareDelta||"")+"</div>";',
' if(d.spark)h+=renderSparkline(d.spark,cdc);',
' h+="</div>";',
' }',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* Pattern B - Multi-KPI Grid */',
'function renderMultiKPI(d){',
' var iconCls=d.iconClass||"green";',
' var icon=d.icon||"\\uD83D\\uDCC8";',
' var h="<div class=\\"card\\"><div class=\\"card-hdr\\">";',
' h+="<div class=\\"card-icon "+iconCls+"\\">"+icon+"</div>";',
' h+="<div class=\\"card-title-block\\"><div class=\\"card-title\\">"+esc(d.title||"")+"</div>";',
' if(d.subtitle)h+="<div class=\\"card-subtitle\\">"+d.subtitle+"</div>";',
' h+="</div>";',
' if(d.tag)h+="<span class=\\"card-tag\\" style=\\""+(d.tagStyle||"")+"\\">"+esc(d.tag)+"</span>";',
' h+="</div>";',
' h+="<div class=\\"card-body\\" style=\\"padding:14px 16px 16px\\"><div class=\\"kpi-grid\\">";',
' for(var i=0;i<d.cells.length;i++){var c=d.cells[i];',
' h+="<div class=\\"kpi-cell\\"><div class=\\"kpi-label\\">"+esc(c.label||"")+"</div>";',
' h+="<div class=\\"kpi-big\\""+(c.bigStyle?" style=\\""+c.bigStyle+"\\"":"")+">"+esc(c.value||"")+"</div>";',
' if(c.delta)h+="<div class=\\"kpi-delta "+(c.deltaClass||"neutral")+"\\">"+esc(c.delta)+"</div>";',
' h+="</div>";',
' }',
' h+="</div>";',
' if(d.insight)h+="<div style=\\"margin-top:12px;font-size:12px;color:#374151;padding:10px 12px;background:#F9FAFB;border-radius:6px;border-left:3px solid #2563EB\\">\\uD83D\\uDCA1 "+d.insight+"</div>";',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* Pattern C - Variance Decomposition */',
'function renderVarianceCard(d){',
' var h="<div class=\\"card\\"><div class=\\"card-hdr\\">";',
' h+="<div class=\\"card-icon amber\\">\\uD83D\\uDD0D</div>";',
' h+="<div class=\\"card-title-block\\"><div class=\\"card-title\\">"+esc(d.title||"")+"</div>";',
' if(d.subtitle)h+="<div class=\\"card-subtitle\\">"+d.subtitle+"</div>";',
' h+="</div></div>";',
' h+="<div class=\\"var-row hdr\\"><div>Category</div><div class=\\"var-amount\\">Prior</div><div class=\\"var-amount\\">Current</div><div>Contribution to change</div><div class=\\"var-amount\\">\\u0394</div><div class=\\"var-pct\\">%</div></div>";',
' for(var i=0;i<d.rows.length;i++){var r=d.rows[i];',
' var barCls=(r.barClass||"");',
' var pctCls=(r.pctClass||"");',
' var pctMark=(r.pctClass==="up"?"pct-up":(r.pctClass==="down"?"pct-down":""));',
' h+="<div class=\\"var-row\\"><div>"+(r.bold?"<b>":"")+esc(r.label)+(r.bold?"</b>":"")+"</div>";',
' h+="<div class=\\"var-amount\\">"+esc(r.prior||"")+"</div>";',
' h+="<div class=\\"var-amount\\">"+esc(r.current||"")+"</div>";',
' h+="<div class=\\"var-bar-bg\\">"+(r.barWidth?"<div class=\\"var-bar-fill "+barCls+"\\" style=\\"width:"+r.barWidth+"%\\"></div>":"")+"</div>";',
' h+="<div class=\\"var-amount\\">"+esc(r.delta||"")+"</div>";',
' h+="<div class=\\"var-pct "+pctCls+" "+pctMark+"\\""+(r.pctClass?"":" style=\\"color:#9CA3AF\\"")+">"+esc(r.pct||"")+"</div></div>";',
' }',
' if(d.insight)h+="<div style=\\"padding:12px 16px;background:#FEF3C7;border-top:1px solid #D97706\\"><div style=\\"font-size:12px;color:#111827\\">\\uD83D\\uDCA1 "+d.insight+"</div></div>";',
' h+="</div>";',
' return h;',
'}',
'',
'/* Pattern D - Search/Top-N Table */',
'function renderTableCard(d){',
' var iconCls=d.iconClass||"blue";',
' var icon=d.icon||"\\uD83D\\uDD0D";',
' var h="<div class=\\"card tbl-card\\"><div class=\\"card-hdr\\">";',
' h+="<div class=\\"card-icon "+iconCls+"\\">"+icon+"</div>";',
' h+="<div class=\\"card-title-block\\"><div class=\\"card-title\\">"+esc(d.title||"")+"</div>";',
' if(d.subtitle)h+="<div class=\\"card-subtitle\\">"+d.subtitle+"</div>";',
' h+="</div></div>";',
' h+="<table><thead><tr>";',
' for(var ci=0;ci<d.cols.length;ci++){var col=d.cols[ci];',
' var cls=col.num?" class=\\"num\\"":"";',
' var w=col.width?" style=\\"width:"+col.width+"\\"":"";',
' h+="<th"+cls+w+">"+esc(col.label)+"</th>";',
' }',
' h+="</tr></thead><tbody>";',
' for(var ri=0;ri<d.rows.length;ri++){var rr=d.rows[ri];',
' h+="<tr>";',
' for(var rci=0;rci<rr.length;rci++){var cell=rr[rci];',
' var cellCls=(typeof cell==="object"&&cell.cls)?cell.cls:(d.cols[rci]&&d.cols[rci].num?"num":"");',
' var cellTxt=(typeof cell==="object")?(cell.html||esc(cell.text||"")):esc(String(cell));',
' var cellOnclick=(typeof cell==="object"&&cell.onclick)?cell.onclick:"";',
' h+="<td"+(cellCls?" class=\\""+cellCls+"\\"":"")+(cellOnclick?" onclick=\\""+cellOnclick+"\\"":"")+">"+cellTxt+"</td>";',
' }',
' h+="</tr>";',
' }',
' if(d.totalRow){',
' h+="<tr class=\\"tbl-row-total\\">";',
' for(var ti=0;ti<d.totalRow.length;ti++){var tc=d.totalRow[ti];',
' var tcCls=(typeof tc==="object"&&tc.cls)?tc.cls:"";',
' var tcCol=(typeof tc==="object"&&tc.colspan)?" colspan=\\""+tc.colspan+"\\"":"";',
' var tcTxt=(typeof tc==="object")?esc(tc.text||""):esc(String(tc));',
' h+="<td"+(tcCls?" class=\\""+tcCls+"\\"":"")+tcCol+">"+tcTxt+"</td>";',
' }',
' h+="</tr>";',
' }',
' h+="</tbody></table></div>";',
' return h;',
'}',
'',
'/* Pattern E - Compare Side-by-Side */',
'function renderCompareCard(d){',
' var icon=d.icon||"\\u2696\\uFE0F";',
' var h="<div class=\\"card\\"><div class=\\"card-hdr\\">";',
' h+="<div class=\\"card-icon\\">"+icon+"</div>";',
' h+="<div class=\\"card-title-block\\"><div class=\\"card-title\\">"+esc(d.title||"")+"</div>";',
' if(d.subtitle)h+="<div class=\\"card-subtitle\\">"+d.subtitle+"</div>";',
' h+="</div></div>";',
' h+="<div class=\\"card-body\\" style=\\"padding:14px 16px 16px\\"><div class=\\"cmp-grid\\">";',
' for(var hi=0;hi<d.headers.length;hi++){var hdr=d.headers[hi];',
' h+="<div class=\\"cmp-cell hdr"+(hdr.num?" num":"")+"\\">"+esc(hdr.label)+"</div>";',
' }',
' for(var ri=0;ri<d.rows.length;ri++){var rr=d.rows[ri];',
' for(var ci=0;ci<rr.length;ci++){var c=rr[ci];',
' var cls=(typeof c==="object"&&c.cls)?c.cls:(d.headers[ci]&&d.headers[ci].num?"num":"");',
' var txt=(typeof c==="object")?(c.html||esc(c.text||"")):esc(String(c));',
' h+="<div class=\\"cmp-cell "+cls+"\\">"+txt+"</div>";',
' }',
' }',
' h+="</div>";',
' if(d.insight)h+="<div style=\\"margin-top:12px;font-size:12px;color:#374151;padding:10px 12px;background:#D1FAE5;border-radius:6px;border-left:3px solid #059669\\">\\uD83D\\uDCA1 "+d.insight+"</div>";',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* Pattern F - Alert Card */',
'function renderAlertCard(d){',
' var sev=d.severity||"amber";',
' var icon=d.icon||(sev==="red"?"\\uD83D\\uDD34":(sev==="green"?"\\u2705":"\\u26A0\\uFE0F"));',
' var clk=d.onclick?(" onclick=\\""+d.onclick+"\\" style=\\"cursor:pointer\\" title=\\"Click to view the journal entry\\""):"";',
' var h="<div class=\\"alert-card "+(sev!=="amber"?sev:"")+"\\""+clk+"><div class=\\"alert-icon\\">"+icon+"</div>";',
' h+="<div class=\\"alert-body\\"><div class=\\"alert-title\\">"+esc(d.title||"")+"</div>";',
' if(d.text)h+="<div class=\\"alert-text\\">"+esc(d.text)+"</div>";',
' if(d.onclick)h+="<div style=\\"font-size:11px;font-weight:600;color:#2E75B6;margin-top:4px\\">View entry \\u2192</div>";',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* Pattern G - Prose / Educational Card */',
'function renderProseCard(d){',
' var icon=d.icon||"\\uD83D\\uDCDA";',
' var h="<div class=\\"card prose-card\\">";',
' h+="<div style=\\"display:flex;align-items:center;gap:8px;margin-bottom:8px\\"><span style=\\"font-size:18px\\">"+icon+"</span>";',
' h+="<span style=\\"font-size:14px;font-weight:700;color:#1F3864\\">"+esc(d.title||"")+"</span></div>";',
' for(var i=0;i<(d.paragraphs||[]).length;i++){h+="<p>"+d.paragraphs[i]+"</p>";}',
' if(d.computedBox)h+="<p style=\\"margin-top:10px;padding:10px 12px;background:#FDF8E8;border-radius:6px;border-left:3px solid #C9A227\\">"+d.computedBox+"</p>";',
' if(d.note)h+="<p style=\\"font-size:11px;color:#6B7280;margin-top:8px;font-style:italic\\">"+esc(d.note)+"</p>";',
' h+="</div>";',
' return h;',
'}',
'',
'/* Pattern H - Suggestion Chips */',
'function renderSuggestionChips(items){',
' if(!items||!items.length)return "";',
' var h="<div class=\\"chips\\">";',
' for(var i=0;i<items.length;i++){var it=items[i];',
' var label=(typeof it==="string")?it:it.label;',
' var act=(typeof it==="object"&&it.onclick)?it.onclick:"";',
' h+="<button class=\\"chip\\""+(act?" onclick=\\""+act+"\\"":"")+">"+esc(label)+"</button>";',
' }',
' h+="</div>";',
' return h;',
'}',
'',
'/* ── Initialize ── */',
'function initialize(){',
' GL_STATE.selectedAccounts={};',
' if(typeof ssInit_==="function")ssInit_();',
' buildTopBar();',
' enterChatMode();',
' document.addEventListener("click",function(ev){var t=ev.target;var inPanel=t.closest&&(t.closest(".panel")||t.closest(".ctrl-btn")||t.closest(".ctrl-group button")||t.closest(".pill-btn")||t.closest(".more-panel")||t.closest(".filter-row"));if(!inPanel){var changed=false;if(GL_STATE.showPicker){GL_STATE.showPicker=false;var pw=document.getElementById("pickerWrap");if(pw)pw.innerHTML="";changed=true;}if(GL_STATE.showCols){GL_STATE.showCols=false;var cw=document.getElementById("colsWrap");if(cw)cw.innerHTML="";changed=true;}if(GL_STATE.showFilter){GL_STATE.showFilter=false;changed=true;}if(GL_STATE.showMore){GL_STATE.showMore=false;changed=true;}if(changed){var gp=document.getElementById("glPanels");if(gp)renderGLPanels();}var tw=document.getElementById("tbColsWrap");if(tw&&tw.innerHTML)tw.innerHTML="";var tmw=document.getElementById("tbMoreWrap");if(tmw&&tmw.innerHTML)tmw.innerHTML="";var iscw=document.getElementById("isColsWrap");if(iscw&&iscw.innerHTML)iscw.innerHTML="";var ismw=document.getElementById("isMoreWrap");if(ismw&&ismw.innerHTML)ismw.innerHTML="";var bscw=document.getElementById("bsColsWrap");if(bscw&&bscw.innerHTML)bscw.innerHTML="";var bsmw=document.getElementById("bsMoreWrap");if(bsmw&&bsmw.innerHTML)bsmw.innerHTML="";var cfmw=document.getElementById("cfMoreWrap");if(cfmw&&cfmw.innerHTML)cfmw.innerHTML="";var scemw=document.getElementById("sceMoreWrap");if(scemw&&scemw.innerHTML)scemw.innerHTML="";}});',
'}',
'',
'/* ── v3.0: Thin topbar + Sidebar + Chatbar ── */',
'function buildTopBar(){',
// Thin topbar: ☰ + brand + spacer + Export + Print
' var h="<div class=\\"topbar-v3\\">";',
' h+="<button class=\\"tb-burger\\" onclick=\\"toggleSidebar()\\" title=\\"Toggle sidebar\\">\\u2630</button>";',
' h+="<span class=\\"tb-brand\\" onclick=\\"refreshData(true)\\" title=\\"Click to refresh from the Sheet\\" style=\\"cursor:pointer\\">EC Tax Bookkeeping</span>";',
' var tbBiz=(typeof DATA!=="undefined"&&DATA.settings&&DATA.settings.businessName)?DATA.settings.businessName:"";',
' if(tbBiz)h+="<span class=\\"tb-org\\" title=\\"Your business name (from Settings)\\">"+esc(tbBiz)+"</span>";',
' h+="<span class=\\"tb-spacer\\"></span>";',
' if(typeof DATA!=="undefined"&&DATA.branches&&DATA.branches.length){',
' var bv="<span style=\\"color:rgba(255,255,255,.8);font-size:11px;margin:0 6px 0 4px\\">Branch:</span><select onchange=\\"setBranchView(this.value)\\" style=\\"background:rgba(255,255,255,.12);color:#fff;border:1px solid rgba(255,255,255,.25);border-radius:4px;padding:4px 8px;font-size:12px;font-family:inherit;margin-right:8px\\">";',
' bv+="<option value=\\"conso\\" style=\\"color:#111827;background:#fff\\""+(BRANCH_VIEW==="conso"?" selected":"")+">Consolidated</option>";',
' bv+="<option value=\\"00\\" style=\\"color:#111827;background:#fff\\""+(BRANCH_VIEW==="00"?" selected":"")+">Head Office</option>";',
' for(var bvi=0;bvi<DATA.branches.length;bvi++){var bvb=DATA.branches[bvi];bv+="<option value=\\""+bvb.no+"\\" style=\\"color:#111827;background:#fff\\""+(BRANCH_VIEW===bvb.no?" selected":"")+">"+esc(bvb.tradeName||("Branch "+bvb.no))+"</option>";}',
' bv+="</select>";h+=bv;',
' }',
' h+="<button class=\\"tb-action\\" id=\\"tbNewBtn\\" onclick=\\"ntxnMenuToggle()\\" title=\\"New Transaction (beta)\\">+ New</button>";',
' h+="<button class=\\"tb-action\\" id=\\"tbUpdateBtn\\" onclick=\\"refreshData()\\" title=\\"Re-read the Sheet and refresh in place (no re-launch)\\">Update</button>";',
' h+="<button class=\\"tb-action\\" onclick=\\"exportExcel()\\">Export Excel</button>";',
' h+="<button class=\\"tb-action\\" onclick=\\"window.print()\\">Print / PDF</button>";',
' h+="</div>";',
' document.getElementById("topbar").innerHTML=h;',
' buildSidebar();',
' buildChatbar();',
'}',
'/* Multi-branch: branch-filtered jeRows view. conso = all. Used by branch-aware reports; tax forms keep DATA.jeRows (always consolidated). */',
'function jeView_(){return (typeof BRANCH_VIEW!=="undefined"&&BRANCH_VIEW&&BRANCH_VIEW!=="conso")?DATA.jeRows.filter(function(j){return (j.branch||"00")===BRANCH_VIEW;}):DATA.jeRows;}',
'function setBranchView(v){BRANCH_VIEW=v;buildTopBar();if(typeof ACTIVE_TAB!=="undefined"&&ACTIVE_TAB)switchTab(ACTIVE_TAB);}',
'/* In-app Update: re-read the source Sheet via google.script.run, swap DATA, re-render in place (no re-launch). */',
'var REFRESH_BUSY=false;',
'function showRefreshOverlay_(){if(document.getElementById("fsRefreshOverlay"))return;var o=document.createElement("div");o.id="fsRefreshOverlay";o.className="fs-refresh-overlay";o.innerHTML="<span class=\\"fs-refresh-dots\\"><i></i><i></i><i></i></span>";document.body.appendChild(o);}',
'function hideRefreshOverlay_(){var o=document.getElementById("fsRefreshOverlay");if(o&&o.parentNode)o.parentNode.removeChild(o);}',
'function refreshData(fullscreen){',
' if(REFRESH_BUSY)return;',
' if(typeof google==="undefined"||!google.script||!google.script.run){refreshToast_("Update needs the live WebApp (open via the Sheet menu link).",true);return;}',
' if(!DATA||!DATA.spreadsheetId){refreshToast_("Update unavailable - re-deploy the launcher and Generate Report once to enable it.",true);return;}',
' REFRESH_BUSY=true;',
' if(fullscreen===true)showRefreshOverlay_();',
' var b=document.getElementById("tbUpdateBtn");if(b){b.style.minWidth=b.offsetWidth+"px";b.disabled=true;b.innerHTML="<span class=\\"tb-dots\\"><i></i><i></i><i></i></span>";}',
' google.script.run.withSuccessHandler(applyRefresh_).withFailureHandler(refreshErr_).apiRefresh(DATA.spreadsheetId);',
'}',
'function refreshDone_(){REFRESH_BUSY=false;hideRefreshOverlay_();var b=document.getElementById("tbUpdateBtn");if(b){b.disabled=false;b.innerHTML="Update";b.style.minWidth="";}}',
/* Re-render the current report tab in place without touching sidebar state / ACTIVE_GROUP / skeleton / scroll. Used by Update. */
'function rerenderActiveTab_(){',
' var tab=ACTIVE_TAB;if(!tab)return;',
' var prev=(typeof SKEL_SUPPRESS!=="undefined")?SKEL_SUPPRESS:false;SKEL_SUPPRESS=true;',
' try{ensureReportDOM();if(tab==="tb")buildTB();else if(tab==="gl")buildGL();else if(tab==="is")buildIS();else if(tab==="bs")buildBS();else if(tab==="cf")buildCF();else if(tab==="sce")buildSCE();else if(tab==="fs")buildFS();else if(tab==="subledger")buildSUBL();else if(tab==="newtxn")buildNewTxn();else if(tab==="db")buildDB();else if(tab==="bme")buildBME();else if(tab==="1702q")build1702QC();else if(tab==="1701q")build1701Q();else if(tab==="2551q")build2551Q();else if(tab==="2550q")build2550Q();else if(tab==="crb_book"||tab==="cdb_book"||tab==="gj_book"||tab==="gl_book"||tab==="sj_book"||tab==="ssj_book"||tab==="pj_book"||tab==="spj_book")buildBookPrint(tab);else if(tab==="slsp")renderSLS();else if(tab==="slp")renderSLP();else if(tab==="coa")buildCOA();else if(tab==="settings")buildSettingsView();else if(tab==="taxcal")buildTaxCalendar();else if(tab==="txnreg")buildTxnReg();}finally{SKEL_SUPPRESS=prev;}',
'}',
'function applyRefresh_(p){',
' refreshDone_();',
' if(!p||p.__error){refreshToast_("Update failed: "+((p&&p.__error)||"no data"),true);return;}',
' DATA=p;',
' if(typeof runAnomalyDaemon==="function"){try{runAnomalyDaemon();}catch(e){}}',
' var _m=document.getElementById("main");var _ms=_m?_m.scrollTop:0;',
' buildTopBar();', /* re-renders topbar + sidebar from current SIDEBAR_STATE (no expand/collapse change) */
' if(typeof CONV_STATE!=="undefined"&&CONV_STATE.mode==="report"&&typeof ACTIVE_TAB!=="undefined"&&ACTIVE_TAB){rerenderActiveTab_();if(_m)_m.scrollTop=_ms;}',
' else{var ci=document.getElementById("convInner");if(ci)ci.innerHTML="";if(typeof CONV_STATE!=="undefined"){CONV_STATE.welcomeShown=false;CONV_STATE.messages=[];}if(typeof enterChatMode==="function")enterChatMode();}',
'}',
'function refreshErr_(e){refreshDone_();refreshToast_("Update failed: "+((e&&e.message)||e||"unknown")+" - make sure the WebApp is deployed with access to the Sheet.",true);}',
'function refreshToast_(msg,isErr){',
' var fb=document.getElementById("chatFeedback");if(fb){fb.className="chat-feedback "+(isErr?"error":"success");fb.textContent=(isErr?"\\u26A0 ":"\\u2713 ")+msg;setTimeout(function(){if(fb)fb.textContent="";},isErr?5000:2800);}',
' var t=document.createElement("div");t.style.cssText="position:fixed;top:60px;right:20px;z-index:9999;background:"+(isErr?"#FEF2F2":"#ECFDF5")+";color:"+(isErr?"#991B1B":"#065F46")+";border:1px solid "+(isErr?"#FECACA":"#A7F3D0")+";border-radius:8px;padding:10px 14px;font-size:13px;font-weight:600;box-shadow:0 4px 16px rgba(0,0,0,.12);max-width:360px;";t.textContent=(isErr?"\\u26A0 ":"\\u2713 ")+msg;document.body.appendChild(t);setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t);},isErr?5000:2800);',
'}',
'',
'/* Build sidebar - collapsed icon strip + expanded full nav + history */',
'function buildSidebar(){',
' var iconMap={books:"\\uD83D\\uDCDA",reports:"\\uD83D\\uDCCA",taxreturns:"\\uD83D\\uDCCB",setup:"\\u2699\\uFE0F"};',
' var labelMap={books:"Books",reports:"Reports",taxreturns:"Tax",setup:"Setup"};',
' var h="";',
// Collapsed icon strip
' h+="<div class=\\"icon-strip\\">";',
' /* v4.0: Chat icon at top of strip */',
' var chatActive=(typeof CONV_STATE!=="undefined"&&CONV_STATE.mode==="chat");',
' h+="<button class=\\"icon-btn"+(chatActive?" active":"")+"\\" onclick=\\"expandSidebar();enterChatMode()\\" title=\\"Chat\\">\\uD83D\\uDCAC</button>";',
' h+="<div class=\\"icon-strip-label\\">Chat</div>";',
' for(var ig=0;ig<NAV_GROUPS.length;ig++){var ig_g=NAV_GROUPS[ig];var ig_act=ig_g.key===ACTIVE_GROUP&&!chatActive;',
' h+="<button class=\\"icon-btn"+(ig_act?" active":"")+"\\" onclick=\\"expandSidebar();switchGroup(\'"+ig_g.key+"\')\\" title=\\""+ig_g.label+"\\">"+iconMap[ig_g.key]+"</button>";',
' h+="<div class=\\"icon-strip-label\\">"+labelMap[ig_g.key]+"</div>";',
' }',
' h+="<button class=\\"icon-btn\\" onclick=\\"toggleSidebar()\\" title=\\"History\\">\\uD83D\\uDCDC</button>";',
' h+="<div class=\\"icon-strip-label\\">History</div>";',
' h+="</div>";',
// Expanded sidebar content
' h+="<div class=\\"sb-content\\">";',
' /* v4.0: Chat item at top of expanded sidebar */',
' var chatActiveExp=(typeof CONV_STATE!=="undefined"&&CONV_STATE.mode==="chat");',
' h+="<div class=\\"nav-section\\" id=\\"navsec-chat\\"><div class=\\"nav-item"+(chatActiveExp?" active":"")+"\\" onclick=\\"enterChatMode()\\" style=\\"padding-left:16px;font-weight:600\\"><span class=\\"nav-ico\\">\\uD83D\\uDCAC</span> Chat</div></div>";',
' /* Dashboard item - directly after Chat */',
' var dbActiveExp=(!chatActiveExp&&ACTIVE_TAB==="db");',
' h+="<div class=\\"nav-section\\" id=\\"navsec-db\\"><div class=\\"nav-item"+(dbActiveExp?" active":"")+"\\" onclick=\\"switchTab(\'db\')\\" style=\\"padding-left:16px;font-weight:600\\"><span class=\\"nav-ico\\">\\uD83D\\uDCCA</span> Dashboard</div></div>";',
' /* Tax Calendar - top-level, after Dashboard */',
' var taxcalActiveExp=(!chatActiveExp&&ACTIVE_TAB==="taxcal");',
' h+="<div class=\\"nav-section\\" id=\\"navsec-taxcal\\"><div class=\\"nav-item"+(taxcalActiveExp?" active":"")+"\\" onclick=\\"switchTab(\'taxcal\')\\" style=\\"padding-left:16px;font-weight:600\\"><span class=\\"nav-ico\\">\\uD83D\\uDCC5</span> Tax Calendar</div></div>";',
' /* Bookkeeping Made Easy - directly under Dashboard */',
' var bmeActiveExp=(!chatActiveExp&&ACTIVE_TAB==="bme");',
' h+="<div class=\\"nav-section\\" id=\\"navsec-bme\\"><div class=\\"nav-item"+(bmeActiveExp?" active":"")+"\\" onclick=\\"switchTab(\'bme\')\\" style=\\"padding-left:16px;font-weight:600\\"><span class=\\"nav-ico\\">\\uD83D\\uDCDD</span> Bookkeeping Made Easy</div></div>";',
' /* New Transaction (write-back) - BETA: hidden from nav; reachable via the "+ New" button in the topbar. */',
' h+="<div class=\\"sb-divider\\"></div>";',
' for(var ng=0;ng<NAV_GROUPS.length;ng++){var grp=NAV_GROUPS[ng];',
' var collapsed=SIDEBAR_STATE.sectionsCollapsed[grp.key]===true;',
' h+="<div class=\\"nav-section"+(collapsed?" collapsed":"")+"\\" id=\\"navsec-"+grp.key+"\\">";',
' h+="<div class=\\"nav-section-hdr\\" onclick=\\"toggleNavSection(\'"+grp.key+"\')\\">";',
' h+="<span class=\\"nav-ico\\">"+iconMap[grp.key]+"</span> "+grp.label;',
' h+="<span class=\\"nav-chev\\">\\u25BE</span>";',
' h+="</div>";',
' h+="<div class=\\"nav-section-body\\">";',
' for(var ti=0;ti<grp.tabs.length;ti++){var t=grp.tabs[ti];var ng_rt=String((typeof DATA!=="undefined"&&DATA.settings&&(DATA.settings.regType||DATA.settings.registrationType))||"").toUpperCase();var ng_hide=false;if(ng_rt){if(ng_rt.indexOf("NON")>=0){if((t.separator&&/VAT Subsidiary/i.test(t.label||""))||["ssj_book","spj_book","slsp","slp","2550q"].indexOf(t.key)>=0)ng_hide=true;}else if(t.key==="2551q")ng_hide=true;}var ng_tc=String((typeof DATA!=="undefined"&&DATA.settings&&DATA.settings.taxpayerClass)||"").toUpperCase();if(ng_tc){if(ng_tc.indexOf("NON")===0){if(["1701q","1701a","1701ms","1701"].indexOf(t.key)>=0)ng_hide=true;}else{if(t.key==="1702q")ng_hide=true;}}if(ng_hide)continue;if(t.separator){h+="<div style=\\"padding:9px 14px 3px 16px;font-size:9px;font-weight:700;color:#9CA3AF;text-transform:uppercase;letter-spacing:.5px;border-top:1px solid #E5E7EB;margin:6px 8px 2px;\\">"+t.label+"</div>";continue;}var actT=t.key===ACTIVE_TAB;var dis=!!t.disabled;',
' var click=dis?"":" onclick=\\"switchTab(\'"+t.key+"\')\\"";',
' var badge=dis?" <span class=\\"nav-item-badge\\">soon</span>":"";',
' h+="<div class=\\"nav-item"+(actT?" active":"")+(dis?" disabled":"")+"\\""+click+">"+t.label+badge+"</div>";',
' }',
' h+="</div></div>";',
' }',
// Divider + History
' h+="<div class=\\"sb-divider\\"></div>";',
' var histCollapsed=SIDEBAR_STATE.sectionsCollapsed.history===true;',
' h+="<div class=\\"nav-section"+(histCollapsed?" collapsed":"")+"\\" id=\\"navsec-history\\">";',
' h+="<div class=\\"nav-section-hdr\\" onclick=\\"toggleNavSection(\'history\')\\">";',
' h+="<span class=\\"nav-ico\\">\\uD83D\\uDCDC</span> History";',
' h+="<span class=\\"nav-chev\\">\\u25BE</span>";',
' h+="</div>";',
' h+="<div class=\\"nav-section-body\\">";',
' if(CHAT_HISTORY.length===0){h+="<div class=\\"history-empty\\">No queries yet. Type below to start.</div>";}',
' else{for(var hi=0;hi<Math.min(CHAT_HISTORY.length,15);hi++){var hitem=CHAT_HISTORY[hi];',
' h+="<div class=\\"history-item\\" onclick=\\"rerunHistory("+hi+")\\" title=\\""+esc(hitem.query)+"\\">";',
' h+="<span class=\\"history-item-text\\">"+esc(hitem.query)+"</span>";',
' h+="<span class=\\"history-item-meta\\">"+formatRelTime(hitem.ts)+"</span>";',
' h+="</div>";',
' }}',
' h+="</div></div>";',
' h+="<div class=\\"sb-divider\\"></div>";',
' h+="<div style=\\"padding:10px 14px 16px;\\">";',
' h+="<div style=\\"font-size:11px;font-weight:700;color:#374151;margin-bottom:6px;\\">Help & Feedback</div>";',
' h+="<a href=\\"https://www.facebook.com/ectaxph\\" target=\\"_blank\\" rel=\\"noopener\\" title=\\"May issue sa system? I-click para mag-message sa EC TAXPH Facebook page. Para ma-diagnose, i-share ang iyong Google Sheet sa ed@ectaxph.com (View access lang). Data privacy: gagamitin LANG para ayusin ang isyu, hindi ipapasa sa iba, at aalisin ang access pagkatapos. Hindi kami humihingi ng password.\\" style=\\"display:flex;align-items:center;gap:7px;justify-content:center;background:#1F3A5F;color:#fff;text-decoration:none;font-size:12px;font-weight:600;padding:8px 10px;border-radius:8px;\\">\\uD83D\\uDCAC Message EC TAXPH</a>";',
' h+="<div style=\\"font-size:10px;color:#9CA3AF;line-height:1.5;margin-top:6px;\\">May issue? I-hover/click ang button para sa detalye + data privacy.</div>";',
' h+="</div>";',
' h+="</div>";',
' var _sbEl=document.getElementById("sidebar");var _sbc=_sbEl&&_sbEl.querySelector(".sb-content");var _sbScroll=_sbc?_sbc.scrollTop:0;_sbEl.innerHTML=h;var _sbc2=_sbEl.querySelector(".sb-content");if(_sbc2)_sbc2.scrollTop=_sbScroll;',
'}',
'',
'/* Build chatbar - floating bottom Claude-style */',
'function buildChatbar(){',
' var h="<div class=\\"chatbar-inner\\">";',
' h+="<div class=\\"chat-input-wrap\\">";',
' h+="<input type=\\"text\\" class=\\"chat-input\\" id=\\"chatInput\\" placeholder=\\"Tanong: kita ko Q1? Or \'trial balance jan 2026 vs dec 2025\'...\\" onkeydown=\\"if(event.key===\'Enter\')executeChat()\\">";',
' h+="<button class=\\"chat-send\\" onclick=\\"executeChat()\\" title=\\"Send\\">\\u21B5</button>";',
' h+="</div>";',
' h+="<div class=\\"chat-feedback\\" id=\\"chatFeedback\\"></div>";',
' h+="</div>";',
' document.getElementById("chatbar").innerHTML=h;',
'}',
'',
'function toggleSidebar(){',
' var sb=document.getElementById("sidebar");',
' var main=document.getElementById("main");',
' var cb=document.getElementById("chatbar");',
' if(sb)sb.classList.toggle("collapsed");',
' if(main)main.classList.toggle("expanded");',
' if(cb)cb.classList.toggle("expanded");',
'}',
'',
'/* v4.0: Force sidebar to expanded state (idempotent) */',
'function expandSidebar(){',
' var sb=document.getElementById("sidebar");',
' var main=document.getElementById("main");',
' var cb=document.getElementById("chatbar");',
' if(sb)sb.classList.remove("collapsed");',
' if(main)main.classList.remove("expanded");',
' if(cb)cb.classList.remove("expanded");',
'}',
'',
'function toggleNavSection(key){',
' if(!SIDEBAR_STATE.sectionsCollapsed[key])SIDEBAR_STATE.sectionsCollapsed[key]=true;',
' else delete SIDEBAR_STATE.sectionsCollapsed[key];',
' buildSidebar();',
'}',
'',
'function formatRelTime(ts){',
' var diff=(Date.now()-ts)/1000;',
' if(diff<60)return "just now";',
' if(diff<3600)return Math.floor(diff/60)+" min ago";',
' if(diff<86400)return Math.floor(diff/3600)+" hr ago";',
' return Math.floor(diff/86400)+"d ago";',
'}',
'',
'/* ── switchGroup: v3.0 - update sidebar instead of topbar ── */',
'function switchGroup(groupKey){',
' ACTIVE_GROUP=groupKey;',
' var grp=null;for(var i=0;i<NAV_GROUPS.length;i++){if(NAV_GROUPS[i].key===groupKey){grp=NAV_GROUPS[i];break;}}',
' if(!grp){buildSidebar();return;}',
// Auto-expand the clicked section
' delete SIDEBAR_STATE.sectionsCollapsed[groupKey];',
// Check if current ACTIVE_TAB belongs to this group
' var inGroup=false;for(var j=0;j<grp.tabs.length;j++){if(grp.tabs[j].key===ACTIVE_TAB&&!grp.tabs[j].disabled){inGroup=true;break;}}',
' if(inGroup){buildSidebar();return;}',
// Find first enabled tab in group
' var firstEnabled=null;for(var k=0;k<grp.tabs.length;k++){if(!grp.tabs[k].disabled){firstEnabled=grp.tabs[k];break;}}',
' if(firstEnabled){switchTab(firstEnabled.key);return;}',
// No enabled tabs - show coming-soon placeholder
' buildSidebar();',
' document.getElementById("controls").innerHTML="";',
' document.getElementById("report").innerHTML="<div style=\\"padding:60px 20px;text-align:center;color:#6B7280;background:white;border-radius:10px;margin:0 auto;max-width:600px;box-shadow:0 1px 3px rgba(0,0,0,.04);\\"><div style=\\"font-size:42px;margin-bottom:16px\\">\\uD83D\\uDEA7</div><h2 style=\\"color:#1F3864;font-size:18px;margin:0 0 10px;font-weight:600\\">"+grp.label+" \\u2014 Coming Soon</h2><p style=\\"font-size:13px;line-height:1.6;color:#6B7280;margin:0\\">This section will include "+grp.tabs.map(function(t){return t.label;}).join(", ")+". Stay tuned!</p></div>";',
'}',
'/* === SKELETON LOADING + FADE TRANSITIONS === */',
'var LAST_ROW_COUNT={tb:30,gl:30,is:20,bs:25,cf:15,sce:10,"1701q":15,fs:30,crb_book:30,cdb_book:30,gj_book:30,gl_book:30};',
'var SKEL_SUPPRESS=false;',
'var SWITCH_TIMER=null;',
'function showSkeleton(tab){',
' var panel=document.getElementById("reportPanel");',
' var rep=document.getElementById("report");',
' if(panel)panel.classList.add("skel-fade-in-start");',
' if(rep)rep.innerHTML="";',
'}',
'function fadeInReport(tab){',
' var panel=document.getElementById("reportPanel");',
' var rep=document.getElementById("report");',
' if(tab&&rep&&rep.innerHTML){',
' var m=rep.innerHTML.match(/<tr/g);',
' if(m&&m.length>0)LAST_ROW_COUNT[tab]=Math.min(30,Math.max(5,m.length));',
' }',
' if(!panel)return;',
' panel.classList.add("skel-fade-in-start");',
' requestAnimationFrame(function(){',
' requestAnimationFrame(function(){',
' panel.classList.remove("skel-fade-in-start");',
' });',
' });',
'}',
'function quietRecompute(tab){',
' var prev=SKEL_SUPPRESS;SKEL_SUPPRESS=true;',
' try{',
' if(tab==="tb")recomputeTB();',
' else if(tab==="gl")recomputeGL();',
' else if(tab==="is")recomputeIS();',
' else if(tab==="bs")recomputeBS();',
' else if(tab==="cf")recomputeCF();',
' else if(tab==="sce")recomputeSCE();',
' else if(tab==="fs")recomputeFS();',
' }finally{SKEL_SUPPRESS=prev;}',
'}',
'var SEARCH_DEBOUNCE={};',
'function debouncedSearch(tab,ms){',
' clearTimeout(SEARCH_DEBOUNCE[tab]);',
' SEARCH_DEBOUNCE[tab]=setTimeout(function(){quietRecompute(tab);},ms||200);',
'}',
'function switchTab(tab){',
' if(tab!=="gl")DRILL_CONTEXT=null;',
' ACTIVE_TAB=tab;',
' if(typeof enterReportMode==="function")enterReportMode();',
// Sync ACTIVE_GROUP based on tab membership
' var __fg="";for(var ng=0;ng<NAV_GROUPS.length;ng++){var g=NAV_GROUPS[ng];for(var nt=0;nt<g.tabs.length;nt++){if(g.tabs[nt].key===tab){__fg=g.key;break;}}if(__fg)break;}ACTIVE_GROUP=__fg;',
// Auto-expand the active group section ONLY for tabs that belong to a group (not standalone bme/db/chat)
' if(__fg)delete SIDEBAR_STATE.sectionsCollapsed[ACTIVE_GROUP];',
' buildSidebar();',
' showSkeleton(tab);',
' SKEL_SUPPRESS=true;',
' if(SWITCH_TIMER){clearTimeout(SWITCH_TIMER);SWITCH_TIMER=null;}',
' SWITCH_TIMER=setTimeout(function(){',
' SWITCH_TIMER=null;',
' ensureReportDOM();if(tab==="tb")buildTB();else if(tab==="gl")buildGL();else if(tab==="is")buildIS();else if(tab==="bs")buildBS();else if(tab==="cf")buildCF();else if(tab==="sce")buildSCE();else if(tab==="fs")buildFS();else if(tab==="subledger")buildSUBL();else if(tab==="newtxn")buildNewTxn();else if(tab==="db")buildDB();else if(tab==="bme")buildBME();else if(tab==="1702q")build1702QC();else if(tab==="1701q")build1701Q();else if(tab==="2551q")build2551Q();else if(tab==="2550q")build2550Q();else if(tab==="crb_book"||tab==="cdb_book"||tab==="gj_book"||tab==="gl_book"||tab==="sj_book"||tab==="ssj_book"||tab==="pj_book"||tab==="spj_book")buildBookPrint(tab);else if(tab==="slsp")renderSLS();else if(tab==="slp")renderSLP();else if(tab==="coa")buildCOA();else if(tab==="settings")buildSettingsView();else if(tab==="taxcal")buildTaxCalendar();else if(tab==="txnreg")buildTxnReg();',
' SKEL_SUPPRESS=false;',
' fadeInReport(tab);',
' },250);',
'}',
'',
'/* ── TB Column Definitions ── */',
'var TB_COLS=[',
' {id:"code",label:"Code",on:false},',
' {id:"trend",label:"Trend",on:false},',
' {id:"fsGroup",label:"FS Group",on:false},',
' {id:"acctType",label:"Account Type",on:false},',
' {id:"taxLine",label:"Tax Line (1701)",on:false},',
' {id:"taxLine2551",label:"Tax Line (2551Q)",on:false},',
' {id:"noteGroup",label:"Note Group",on:false}',
'];',
'',
'/* ── Dropdown button label helpers (with count) ── */',
'function tbColsLabel(){var n=TB_COLS.filter(function(c){return c.on}).length;return n>0?"Columns ("+n+") \\u25BE":"Columns \\u25BE";}',
'function glColsLabel(){var n=GL_COLS.filter(function(c){return c.on&&!c.core}).length;return n>0?"Columns ("+n+") \\u25BE":"Columns \\u25BE";}',
'function glAcctLabel(){var n=Object.keys(GL_STATE.selectedAccounts).filter(function(k){return GL_STATE.selectedAccounts[k]}).length;var tot=DATA.coa.filter(function(a){return!a.active||String(a.active).toUpperCase()!=="NO"}).length;return n===0?"Accounts \\u25BE":n===tot?"Accounts (All) \\u25BE":"Accounts ("+n+") \\u25BE";}',
'function updateTBColsBtn(){var w=document.getElementById("tbColsBtnWrap");if(w)w.innerHTML=renderTBColsBtn();}',
'function updateGLColsBtn(){var b=document.getElementById("glColsBtn");if(b)b.innerHTML=glColsLabel();}',
'function updateGLAcctBtn(){var b=document.getElementById("glAcctBtn");if(b)b.innerHTML=glAcctLabel();}',
'',
'/* ── Hide Decimals toggle (global) ── */',
'function renderHideDecimalsToggle(){return "<div class=\\"ctrl-spacer\\"></div><div class=\\"ctrl-group\\"><label> </label><div class=\\"toggle-wrap\\"><span class=\\"toggle-label\\">Hide Decimals</span><label class=\\"toggle-switch\\"><input type=\\"checkbox\\" "+(APP_STATE.hideDecimals?"checked":"")+" onchange=\\"toggleHideDecimals(this.checked)\\"><span class=\\"toggle-slider\\"></span></label></div></div>";}',
'function toggleHideDecimals(v){APP_STATE.hideDecimals=v;quietRecompute(ACTIVE_TAB);}',
'',
'/* ══════════════════════════════════════',
' TRIAL BALANCE',
' ══════════════════════════════════════ */',
'/* ── TB View/Net/Sort/Var/Sections helpers + More panel ── */',
// Section collapse
'function toggleTBSection(k){TB_STATE.collapsed[k]=!TB_STATE.collapsed[k];quietRecompute("tb");}',
'function tbAnyExpanded(){var keys=["Asset","Liability","Equity"];if(!TB_STATE.netToggle){keys.push("Income","Expense");}else{keys.push("NetIncome");if(!TB_STATE.collapsed.NetIncome){keys.push("Income","Expense");}}for(var i=0;i<keys.length;i++){if(!TB_STATE.collapsed[keys[i]])return true;}return false;}',
// State mutation functions (also re-render More panel to reflect new state)
'function toggleTBView(){TB_STATE.view=TB_STATE.view==="classic"?"flat":"classic";refreshTBMorePanel();quietRecompute("tb");}',
'function toggleTBNet(){TB_STATE.netToggle=!TB_STATE.netToggle;if(TB_STATE.netToggle)TB_STATE.collapsed.NetIncome=true;refreshTBMorePanel();quietRecompute("tb");}',
'function toggleTBVar(){TB_STATE.showVar=!TB_STATE.showVar;refreshTBMorePanel();quietRecompute("tb");}',
'function toggleTBAllSections(){var collapse=tbAnyExpanded();var keys=["Asset","Liability","Equity","Income","Expense","NetIncome"];for(var i=0;i<keys.length;i++)TB_STATE.collapsed[keys[i]]=collapse;refreshTBMorePanel();quietRecompute("tb");}',
// Sort
'function tbSortBy(col){if(TB_STATE.sortCol===col){if(TB_STATE.sortDir==="asc")TB_STATE.sortDir="desc";else{TB_STATE.sortCol=null;TB_STATE.sortDir="asc";}}else{TB_STATE.sortCol=col;TB_STATE.sortDir="asc";}quietRecompute("tb");}',
'function tbSortArrow(col){if(TB_STATE.sortCol!==col)return" <span class=\\"sort-arrow\\">\\u25B4\\u25BE</span>";return" <span class=\\"sort-arrow active\\">"+(TB_STATE.sortDir==="asc"?"\\u25B2":"\\u25BC")+"</span>";}',
// Columns button (in main bar)
'function renderTBColsBtn(){var n=TB_COLS.filter(function(c){return c.on}).length;var lab=n>0?"Columns ("+n+") \\u25BE":"Columns \\u25BE";var inner="<button id=\\"tbColsBtn\\" class=\\"ctrl-dropdown-btn\\" onclick=\\"toggleTBCols()\\" style=\\""+(n>0?"border-right:none;border-radius:4px 0 0 4px":"")+"\\">"+lab+"</button>";if(n>0)inner+="<button class=\\"ctrl-dropdown-btn\\" onclick=\\"clearTBCols()\\" title=\\"Clear columns\\" style=\\"border-radius:0 4px 4px 0;padding:5px 8px;min-width:auto;color:#6B7280;text-align:center\\">\\u2715</button>";return"<div style=\\"display:flex;align-items:stretch\\">"+inner+"</div>";}',
'function clearTBCols(){for(var i=0;i<TB_COLS.length;i++)TB_COLS[i].on=false;var w=document.getElementById("tbColsWrap");if(w)w.innerHTML="";updateTBColsBtn();quietRecompute("tb");}',
// More panel
'function toggleTBMore(){var wrap=document.getElementById("tbMoreWrap");if(wrap.innerHTML){wrap.innerHTML="";return;}wrap.innerHTML=renderTBMorePanel();}',
'function refreshTBMorePanel(){var wrap=document.getElementById("tbMoreWrap");if(wrap&&wrap.innerHTML)wrap.innerHTML=renderTBMorePanel();}',
'function renderTBMorePanel(){',
' var cmp=document.getElementById("tbCmp"),cmpVal=cmp?cmp.value:"none";',
' var h="<div class=\\"panel\\" style=\\"width:280px;right:0;left:auto\\">";',
' h+="<div class=\\"panel-hdr\\"><span class=\\"panel-title\\">More</span><button class=\\"panel-close\\" onclick=\\"toggleTBMore()\\">\\u2715</button></div>";',
// Row: View (bilateral toggle - Grouped left, List right)
' var vList=TB_STATE.view==="flat";',
' var gClass=vList?"bi-label":"bi-label active";',
' var lClass=vList?"bi-label active":"bi-label";',
' h+="<div class=\\"more-row\\"><span class=\\"more-row-label\\">View</span><div class=\\"toggle-bilateral\\"><span class=\\""+gClass+"\\">Grouped</span><label class=\\"toggle-switch\\"><input type=\\"checkbox\\" "+(vList?"checked":"")+" onchange=\\"toggleTBView()\\"><span class=\\"toggle-slider\\"></span></label><span class=\\""+lClass+"\\">List</span></div></div>";',
// Row: Net Income (single button, cycles label)
' var niOn=TB_STATE.netToggle;var niLab=niOn?"Show Income & Expense":"Show Net Income";',
' h+="<div class=\\"more-row\\"><span class=\\"more-row-label\\">Net Income</span><button class=\\"more-action-btn\\" onclick=\\"toggleTBNet()\\">"+niLab+"</button></div>";',
// Row: Sections (Collapse All / Expand All smart button)
' var anyExp=tbAnyExpanded();var secLab=anyExp?"Collapse All":"Expand All";',
' h+="<div class=\\"more-row\\"><span class=\\"more-row-label\\">Sections</span><button class=\\"more-action-btn\\" onclick=\\"toggleTBAllSections()\\">"+secLab+"</button></div>";',
// Row: Variance (toggle - only when Compare ≠ None)
' if(cmpVal!=="none"){',
' h+="<div class=\\"more-row\\"><span class=\\"more-row-label\\">Variance</span><label class=\\"toggle-switch\\"><input type=\\"checkbox\\" "+(TB_STATE.showVar?"checked":"")+" onchange=\\"toggleTBVar()\\"><span class=\\"toggle-slider\\"></span></label></div>";',
' }',
// Row: Hide Decimals (toggle - existing APP_STATE)
' h+="<div class=\\"more-row\\"><span class=\\"more-row-label\\">Hide Decimals</span><label class=\\"toggle-switch\\"><input type=\\"checkbox\\" "+(APP_STATE.hideDecimals?"checked":"")+" onchange=\\"toggleHideDecimalsFromMore(this.checked)\\"><span class=\\"toggle-slider\\"></span></label></div>";',
' h+="</div>";',
' return h;',
'}',
'function toggleHideDecimalsFromMore(v){APP_STATE.hideDecimals=v;quietRecompute("tb");}',
'function tbSortRows(rows){if(!TB_STATE.sortCol)return rows;var sorted=rows.slice();var dir=TB_STATE.sortDir==="asc"?1:-1;var col=TB_STATE.sortCol;sorted.sort(function(a,b){var av,bv;if(col==="dr")return(a.dr-b.dr)*dir;if(col==="cr")return(a.cr-b.cr)*dir;if(col==="code"){av=a.code||"";bv=b.code||"";}else if(col==="title"){av=a.title||"";bv=b.title||"";}else{av=a[col]||"";bv=b[col]||"";}return String(av).localeCompare(String(bv))*dir;});return sorted;}',
'',
'function buildTB(){ensureReportDOM();buildTBControls();recomputeTB();}',
'function buildTBControls(){',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(function(d){return d&&d.length>=10}).sort();',
' var maxD=dates[dates.length-1]||"2026-12-31";',
' var today=new Date();var todayStr=today.getFullYear()+"-"+pad(today.getMonth()+1)+"-"+pad(today.getDate());',
' var h="<div class=\\"ctrl-bar\\" style=\\"max-width:8.5in;margin:0 auto\\">";',
' h+="<div class=\\"ctrl-group\\"><label>As-of Date</label><select id=\\"tbPreset\\" onchange=\\"applyTBPreset()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' h+="<option value=\\"today\\">Today</option>";',
' h+="<option value=\\"latest\\">Latest Transaction</option>";',
' h+="<option value=\\"eolm\\">End of Last Month</option>";',
' h+="<option value=\\"eolq\\">End of Last Quarter</option>";',
' h+="<option value=\\"eotq\\">End of This Quarter</option>";',
' h+="<option value=\\"tfy\\" selected>This Financial Year</option>";',
' h+="<option value=\\"lfy\\">Last Financial Year</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"q1\\">Quarter 1</option><option value=\\"q2\\">Quarter 2</option><option value=\\"q3\\">Quarter 3</option><option value=\\"q4\\">Quarter 4</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"jan\\">January</option><option value=\\"feb\\">February</option><option value=\\"mar\\">March</option><option value=\\"apr\\">April</option><option value=\\"may\\">May</option><option value=\\"jun\\">June</option><option value=\\"jul\\">July</option><option value=\\"aug\\">August</option><option value=\\"sep\\">September</option><option value=\\"oct\\">October</option><option value=\\"nov\\">November</option><option value=\\"dec\\">December</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"custom\\">Custom</option></select></div>";',
' h+="<div class=\\"ctrl-group\\"><label> </label><input type=\\"date\\" id=\\"tbAsOf\\" value=\\""+maxD+"\\" onchange=\\"document.getElementById(\'tbPreset\').value=\'custom\';recomputeTB()\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Compare</label><select id=\\"tbCmp\\" onchange=\\"onTBCmpChange()\\">";',
' h+="<option value=\\"sppy\\">Same Period Last Year</option><option value=\\"fpy\\">Full Prior Year</option><option value=\\"pq\\">Previous Quarter</option>";',
' h+="<option value=\\"multi\\">Multi-Period</option><option value=\\"custom\\">Custom</option><option value=\\"none\\" selected>None</option></select></div>";',
' h+="<div class=\\"ctrl-group\\" style=\\"position:relative\\"><label>Columns</label><div id=\\"tbColsBtnWrap\\">"+renderTBColsBtn()+"</div><div id=\\"tbColsWrap\\"></div></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Search</label><input type=\\"text\\" id=\\"tbSearch\\" placeholder=\\"Type account name...\\" oninput=\\"debouncedSearch('tb')\\" style=\\"min-width:150px\\"></div>";',
' h+="<div class=\\"ctrl-group\\" style=\\"position:relative\\"><label> </label><button id=\\"tbMoreBtn\\" class=\\"ctrl-dropdown-btn\\" onclick=\\"toggleTBMore()\\">More \\u25BE</button><div id=\\"tbMoreWrap\\"></div></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"tbMultiIntGrp\\" style=\\"display:none\\"><label>Interval</label><select id=\\"tbMultiInt\\" onchange=\\"recomputeTB()\\">";',
' h+="<option value=\\"monthly\\">Monthly</option><option value=\\"quarterly\\">Quarterly</option><option value=\\"annually\\">Annually</option></select></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"tbMultiNumGrp\\" style=\\"display:none\\"><label>Periods</label><select id=\\"tbMultiNum\\" onchange=\\"recomputeTB()\\">";',
' for(var p=2;p<=12;p++)h+="<option value=\\""+p+"\\""+(p===4?" selected":"")+">"+p+"</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"tbCustomGrp\\" style=\\"display:none\\"><label>Custom As-of</label><input type=\\"date\\" id=\\"tbCustomDate\\" value=\\""+maxD+"\\" onchange=\\"recomputeTB()\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Zoom</label><div style=\\"display:flex;gap:4px;align-items:center\\"><button onclick=\\"rptZoom(-0.1)\\" class=\\"ctrl-btn\\" style=\\"padding:5px 10px;font-size:14px;line-height:1\\" title=\\"Zoom out\\">−</button><span id=\\"rptZoomPct\\" onclick=\\"rptZoomReset()\\" title=\\"Click to reset to 100% (PDF size)\\" style=\\"cursor:pointer;min-width:46px;text-align:center;font-size:12px;font-weight:600;color:var(--navy)\\">100%</span><button onclick=\\"rptZoom(0.1)\\" class=\\"ctrl-btn\\" style=\\"padding:5px 10px;font-size:14px;line-height:1\\" title=\\"Zoom in\\">+</button></div></div>";',
' h+="</div>";',
' document.getElementById("controls").innerHTML=h;',
' applyTBPreset();',
'}',
'',
'/* ── TB Preset ── */',
'function applyTBPreset(){',
' var v=document.getElementById("tbPreset").value;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' var maxD=dates[dates.length-1]||"2026-12-31";',
' var today=new Date();var todayStr=today.getFullYear()+"-"+pad(today.getMonth()+1)+"-"+pad(today.getDate());',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var d;',
' if(v==="custom"){d=document.getElementById("tbAsOf").value;}',
' else{var r=computeDateRange(v,maxD,fyM,todayStr,dates[0]);d=r?r.asOf:maxD;}',
' document.getElementById("tbAsOf").value=d;',
' recomputeTB();',
'}',
'',
'/* ── TB Columns Panel ── */',
'function toggleTBCols(){',
' var wrap=document.getElementById("tbColsWrap");',
' if(wrap.innerHTML){wrap.innerHTML="";return;}',
' var h="<div class=\\"panel\\" style=\\"width:200px\\">";',
' h+="<div class=\\"panel-hdr\\"><span class=\\"panel-title\\">Columns</span><button class=\\"panel-close\\" onclick=\\"toggleTBCols()\\">\\u2715</button></div>";',
' for(var j=0;j<TB_COLS.length;j++){h+="<label class=\\"col-check\\"><input type=\\"checkbox\\" "+(TB_COLS[j].on?"checked":"")+" onchange=\\"TB_COLS["+j+"].on=this.checked;updateTBColsBtn();quietRecompute(\'tb\')\\"> "+TB_COLS[j].label+"</label>";}',
' h+="</div>";',
' wrap.innerHTML=h;}',
'',
'function onTBCmpChange(){var m=document.getElementById("tbCmp").value;document.getElementById("tbMultiIntGrp").style.display=m==="multi"?"flex":"none";document.getElementById("tbMultiNumGrp").style.display=m==="multi"?"flex":"none";document.getElementById("tbCustomGrp").style.display=m==="custom"?"flex":"none";if(m==="none")TB_STATE.showVar=false;refreshTBMorePanel();recomputeTB();}',
'',
'/* ── TB Computation ── */',
'function filterByType(jeRows,t){if(t==="all")return jeRows;return jeRows.filter(function(je){var id=je.entryId||"",rf=je.ref||"";var isA=id.indexOf("AJE")===0||id.indexOf("aje")===0||rf.indexOf("AJE")===0||rf.indexOf("DEP")===0||rf.indexOf("INS")===0||rf.indexOf("OPT")===0||rf.indexOf("IT-")===0;return t==="unadj"?!isA:t==="aje"?isA:true;});}',
'function computeTB(jeRows,coa,asOf,fyM){',
' var yr=parseInt(asOf.substring(0,4)),mo=parseInt(asOf.substring(5,7));var cyS,pyS,pyE;',
' if(fyM===1){cyS=yr+"-01-01";pyS=(yr-1)+"-01-01";pyE=(yr-1)+"-12-31";}',
' else{if(mo>=fyM){cyS=yr+"-"+pad(fyM)+"-01";pyS=(yr-1)+"-"+pad(fyM)+"-01";pyE=yr+"-"+pad(fyM-1)+"-"+ld(fyM-1,yr);}else{cyS=(yr-1)+"-"+pad(fyM)+"-01";pyS=(yr-2)+"-"+pad(fyM)+"-01";pyE=(yr-1)+"-"+pad(fyM-1)+"-"+ld(fyM-1,yr-1);}}',
' var acctMap={},acctOrd=[],capAcct="",reAcct="";',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;var t=String(a.title).trim(),tp=String(a.type).trim();var nrm=String(a.normal||"").trim();var isRE=tp==="Revenue"||tp==="Expense";var isContraEq=tp==="Equity"&&nrm==="Debit";acctMap[t]={code:a.code,title:t,type:tp,fsGroup:a.fsGroup||"",normal:nrm,taxLine:a.taxLine||"",taxLine2551:a.ptTaxLine||"",noteGroup:a.noteGroup||"",isRE:isRE,isContraEq:isContraEq,net:0};acctOrd.push(t);if(!capAcct&&tp==="Equity"&&nrm==="Credit")capAcct=t;if(tp==="Equity"&&nrm==="Credit"&&t==="Retained Earnings")reAcct=t;}',
// For Rev/Exp: only CY entries. For Contra-Equity (Withdrawals): only CY entries. For all else: all history.
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j],d=je.date||"";if(!acctMap[je.account])continue;var m=acctMap[je.account];if(m.isRE||m.isContraEq){if(d<cyS||d>asOf)continue;}else{if(d>asOf)continue;}m.net+=(je.debit||0)-(je.credit||0);}',
// PY Contra-Equity (Withdrawals) - roll into Capital account (reduces its credit balance)
' var pyContraEq=0;',
' for(var p3=0;p3<jeRows.length;p3++){var pj3=jeRows[p3],pd3=pj3.date||"";if(!pd3||pd3>=cyS)continue;if(!acctMap[pj3.account])continue;if(!acctMap[pj3.account].isContraEq)continue;pyContraEq+=(pj3.debit||0)-(pj3.credit||0);}',
// Cumulative Net Income = sum of prior-FY Rev/Exp net
' var cumNI=0;',
' for(var p=0;p<jeRows.length;p++){var pj=jeRows[p],pd=pj.date||"";if(!pd||pd>=cyS)continue;if(!acctMap[pj.account])continue;if(!acctMap[pj.account].isRE)continue;cumNI+=(pj.debit||0)-(pj.credit||0);}',
// Current Year Net Income
' var cyNI=0;',
' for(var p2=0;p2<jeRows.length;p2++){var pj2=jeRows[p2],pd2=pj2.date||"";if(!pd2||pd2<cyS||pd2>asOf)continue;if(!acctMap[pj2.account])continue;if(!acctMap[pj2.account].isRE)continue;cyNI+=(pj2.debit||0)-(pj2.credit||0);}',
// Roll pyContraEq into Capital (Withdrawals is DR-normal, rolling reduces Capital CR balance)
// CORP (2026-06-05, D2): Non-Individual rolls prior-FY contra-equity AND prior-FY Rev/Exp net
// into the account titled "Retained Earnings" (no synthetic Cumulative Net Income row).
' var tbIsCorp=String((typeof DATA!=="undefined"&&DATA.settings&&DATA.settings.taxpayerClass)||"").toUpperCase().indexOf("NON")===0;',
' var rollT=(tbIsCorp&&reAcct)?reAcct:capAcct;',
' if(rollT&&pyContraEq!==0)acctMap[rollT].net+=pyContraEq;',
' if(tbIsCorp&&reAcct&&cumNI!==0){acctMap[reAcct].net+=cumNI;cumNI=0;}',
' var rows=[],tDR=0,tCR=0;',
' for(var k=0;k<acctOrd.length;k++){var t=acctOrd[k],a=acctMap[t],n=rnd(a.net);var dr=n>0?n:0,cr=n<0?Math.abs(n):0;tDR+=dr;tCR+=cr;rows.push({code:a.code,title:t,type:a.type,fsGroup:a.fsGroup,normal:a.normal,taxLine:a.taxLine,taxLine2551:a.taxLine2551,noteGroup:a.noteGroup,dr:dr,cr:cr,net:n,isRE:a.isRE,synthetic:false});}',
// Synthetic Cumulative Net Income - Equity section
' var cumNIRow={code:"",title:"Cumulative Net Income",type:"Equity",fsGroup:"Equity",normal:"Credit",taxLine:"",taxLine2551:"",noteGroup:"",dr:cumNI>0?rnd(cumNI):0,cr:cumNI<0?rnd(-cumNI):0,net:rnd(-cumNI),isRE:false,synthetic:true};',
' if(cumNIRow.dr!==0||cumNIRow.cr!==0){rows.push(cumNIRow);tDR+=cumNIRow.dr;tCR+=cumNIRow.cr;}',
// Current Year NI (synthetic, returned separately)
' var cyNIRow={code:"",title:"Current Year Net Income",type:"NetIncome",fsGroup:"Net Income",normal:"Credit",taxLine:"",taxLine2551:"",noteGroup:"",dr:cyNI>0?rnd(cyNI):0,cr:cyNI<0?rnd(-cyNI):0,net:rnd(-cyNI),isRE:false,synthetic:true};',
' return{rows:rows,tDR:rnd(tDR),tCR:rnd(tCR),asOf:asOf,cyNIRow:cyNIRow,cyS:cyS};}',
'',
'function computeMultiTB(){var asOf=document.getElementById("tbAsOf").value;var fyM=parseInt(DATA.settings.fyStartMonth)||1;var mode=document.getElementById("tbCmp").value;var je=jeView_();var periods=[];',
' if(mode==="none"){periods=[{label:fmtDateLabel(asOf),tb:computeTB(je,DATA.coa,asOf,fyM)}];}',
' else if(mode==="sppy"){var py=shiftYear(asOf,-1);periods=[{label:fmtDateLabel(asOf),tb:computeTB(je,DATA.coa,asOf,fyM)},{label:fmtDateLabel(py),tb:computeTB(je,DATA.coa,py,fyM)}];}',
' else if(mode==="fpy"){var yr2=parseInt(asOf.substring(0,4));var fyEnd=(yr2-1)+"-12-31";periods=[{label:fmtDateLabel(asOf),tb:computeTB(je,DATA.coa,asOf,fyM)},{label:fmtDateLabel(fyEnd),tb:computeTB(je,DATA.coa,fyEnd,fyM)}];}',
' else if(mode==="pq"){var pq=shiftMonths(asOf,-3);periods=[{label:fmtDateLabel(asOf),tb:computeTB(je,DATA.coa,asOf,fyM)},{label:fmtDateLabel(pq),tb:computeTB(je,DATA.coa,pq,fyM)}];}',
' else if(mode==="custom"){var cd=document.getElementById("tbCustomDate").value;periods=[{label:fmtDateLabel(asOf),tb:computeTB(je,DATA.coa,asOf,fyM)},{label:fmtDateLabel(cd),tb:computeTB(je,DATA.coa,cd,fyM)}];}',
' else if(mode==="multi"){var interval=document.getElementById("tbMultiInt").value;var numP=parseInt(document.getElementById("tbMultiNum").value)||4;for(var i=0;i<numP;i++){var pEnd;if(interval==="monthly")pEnd=shiftMonths(asOf,-i);else if(interval==="quarterly")pEnd=shiftMonths(asOf,-i*3);else pEnd=shiftYear(asOf,-i);periods.push({label:fmtDateLabel(pEnd),tb:computeTB(je,DATA.coa,pEnd,fyM)});}}',
' LAST_PERIODS=periods;return periods;}',
'function recomputeTB(){if(!document.getElementById("tbAsOf"))return;',
' if(!SKEL_SUPPRESS){showSkeleton("tb");SKEL_SUPPRESS=true;setTimeout(function(){recomputeTB();SKEL_SUPPRESS=false;fadeInReport("tb");},200);return;}',
' renderTB(computeMultiTB());',
'}',
'',
'/* ── TB Sparkline ── */',
'function makeSparkline(title,fyM){',
' var asOf=document.getElementById("tbAsOf").value;var pts=[];',
' for(var i=5;i>=0;i--){var d=shiftMonths(asOf,-i);var tb=computeTB(jeView_(),DATA.coa,d,fyM);var r=findAcct(tb.rows,title);pts.push(r?(r.dr||0)-(r.cr||0):0);}',
' if(pts.every(function(p){return p===0}))return"";var max=Math.max.apply(null,pts.map(Math.abs));if(max===0)return"";',
' var w=70,h=18,points=[];for(var j=0;j<pts.length;j++){var x=(j/(pts.length-1))*w;var y=h-((pts[j]+max)/(2*max))*(h-4)-2;points.push(x.toFixed(1)+","+y.toFixed(1));}',
' var last=pts[pts.length-1],first=pts[0];var col=last>first?"#2E7D32":last<first?"#C62828":"#888";var lp=points[points.length-1].split(",");',
' return"<svg width=\\""+w+"\\" height=\\""+h+"\\" style=\\"vertical-align:middle\\"><polyline points=\\""+points.join(" ")+"\\" fill=\\"none\\" stroke=\\""+col+"\\" stroke-width=\\"1.5\\"/><circle cx=\\""+lp[0]+"\\" cy=\\""+lp[1]+"\\" r=\\"2\\" fill=\\""+col+"\\"/></svg>";}',
'',
'/* ── TB Render ── */',
'function renderTB(periods){',
' var numP=periods.length,primary=periods[0].tb,showVar=TB_STATE.showVar===true&&numP>=2;',
' var search=(document.getElementById("tbSearch").value||"").toLowerCase();',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var visTBCols=TB_COLS.filter(function(c){return c.on});',
' var showCode=visTBCols.some(function(c){return c.id==="code";});',
' var metaCols=visTBCols.filter(function(c){return c.id!=="code";});',
// Classic: Code (optional) + Title + meta + numP*2 + variance*2
// Always 2 fixed columns: Code is optional, Title is always there
' var preCols=(showCode?1:0)+1+metaCols.length;',
' var labelCols=(showCode?2:1);', // colspan for label (Code + Title when code on, else just Title)
' var numCols=numP*2+(showVar?2:0);',
' var totalCols=preCols+numCols;',
// Rows: apply net income synthesis if toggle on
' var baseRows=primary.rows.slice();',
// If Net Income toggle is ON and in Classic mode, we will render Income/Expense inside nested section.
// computeTB already rolled prior-year stuff into Capital + Cumulative NI, so baseRows only has current-FY Rev/Exp.
// Filter: keep rows with non-zero in any period
' baseRows=baseRows.filter(function(r){if(r.dr!==0||r.cr!==0)return true;for(var pi=1;pi<numP;pi++){var pr=findAcct(periods[pi].tb.rows,r.title);if(pr&&(pr.dr!==0||pr.cr!==0))return true;}return false;});',
' if(search)baseRows=baseRows.filter(function(r){return r.title.toLowerCase().indexOf(search)>=0||(r.code||"").toLowerCase().indexOf(search)>=0;});',
' var h="<div class=\\"rpt-doc\\"><div class=\\"rpt-page\\">"+renderReportHeader(getReportMetadata("Trial Balance","asof",{date:primary.asOf}));',
' h+="<div class=\\"table-wrap airy-report\\"><table class=\\"tb-table\\"><thead><tr>";',
// Headers with sort arrows
' if(showCode)h+="<th class=\\"sortable\\" onclick=\\"tbSortBy(\'code\')\\" style=\\"text-align:left;min-width:60px\\">Code"+tbSortArrow("code")+"</th>";',
' h+="<th class=\\"sortable\\" onclick=\\"tbSortBy(\'title\')\\" style=\\"text-align:left;min-width:200px\\">Account Title"+tbSortArrow("title")+"</th>";',
' for(var ci=0;ci<metaCols.length;ci++){var tc=metaCols[ci];',
' if(tc.id==="trend")h+="<th style=\\"text-align:center\\">Trend</th>";',
' else h+="<th class=\\"sortable\\" onclick=\\"tbSortBy(\'"+tc.id+"\')\\">"+tc.label+tbSortArrow(tc.id)+"</th>";}',
' for(var p=0;p<numP;p++){var plab=(numP>1?periods[p].label+"<br>":"");h+="<th class=\\"num sortable\\" onclick=\\"tbSortBy(\'dr\')\\">"+plab+"Debit"+(p===0?tbSortArrow("dr"):"")+"</th><th class=\\"num sortable\\" onclick=\\"tbSortBy(\'cr\')\\">"+plab+"Credit"+(p===0?tbSortArrow("cr"):"")+"</th>";}',
' if(showVar)h+="<th class=\\"num\\">Variance<br>Amount</th><th class=\\"num\\">Variance<br>%</th>";',
' h+="</tr></thead><tbody>";',
// Group rows by type section
' var byType={Asset:[],Liability:[],Equity:[],Revenue:[],Expense:[]};',
' for(var i=0;i<baseRows.length;i++){var row=baseRows[i];var tp=row.type;if(tp==="Income")tp="Revenue";if(byType[tp])byType[tp].push(row);}',
// Totals per type accumulator (for subtotals across periods)
' var typeTot={};for(var pt=0;pt<numP;pt++)typeTot["p"+pt]={};',
' function accumTot(tp,pi,row){if(!typeTot["p"+pi][tp])typeTot["p"+pi][tp]={dr:0,cr:0};typeTot["p"+pi][tp].dr+=row.dr;typeTot["p"+pi][tp].cr+=row.cr;}',
// Helper: render one account row
' function renderAcctRow(row,nested){var rh="<tr class=\\"tb-acct-row"+(nested?" tb-nested-acct":"")+(row.synthetic?" tb-synth":"")+"\\">";',
' if(showCode)rh+="<td class=\\"tb-code\\">"+(row.code||"")+"</td>";',
' rh+="<td>"+row.title+"</td>";',
' for(var ci2=0;ci2<metaCols.length;ci2++){var tc2=metaCols[ci2];',
' if(tc2.id==="trend")rh+="<td style=\\"text-align:center\\">"+(row.synthetic?"":makeSparkline(row.title,fyM))+"</td>";',
' else if(tc2.id==="fsGroup")rh+="<td class=\\"tb-meta-col\\">"+(row.fsGroup||"")+"</td>";',
' else if(tc2.id==="acctType")rh+="<td class=\\"tb-meta-col\\">"+(row.type==="Revenue"?"Income":row.type)+"</td>";',
' else if(tc2.id==="taxLine")rh+="<td class=\\"tb-meta-col\\">"+(row.taxLine||"")+"</td>";',
' else if(tc2.id==="taxLine2551")rh+="<td class=\\"tb-meta-col\\">"+(row.taxLine2551||"")+"</td>";',
' else if(tc2.id==="noteGroup")rh+="<td class=\\"tb-meta-col\\">"+(row.noteGroup||"")+"</td>";',
' else rh+="<td></td>";}',
' for(var pii=0;pii<numP;pii++){var pRow=pii===0?row:findAcct(periods[pii].tb.rows,row.title);var dr=pRow?pRow.dr:0,cr=pRow?pRow.cr:0;var drillable=!row.synthetic;var drAttr=drillable&&dr>0?" onclick=\\"drillDown(\'"+escJS(row.title)+"\')\\"":"";var crAttr=drillable&&cr>0?" onclick=\\"drillDown(\'"+escJS(row.title)+"\')\\"":"";rh+="<td class=\\"num "+(drillable&&dr>0?"tb-acct-amt":"")+"\\""+drAttr+">"+(dr>0?fmt(dr):"-")+"</td><td class=\\"num "+(drillable&&cr>0?"tb-acct-amt":"")+"\\""+crAttr+">"+(cr>0?fmt(cr):"-")+"</td>";}',
' if(showVar){var cyNet=row.dr-row.cr;var pyRow=findAcct(periods[1].tb.rows,row.title);var pyNet=pyRow?(pyRow.dr-pyRow.cr):0;var va=rnd(cyNet-pyNet);var vp=pyNet!==0?rnd((va/Math.abs(pyNet))*100):cyNet!==0?100:0;var vc=va>0?"#2E7D32":va<0?"#C62828":"#6B7280";rh+="<td class=\\"num\\" style=\\"color:"+vc+"\\">"+(va!==0?(va>0?"+":"")+fmt(va):"-")+"</td><td class=\\"num\\" style=\\"color:"+vc+"\\">"+(va!==0?(vp>0?"+":"")+vp.toFixed(1)+"%":"-")+"</td>";}',
' rh+="</tr>";return rh;}',
// Helper: render a section header (collapsible)
' function renderSectionHeader(key,label,rows,nested){var collapsed=TB_STATE.collapsed[key];var chev=collapsed?"\\u25B8":"\\u25BE";var cls="tb-section-row"+(nested?" tb-nested-hdr":"");if(!collapsed){return"<tr class=\\""+cls+"\\" onclick=\\"toggleTBSection(\'"+key+"\')\\"><td colspan=\\""+totalCols+"\\"><span class=\\"chev\\">"+chev+"</span>"+label+"</td></tr>";}else{var sDR=0,sCR=0;for(var sr=0;sr<rows.length;sr++){sDR+=rows[sr].dr;sCR+=rows[sr].cr;}var net=sDR-sCR;var nDR=net>0?net:0,nCR=net<0?-net:0;var rh="<tr class=\\""+cls+"\\" onclick=\\"toggleTBSection(\'"+key+"\')\\">";rh+="<td colspan=\\""+labelCols+"\\"><span class=\\"chev\\">"+chev+"</span>"+label+"</td>";for(var mc2=0;mc2<metaCols.length;mc2++)rh+="<td></td>";for(var pic=0;pic<numP;pic++){var pDR=pic===0?nDR:0,pCR=pic===0?nCR:0;if(pic>0){var pSDR=0,pSCR=0;for(var psr=0;psr<rows.length;psr++){var pRow=findAcct(periods[pic].tb.rows,rows[psr].title);if(pRow){pSDR+=pRow.dr;pSCR+=pRow.cr;}}var pNet=pSDR-pSCR;pDR=pNet>0?pNet:0;pCR=pNet<0?-pNet:0;}rh+="<td class=\\"num\\">"+(pDR>0?fmt(pDR):"-")+"</td><td class=\\"num\\">"+(pCR>0?fmt(pCR):"-")+"</td>";}if(showVar)rh+="<td class=\\"num\\">-</td><td class=\\"num\\">-</td>";return rh+"</tr>";}}',
// Helper: render section subtotal (when expanded)
' function renderSectionSubtotal(key,label,rows,nested){var sDR=0,sCR=0;for(var sr=0;sr<rows.length;sr++){sDR+=rows[sr].dr;sCR+=rows[sr].cr;}for(var pi3=0;pi3<numP;pi3++){for(var sr2=0;sr2<rows.length;sr2++)accumTot(key,pi3,pi3===0?rows[sr2]:(findAcct(periods[pi3].tb.rows,rows[sr2].title)||{dr:0,cr:0}));}var cls="tb-subtotal-row"+(nested?" tb-nested-sub":"");var rh="<tr class=\\""+cls+"\\"><td colspan=\\""+labelCols+"\\">Total "+label+"</td>";for(var mc3=0;mc3<metaCols.length;mc3++)rh+="<td></td>";for(var pi4=0;pi4<numP;pi4++){var t=typeTot["p"+pi4][key]||{dr:0,cr:0};rh+="<td class=\\"num\\">"+(t.dr>0?fmt(t.dr):"-")+"</td><td class=\\"num\\">"+(t.cr>0?fmt(t.cr):"-")+"</td>";}if(showVar){var cy=typeTot.p0[key]||{dr:0,cr:0};var py=(typeTot.p1||{})[key]||{dr:0,cr:0};var v=rnd((cy.dr-cy.cr)-(py.dr-py.cr));rh+="<td class=\\"num\\">"+(v!==0?fmt(v):"-")+"</td><td class=\\"num\\">-</td>";}return rh+"</tr>";}',
// Helper: render a single section (header + accounts + subtotal)
' function renderSection(key,label,rows,nested){var sorted=tbSortRows(rows);var html="";html+=renderSectionHeader(key,label,rows,nested);if(!TB_STATE.collapsed[key]){for(var sr3=0;sr3<sorted.length;sr3++)html+=renderAcctRow(sorted[sr3],nested);html+=renderSectionSubtotal(key,label,rows,nested);}return html;}',
// Totals accumulator for the grand total
// Grand total: use pre-computed primary.tDR/tCR (already rnd'd)
' var grandDR=primary.tDR,grandCR=primary.tCR;',
' function addGrand(rowsArr){/* no-op: totals precomputed */}',
// Render based on view mode
' if(TB_STATE.view==="flat"){',
' var allRows=[];["Asset","Liability","Equity","Revenue","Expense"].forEach(function(k){for(var fr=0;fr<byType[k].length;fr++)allRows.push(byType[k][fr]);});allRows=tbSortRows(allRows);addGrand(allRows);for(var fr2=0;fr2<allRows.length;fr2++)h+=renderAcctRow(allRows[fr2],false);',
' }else{',
// Classic - Asset, Liability, Equity
' h+=renderSection("Asset","Asset",byType.Asset,false);addGrand(byType.Asset);',
' h+=renderSection("Liability","Liability",byType.Liability,false);addGrand(byType.Liability);',
' h+=renderSection("Equity","Equity",byType.Equity,false);addGrand(byType.Equity);',
// Income + Expense: either separate OR inside Net Income nested section
' if(TB_STATE.netToggle){',
// Net Income parent: nested Income + Expense inside
' var niCollapsed=TB_STATE.collapsed.NetIncome;',
' var cyNIRow=primary.cyNIRow;',
' var niNetDR=cyNIRow.dr,niNetCR=cyNIRow.cr;',
' addGrand([{dr:niNetDR,cr:niNetCR}]);', // synthesized into one row for totals
' if(niCollapsed){',
// Collapsed: single line "Net Income"
' var chev="\\u25B8";h+="<tr class=\\"tb-section-row\\" onclick=\\"toggleTBSection(\'NetIncome\')\\">";h+="<td colspan=\\""+labelCols+"\\"><span class=\\"chev\\">"+chev+"</span>Net Income</td>";for(var mcn=0;mcn<metaCols.length;mcn++)h+="<td></td>";for(var pinc=0;pinc<numP;pinc++){var pDR2=niNetDR,pCR2=niNetCR;if(pinc>0){var pCyNIRow=periods[pinc].tb.cyNIRow||{dr:0,cr:0};pDR2=pCyNIRow.dr;pCR2=pCyNIRow.cr;}h+="<td class=\\"num\\">"+(pDR2>0?fmt(pDR2):"-")+"</td><td class=\\"num\\">"+(pCR2>0?fmt(pCR2):"-")+"</td>";}if(showVar)h+="<td class=\\"num\\">-</td><td class=\\"num\\">-</td>";h+="</tr>";',
' }else{',
// Expanded: Net Income header + nested Income + nested Expense + Total Net Income
' h+="<tr class=\\"tb-section-row\\" onclick=\\"toggleTBSection(\'NetIncome\')\\"><td colspan=\\""+totalCols+"\\"><span class=\\"chev\\">\\u25BE</span>Net Income</td></tr>";',
' h+=renderSection("Income","Income",byType.Revenue,true);',
' h+=renderSection("Expense","Expense",byType.Expense,true);',
// Total Net Income subtotal (the net figure)
' h+="<tr class=\\"tb-subtotal-row tb-ni-subtotal\\"><td colspan=\\""+labelCols+"\\">Total Net Income</td>";for(var mcnt=0;mcnt<metaCols.length;mcnt++)h+="<td></td>";for(var pint=0;pint<numP;pint++){var pCyNIRow2=pint===0?cyNIRow:(periods[pint].tb.cyNIRow||{dr:0,cr:0});h+="<td class=\\"num\\">"+(pCyNIRow2.dr>0?fmt(pCyNIRow2.dr):"-")+"</td><td class=\\"num\\">"+(pCyNIRow2.cr>0?fmt(pCyNIRow2.cr):"-")+"</td>";}if(showVar)h+="<td class=\\"num\\">-</td><td class=\\"num\\">-</td>";h+="</tr>";',
' }',
// When Net Income toggle is ON and COLLAPSED, grand total uses net figure.
// When EXPANDED, raw I+E accounts are shown so totals must stay raw.
' if(niCollapsed){',
' var rawIDR=0,rawICR=0;for(var riq=0;riq<byType.Revenue.length;riq++){rawIDR+=byType.Revenue[riq].dr;rawICR+=byType.Revenue[riq].cr;}',
' var rawEDR=0,rawECR=0;for(var req=0;req<byType.Expense.length;req++){rawEDR+=byType.Expense[req].dr;rawECR+=byType.Expense[req].cr;}',
' grandDR=rnd(grandDR-rawIDR-rawEDR+niNetDR);grandCR=rnd(grandCR-rawICR-rawECR+niNetCR);',
' }',
' }else{',
// Normal: Income + Expense separate top-level sections
' h+=renderSection("Income","Income",byType.Revenue,false);addGrand(byType.Revenue);',
' h+=renderSection("Expense","Expense",byType.Expense,false);addGrand(byType.Expense);',
' }',
' }',
// Grand total row
' h+="<tr class=\\"tb-total-row\\"><td colspan=\\""+labelCols+"\\">Totals</td>";',
' for(var mcg=0;mcg<metaCols.length;mcg++)h+="<td></td>";',
// For classic with netToggle OFF, grandDR/CR already accumulated per-section
// For classic with netToggle ON, grandDR/CR was adjusted above
// For flat, grandDR/CR was accumulated via addGrand
// For CY col (pi=0), use computed. For other periods, use periods[pi].tb.tDR/tCR.
' h+="<td class=\\"num\\">"+fmt(grandDR)+"</td><td class=\\"num\\">"+fmt(grandCR)+"</td>";',
' for(var pt3=1;pt3<numP;pt3++)h+="<td class=\\"num\\">"+fmt(periods[pt3].tb.tDR)+"</td><td class=\\"num\\">"+fmt(periods[pt3].tb.tCR)+"</td>";',
' if(showVar){var gv=rnd(grandDR-periods[1].tb.tDR);h+="<td class=\\"num\\">"+(gv!==0?fmt(gv):"-")+"</td><td class=\\"num\\">-</td>";}',
' h+="</tr></tbody></table></div>";',
' h+="</div></div>";',
' document.getElementById("report").innerHTML=h;rptApplyZoom_();}',
// Legacy tbSubRow kept for backward-compat (not used by new renderTB)
'function tbSubRow(type,numP,tt,sv,preCols){var h="<tr class=\\"tb-subtotal-row\\"><td colspan=\\""+preCols+"\\">Total "+type+"</td>";for(var p=0;p<numP;p++){var t=tt["p"+p][type]||{dr:0,cr:0};h+="<td class=\\"num\\">"+(t.dr>0?fmt(t.dr):"-")+"</td><td class=\\"num\\">"+(t.cr>0?fmt(t.cr):"-")+"</td>";}if(sv){var cy=tt.p0[type]||{dr:0,cr:0};var py=(tt.p1||{})[type]||{dr:0,cr:0};var v=rnd((cy.dr-cy.cr)-(py.dr-py.cr));h+="<td class=\\"num\\">"+(v!==0?fmt(v):"-")+"</td><td class=\\"num\\">-</td>";}return h+"</tr>";}',
'',
'/* ── TB Drill-Down ── */',
'function drillDown(acctTitle){',
' TB_STATE.asOf=document.getElementById("tbAsOf").value;TB_STATE.preset=document.getElementById("tbPreset").value;TB_STATE.cmp=document.getElementById("tbCmp").value;TB_STATE.search=document.getElementById("tbSearch").value;TB_STATE.customDate=document.getElementById("tbCustomDate")?document.getElementById("tbCustomDate").value:"";',
' DRILL_CONTEXT={from:"tb",prevState:null};',
' GL_STATE.selectedAccounts={};GL_STATE.selectedAccounts[acctTitle]=true;GL_STATE.page=1;GL_STATE.groupMode="account";',
' switchTab("gl");}',
'function backToTB(){DRILL_CONTEXT=null;switchTab("tb");if(TB_STATE.asOf){setTimeout(function(){document.getElementById("tbPreset").value=TB_STATE.preset||"tfy";document.getElementById("tbAsOf").value=TB_STATE.asOf;document.getElementById("tbCmp").value=TB_STATE.cmp;document.getElementById("tbSearch").value=TB_STATE.search||"";if(TB_STATE.customDate&&document.getElementById("tbCustomDate"))document.getElementById("tbCustomDate").value=TB_STATE.customDate;onTBCmpChange();},50);}}',
'',
// ═══ IS drill-down ═══
'function drillFromIS(acctTitle){',
' var prev={from:IS_STATE.from,to:IS_STATE.to,preset:IS_STATE.preset,compareMode:IS_STATE.compareMode,customCompareDate:IS_STATE.customCompareDate,showVariance:IS_STATE.showVariance,showPctRevenue:IS_STATE.showPctRevenue,showTaxLine1701:IS_STATE.showTaxLine1701,showTaxLine2551:IS_STATE.showTaxLine2551,showNoteGroup:IS_STATE.showNoteGroup,showTrend:IS_STATE.showTrend,multiInterval:IS_STATE.multiInterval,multiCount:IS_STATE.multiCount,collapsedSections:JSON.parse(JSON.stringify(IS_STATE.collapsedSections||{}))};',
' DRILL_CONTEXT={from:"is",prevState:prev};',
' GL_STATE.selectedAccounts={};GL_STATE.selectedAccounts[acctTitle]=true;GL_STATE.page=1;GL_STATE.groupMode="account";',
// GL dates use IS From/To
' GL_STATE._pendingFrom=IS_STATE.from;GL_STATE._pendingTo=IS_STATE.to;GL_STATE._pendingPreset="custom";',
' switchTab("gl");',
'}',
'',
// Per-period IS drill - used by multi-period columns; drill dates = that column's from/to
'function drillFromISPeriod(acctTitle,fromDate,toDate){',
' var prev={from:IS_STATE.from,to:IS_STATE.to,preset:IS_STATE.preset,compareMode:IS_STATE.compareMode,customCompareDate:IS_STATE.customCompareDate,showVariance:IS_STATE.showVariance,showPctRevenue:IS_STATE.showPctRevenue,showTaxLine1701:IS_STATE.showTaxLine1701,showTaxLine2551:IS_STATE.showTaxLine2551,showNoteGroup:IS_STATE.showNoteGroup,showTrend:IS_STATE.showTrend,multiInterval:IS_STATE.multiInterval,multiCount:IS_STATE.multiCount,collapsedSections:JSON.parse(JSON.stringify(IS_STATE.collapsedSections||{}))};',
' DRILL_CONTEXT={from:"is",prevState:prev};',
' GL_STATE.selectedAccounts={};GL_STATE.selectedAccounts[acctTitle]=true;GL_STATE.page=1;GL_STATE.groupMode="account";',
' GL_STATE._pendingFrom=fromDate||IS_STATE.from;GL_STATE._pendingTo=toDate||IS_STATE.to;GL_STATE._pendingPreset="custom";',
' switchTab("gl");',
'}',
'',
// ═══ BS drill-down ═══
'function drillFromBS(acctTitle){',
' var prev={asOfDate:BS_STATE.asOfDate,preset:BS_STATE.preset,compareMode:BS_STATE.compareMode,customCompareDate:BS_STATE.customCompareDate,showVariance:BS_STATE.showVariance,showPctTotalAssets:BS_STATE.showPctTotalAssets,showTrend:BS_STATE.showTrend,showNoteGroup:BS_STATE.showNoteGroup,multiInterval:BS_STATE.multiInterval,multiCount:BS_STATE.multiCount,collapsedTypes:JSON.parse(JSON.stringify(BS_STATE.collapsedTypes||{})),collapsedFSGroups:JSON.parse(JSON.stringify(BS_STATE.collapsedFSGroups||{}))};',
' DRILL_CONTEXT={from:"bs",prevState:prev};',
' GL_STATE.selectedAccounts={};GL_STATE.selectedAccounts[acctTitle]=true;GL_STATE.page=1;GL_STATE.groupMode="account";',
// GL dates: FY start (from Settings fyStartMonth) through BS as-of
' var asOf=BS_STATE.asOfDate||document.getElementById("bsDate").value;',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var fyStart=computeFYStart(asOf,fyM);',
' GL_STATE._pendingFrom=fyStart;GL_STATE._pendingTo=asOf;GL_STATE._pendingPreset="custom";',
' switchTab("gl");',
'}',
'',
// Per-period drill - used by multi-period BS columns; drill date range = FY start of THAT period through THAT period
'function drillFromBSPeriod(acctTitle,periodAsOf){',
' var prev={asOfDate:BS_STATE.asOfDate,preset:BS_STATE.preset,compareMode:BS_STATE.compareMode,customCompareDate:BS_STATE.customCompareDate,showVariance:BS_STATE.showVariance,showPctTotalAssets:BS_STATE.showPctTotalAssets,showTrend:BS_STATE.showTrend,showNoteGroup:BS_STATE.showNoteGroup,multiInterval:BS_STATE.multiInterval,multiCount:BS_STATE.multiCount,collapsedTypes:JSON.parse(JSON.stringify(BS_STATE.collapsedTypes||{})),collapsedFSGroups:JSON.parse(JSON.stringify(BS_STATE.collapsedFSGroups||{}))};',
' DRILL_CONTEXT={from:"bs",prevState:prev};',
' GL_STATE.selectedAccounts={};GL_STATE.selectedAccounts[acctTitle]=true;GL_STATE.page=1;GL_STATE.groupMode="account";',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var fyStart=computeFYStart(periodAsOf,fyM);',
' GL_STATE._pendingFrom=fyStart;GL_STATE._pendingTo=periodAsOf;GL_STATE._pendingPreset="custom";',
' switchTab("gl");',
'}',
'',
// Drill from "Current Year Net Income" to IS tab, dates = FY start → period as-of
'function drillToISFromBS(periodAsOf){',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var fyStart=computeFYStart(periodAsOf,fyM);',
' IS_STATE.from=fyStart;IS_STATE.to=periodAsOf;IS_STATE.preset="custom";',
' DRILL_CONTEXT=null;',
' switchTab("is");',
'}',
'',
// Compute FY start date (YYYY-MM-DD) for a given as-of date and FY start month
'function computeFYStart(asOfStr,fyM){',
' var yr=parseInt(asOfStr.substring(0,4));var mo=parseInt(asOfStr.substring(5,7));',
// If current month is >= FY start month, FY started this calendar year. Else last year.
' var fyYr=(mo>=fyM)?yr:(yr-1);',
' return fyYr+"-"+pad(fyM)+"-01";',
'}',
'',
// Add N days (can be negative) to an ISO date string; returns ISO date string
'function addDaysISO(isoStr,days){',
' var d=new Date(isoStr+"T00:00:00Z");',
' d.setUTCDate(d.getUTCDate()+days);',
' return d.getUTCFullYear()+"-"+pad(d.getUTCMonth()+1)+"-"+pad(d.getUTCDate());',
'}',
'',
// ═══ Unified back-from-drill handler ═══
'function backToSource(){',
' if(!DRILL_CONTEXT)return;',
' var from=DRILL_CONTEXT.from;',
' if(from==="tb"){backToTB();return;}',
' if(from==="is"){backToIS();return;}',
' if(from==="bs"){backToBS();return;}',
'}',
'',
'function backToIS(){',
' var prev=DRILL_CONTEXT&&DRILL_CONTEXT.prevState;',
' DRILL_CONTEXT=null;',
' if(prev){for(var k in prev){if(Object.prototype.hasOwnProperty.call(prev,k))IS_STATE[k]=prev[k];}}',
' switchTab("is");',
'}',
'',
'function backToBS(){',
' var prev=DRILL_CONTEXT&&DRILL_CONTEXT.prevState;',
' DRILL_CONTEXT=null;',
' if(prev){for(var k in prev){if(Object.prototype.hasOwnProperty.call(prev,k))BS_STATE[k]=prev[k];}}',
' switchTab("bs");',
'}',
'',
'/* ══════════════════════════════════════',
' GENERAL LEDGER',
' ══════════════════════════════════════ */',
'function buildGL(){buildGLControls();recomputeGL();}',
'',
'/* ── More button with optional filter count + clear ── */',
'function renderMoreButton(){',
' var fc=GL_STATE.filters.length;',
' if(fc===0){return "<button id=\\"glMoreBtn\\" class=\\"ctrl-btn"+(GL_STATE.showMore?" on":"")+"\\" onclick=\\"GL_STATE.showMore=!GL_STATE.showMore;renderGLPanels()\\">More \\u25BE</button>";}',
' return "<button id=\\"glMoreBtn\\" class=\\"more-btn-active\\" onclick=\\"GL_STATE.showMore=!GL_STATE.showMore;renderGLPanels()\\"><span>More ("+fc+") \\u25BE</span><span class=\\"more-btn-active-x\\" onclick=\\"event.stopPropagation();clearAllFilters()\\">\\u2715</span></button>";',
'}',
'function updateMoreBtn(){var b=document.getElementById("glMoreBtn");if(b&&b.parentNode)b.outerHTML=renderMoreButton();}',
'function clearAllFilters(){GL_STATE.filters=[];GL_STATE.page=1;renderGLPanels();recomputeGL();updateMoreBtn();}',
'',
'function buildGLControls(){',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(function(d){return d&&d.length>=10}).sort();',
' var minD=dates[0]||"2025-01-01",maxD=dates[dates.length-1]||"2026-12-31";',
// Consume pending drill-override date values (from IS/BS drill)
' var fromVal=GL_STATE._pendingFrom||minD;',
' var toVal=GL_STATE._pendingTo||maxD;',
' var presetVal=GL_STATE._pendingPreset||"fy";',
' delete GL_STATE._pendingFrom;delete GL_STATE._pendingTo;delete GL_STATE._pendingPreset;',
' var selCount=Object.keys(GL_STATE.selectedAccounts).filter(function(k){return GL_STATE.selectedAccounts[k]}).length;',
' var totalAccts=DATA.coa.filter(function(a){return!a.active||String(a.active).toUpperCase()!=="NO"}).length;',
' var label=selCount===0?"Accounts \\u25BE":selCount===totalAccts?"Accounts (All) \\u25BE":"Accounts ("+selCount+") \\u25BE";',
' var h="";',
' if(DRILL_CONTEXT){var _isChat=DRILL_CONTEXT.from&&DRILL_CONTEXT.from.indexOf("chat")===0;var _srcLabel=DRILL_CONTEXT.from==="tb"?"Trial Balance":DRILL_CONTEXT.from==="is"?"Income Statement":DRILL_CONTEXT.from==="bs"?"Balance Sheet":"";var _backFn=_isChat?"backFromChatDrill()":"backToSource()";var _backLabel=_isChat?"\\u2190 Back to previous view":"\\u2190 Back to "+_srcLabel;h+="<button class=\\"back-btn\\" onclick=\\""+_backFn+"\\">"+_backLabel+"</button><br>";}',
// Row 1
' h+="<div class=\\"ctrl-bar\\">";',
' h+="<div class=\\"ctrl-group\\" style=\\"position:relative\\"><label>Accounts</label><button id=\\"glAcctBtn\\" class=\\"ctrl-dropdown-btn\\" onclick=\\"togglePanel(\'Picker\')\\">"+label+"</button><div id=\\"pickerWrap\\"></div></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Date Range</label><div style=\\"display:flex;gap:4px;align-items:center\\">";',
' h+="<select id=\\"glPreset\\" onchange=\\"applyGLPreset()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' var glPresetOpts=[["fy","This Financial Year"],["ytd","Year-to-Date"],["tq","This Quarter"],["qtd","Quarter-to-Date"],["tm","This Month"],["mtd","Month-to-Date"],["lm","Last Month"],["lq","Last Quarter"],["lfy","Last Financial Year"],["all","All Time"],["custom","Custom"]];',
' for(var gpi=0;gpi<glPresetOpts.length;gpi++){h+="<option value=\\""+glPresetOpts[gpi][0]+"\\""+(glPresetOpts[gpi][0]===presetVal?" selected":"")+">"+glPresetOpts[gpi][1]+"</option>";}',
' h+="</select>";',
' h+="<input type=\\"date\\" id=\\"glFrom\\" value=\\""+fromVal+"\\" onchange=\\"recomputeGL()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit;width:125px\\">";',
' h+="<input type=\\"date\\" id=\\"glTo\\" value=\\""+toVal+"\\" onchange=\\"recomputeGL()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit;width:125px\\">";',
' h+="</div></div>";',
' h+="<div class=\\"ctrl-group\\" style=\\"position:relative\\"><label>Columns</label><button id=\\"glColsBtn\\" class=\\"ctrl-dropdown-btn\\" onclick=\\"togglePanel(\'Cols\')\\">"+glColsLabel()+"</button><div id=\\"colsWrap\\"></div></div>";',
// Group dropdown - reduced to 2 options
' h+="<div class=\\"ctrl-group\\"><label>Group By</label><select id=\\"glGrouping\\" onchange=\\"onGroupChange()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' h+="<option value=\\"account\\""+(GL_STATE.groupMode==="account"?" selected":"")+">Account</option>";',
' h+="<option value=\\"name\\""+(GL_STATE.groupMode==="name"?" selected":"")+">Name</option>";',
' h+="</select></div>";',
' h+=renderHideDecimalsToggle();',
' h+="</div>";',
// Row 2
' h+="<div class=\\"ctrl-bar\\">";',
' h+="<div class=\\"ctrl-group\\"><label>Accounts to Include</label><select id=\\"glAcctInclude\\" onchange=\\"GL_STATE.acctInclude=this.value;recomputeGL()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' h+="<option value=\\"with_trans\\">Only with transactions</option><option value=\\"with_bal\\">With transactions or non-zero balance</option><option value=\\"all_zero\\">All including zero</option></select></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Search</label><input type=\\"text\\" id=\\"glSearch\\" placeholder=\\"Filter entries...\\" oninput=\\"debouncedSearch('gl')\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit;min-width:140px\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label> </label><div style=\\"display:flex;gap:4px\\">";',
' h+=renderMoreButton();',
' h+="</div></div>";',
' h+="</div>";',
' h+="<div id=\\"glPanels\\"></div>";',
' document.getElementById("controls").innerHTML=h;',
' applyGLPreset();renderGLPanels();',
'}',
'',
// Handle group mode change
'function onGroupChange(){GL_STATE.groupMode=document.getElementById("glGrouping").value;GL_STATE.page=1;recomputeGL();}',
'',
'/* ── GL Panels (Filter + More) ── */',
'function renderGLPanels(){',
' var h="";',
// Filter panel
' if(GL_STATE.showFilter){',
' h+="<div class=\\"more-panel\\" style=\\"display:block\\">";',
' h+="<div style=\\"font-weight:700;font-size:12px;color:var(--navy);margin-bottom:8px\\">Filters</div>";',
' for(var i=0;i<GL_STATE.filters.length;i++){var f=GL_STATE.filters[i];',
' var fv=FILTER_VARS.find(function(x){return x.id===f.varId})||FILTER_VARS[0];',
' h+="<div class=\\"filter-row\\">";',
' h+="<select onchange=\\"changeFilterVar("+i+",this.value)\\">";',
' for(var fvi=0;fvi<FILTER_VARS.length;fvi++)h+="<option value=\\""+FILTER_VARS[fvi].id+"\\""+(FILTER_VARS[fvi].id===f.varId?" selected":"")+">"+FILTER_VARS[fvi].label+"</option>";',
' h+="</select>";',
' var ops=FILTER_OPS[fv.otype]||FILTER_OPS.text;',
' h+="<select onchange=\\"GL_STATE.filters["+i+"].op=this.value\\">";',
' for(var oi=0;oi<ops.length;oi++)h+="<option value=\\""+ops[oi]+"\\""+(ops[oi]===f.op?" selected":"")+">"+ops[oi]+"</option>";',
' h+="</select>";',
' if(fv.opts){h+="<select onchange=\\"GL_STATE.filters["+i+"].val=this.value\\"><option value=\\"\\">--</option>";for(var ovi=0;ovi<fv.opts.length;ovi++)h+="<option value=\\""+fv.opts[ovi]+"\\""+(fv.opts[ovi]===f.val?" selected":"")+">"+fv.opts[ovi]+"</option>";h+="</select>";}',
' else{h+="<input type=\\"text\\" value=\\""+esc(f.val||"")+"\\" oninput=\\"GL_STATE.filters["+i+"].val=this.value\\" placeholder=\\"Value...\\" style=\\"width:140px\\">";}',
' h+="<button class=\\"filter-remove\\" onclick=\\"GL_STATE.filters.splice("+i+",1);renderGLPanels();updateMoreBtn()\\">\\u2715</button></div>";}',
' h+="<div style=\\"margin-top:6px\\"><button class=\\"pill-btn\\" onclick=\\"addFilter()\\">+ Add Filter</button>";',
' h+=" <button class=\\"pill-btn\\" onclick=\\"GL_STATE.page=1;recomputeGL();updateMoreBtn()\\" style=\\"background:var(--blue);color:white;border-color:var(--blue)\\">Apply</button>";',
' h+=" <button class=\\"pill-btn\\" onclick=\\"GL_STATE.filters=[];GL_STATE.page=1;renderGLPanels();recomputeGL();updateMoreBtn()\\">Clear All</button></div></div>";}',
// More panel - O/C toggles auto-hide for Name mode
' if(GL_STATE.showMore){',
' h+="<div class=\\"more-panel\\">";',
' h+="<button class=\\"ctrl-btn"+(GL_STATE.showFilter?" on":"")+"\\" onclick=\\"GL_STATE.showFilter=!GL_STATE.showFilter;renderGLPanels()\\" style=\\"margin-right:8px\\">\\uD83D\\uDD0D Filter</button>";',
' if(GL_STATE.groupMode==="account"){',
' h+="<label class=\\"more-check\\"><input type=\\"checkbox\\" "+(GL_STATE.showOC_ALE?"checked":"")+" onchange=\\"GL_STATE.showOC_ALE=this.checked;quietRecompute(\'gl\')\\"> Opening & Closing: Asset, Liability, Equity</label>";',
' h+="<label class=\\"more-check\\"><input type=\\"checkbox\\" "+(GL_STATE.showOC_RevExp?"checked":"")+" onchange=\\"GL_STATE.showOC_RevExp=this.checked;quietRecompute(\'gl\')\\"> Opening & Closing: Revenue & Expense</label>";',
' }',
' h+="</div>";}',
' document.getElementById("glPanels").innerHTML=h;',
'}',
'',
'/* ── GL Panels: Account Picker + Columns ── */',
'function togglePanel(name){',
' if(name==="Picker"){GL_STATE.showPicker=!GL_STATE.showPicker;GL_STATE.showCols=false;}',
' else{GL_STATE.showCols=!GL_STATE.showCols;GL_STATE.showPicker=false;}',
' renderPickerPanel();renderColsPanel();}',
'function renderPickerPanel(){',
' var wrap=document.getElementById("pickerWrap");if(!GL_STATE.showPicker){wrap.innerHTML="";return;}',
' var types=["Asset","Liability","Equity","Revenue","Expense"];',
' var h="<div class=\\"panel\\" style=\\"width:300px;max-height:450px;overflow-y:auto\\">";',
' h+="<div class=\\"panel-hdr\\"><span class=\\"panel-title\\">Select Accounts</span><button class=\\"panel-close\\" onclick=\\"togglePanel(\'Picker\')\\">\\u2715</button></div>";',
' h+="<input type=\\"text\\" class=\\"panel-search\\" id=\\"acctSrch\\" placeholder=\\"Search...\\" oninput=\\"filterAcctList()\\">";',
' h+="<div style=\\"display:flex;gap:4px;flex-wrap:wrap;margin-bottom:6px\\">";',
' h+="<button class=\\"pill-btn\\" onclick=\\"acctToggleAll()\\">All</button>";',
' for(var t=0;t<types.length;t++)h+="<button class=\\"pill-btn\\" onclick=\\"acctToggleType(\'"+types[t]+"\')\\">" +types[t]+"</button>";',
' h+="</div>";',
' h+="<label style=\\"display:flex;align-items:center;gap:4px;font-size:10px;color:#888;margin-bottom:6px;cursor:pointer\\"><input type=\\"checkbox\\" "+(GL_STATE.showInactive?"checked":"")+" onchange=\\"GL_STATE.showInactive=this.checked;filterAcctList()\\"> Include Inactive</label>";',
' h+="<div id=\\"acctListDiv\\">"+buildAcctList("")+"</div></div>";',
' wrap.innerHTML=h;}',
'function buildAcctList(filter){var types=["Asset","Liability","Equity","Revenue","Expense"];var h="";',
' for(var t=0;t<types.length;t++){var type=types[t];var accts=DATA.coa.filter(function(a){var isInactive=a.active&&String(a.active).toUpperCase()==="NO";if(isInactive&&!GL_STATE.showInactive)return false;return String(a.type).trim()===type;});',
' if(filter)accts=accts.filter(function(a){var s=filter.toLowerCase();return String(a.title).toLowerCase().indexOf(s)>=0||String(a.code).indexOf(s)>=0;});',
' if(accts.length===0)continue;h+="<div class=\\"acct-type-hdr\\">"+type+"</div>";',
' for(var i=0;i<accts.length;i++){var a=accts[i],t2=String(a.title).trim(),ck=GL_STATE.selectedAccounts[t2]?"checked":"";var isInact=a.active&&String(a.active).toUpperCase()==="NO";',
' h+="<label class=\\"acct-check\\""+(isInact?" style=\\"opacity:0.5\\"":"")+"><input type=\\"checkbox\\" "+ck+" onchange=\\"acctTgl(\'"+escJS(t2)+"\')\\"><span class=\\"acct-check-code\\">"+a.code+"</span><span>"+esc(t2)+(isInact?" (inactive)":"")+"</span></label>";}}return h;}',
'function filterAcctList(){document.getElementById("acctListDiv").innerHTML=buildAcctList(document.getElementById("acctSrch").value);}',
'function acctTgl(t){GL_STATE.selectedAccounts[t]=!GL_STATE.selectedAccounts[t];GL_STATE.page=1;updateGLAcctBtn();recomputeGL();}',
'function acctToggleAll(){var allOn=DATA.coa.filter(function(a){var isInact=a.active&&String(a.active).toUpperCase()==="NO";return !isInact;}).every(function(a){return GL_STATE.selectedAccounts[String(a.title).trim()]});',
' DATA.coa.forEach(function(a){var isInact=a.active&&String(a.active).toUpperCase()==="NO";if(!isInact)GL_STATE.selectedAccounts[String(a.title).trim()]=!allOn;});',
' renderPickerPanel();updateGLAcctBtn();GL_STATE.page=1;recomputeGL();}',
'function acctToggleType(type){var typeAccts=DATA.coa.filter(function(a){var isInact=a.active&&String(a.active).toUpperCase()==="NO";return String(a.type).trim()===type&&!isInact;});',
' var allOn=typeAccts.every(function(a){return GL_STATE.selectedAccounts[String(a.title).trim()]});',
' typeAccts.forEach(function(a){GL_STATE.selectedAccounts[String(a.title).trim()]=!allOn;});',
' renderPickerPanel();updateGLAcctBtn();GL_STATE.page=1;recomputeGL();}',
'',
'/* ── Columns Panel ── */',
'function renderColsPanel(){',
' var wrap=document.getElementById("colsWrap");if(!GL_STATE.showCols){wrap.innerHTML="";return;}',
' var h="<div class=\\"panel\\" style=\\"width:220px;max-height:400px;overflow-y:auto\\">";',
' h+="<div class=\\"panel-hdr\\"><span class=\\"panel-title\\">Columns</span><button class=\\"panel-close\\" onclick=\\"togglePanel(\'Cols\')\\">\\u2715</button></div>";',
' for(var j=0;j<GL_COLS.length;j++){var c=GL_COLS[j];if(c.core)continue;h+="<label class=\\"col-check\\"><input type=\\"checkbox\\" "+(c.on?"checked":"")+" onchange=\\"GL_COLS["+j+"].on=this.checked;updateGLColsBtn();quietRecompute(\'gl\')\\"> "+c.label+"</label>";}',
' h+="<div style=\\"margin-top:8px\\"><button class=\\"pill-btn\\" onclick=\\"resetCols()\\">Reset Defaults</button></div></div>";',
' wrap.innerHTML=h;}',
'function isColOrigOn(id){return id==="ref"||id==="desc"||id==="name";}',
'function resetCols(){for(var i=0;i<GL_COLS.length;i++){GL_COLS[i].on=GL_COLS[i].core||isColOrigOn(GL_COLS[i].id);}renderColsPanel();updateGLColsBtn();quietRecompute("gl");}',
'',
'/* ── GL Date Presets ── */',
'function applyGLPreset(){',
' var preset=document.getElementById("glPreset").value;',
' if(preset==="custom")return;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' var maxD=dates[dates.length-1]||"2026-12-31",minD=dates[0]||"2025-01-01";',
' var today=new Date();var todayStr=today.getFullYear()+"-"+pad(today.getMonth()+1)+"-"+pad(today.getDate());',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var r=computeDateRange(preset,maxD,fyM,todayStr,minD);',
' if(!r)return;',
' document.getElementById("glFrom").value=r.from;document.getElementById("glTo").value=r.to;GL_STATE.page=1;recomputeGL();}',
'',
'/* ── GL Computation ── */',
'function getAcctInfo(title){for(var i=0;i<DATA.coa.length;i++){if(String(DATA.coa[i].title).trim()===title)return DATA.coa[i];}return null;}',
'function computeGLForAccount(acctTitle,fromDate,toDate,fyM){',
' var info=getAcctInfo(acctTitle);if(!info)return null;',
' var type=String(info.type).trim(),isALE=type==="Asset"||type==="Liability"||type==="Equity";',
' var isRevExp=type==="Revenue"||type==="Expense";',
// Opening balance logic
' var opening=0,showOpening=false;',
' if(isALE&&GL_STATE.showOC_ALE){showOpening=true;for(var j=0;j<DATA.jeRows.length;j++){var je=DATA.jeRows[j];if(je.account!==acctTitle||je.date>=fromDate)continue;opening+=(je.debit||0)-(je.credit||0);}}',
' if(isRevExp&&GL_STATE.showOC_RevExp){showOpening=true;',
// FY-aware opening for Rev/Exp
' var fyr=parseInt(fromDate.substring(0,4)),fmo=parseInt(fromDate.substring(5,7));',
' var fyStart;if(fyM===1)fyStart=fyr+"-01-01";else fyStart=fmo>=fyM?fyr+"-"+pad(fyM)+"-01":(fyr-1)+"-"+pad(fyM)+"-01";',
' for(var j2=0;j2<DATA.jeRows.length;j2++){var je2=DATA.jeRows[j2];if(je2.account!==acctTitle)continue;if(je2.date>=fyStart&&je2.date<fromDate)opening+=(je2.debit||0)-(je2.credit||0);}}',
// Period entries
' var entries=[];',
' for(var k=0;k<DATA.jeRows.length;k++){var je3=DATA.jeRows[k];if(je3.account!==acctTitle||je3.date<fromDate||je3.date>toDate)continue;entries.push(je3);}',
' var periodDr=0,periodCr=0;for(var e=0;e<entries.length;e++){periodDr+=(entries[e].debit||0);periodCr+=(entries[e].credit||0);}',
' var isDrNormal=String(info.normal||"").trim()==="Debit";',
' return{code:info.code,title:acctTitle,type:type,fsGroup:info.fsGroup||"",taxLine:info.taxLine||"",noteGroup:info.noteGroup||"",taxLine2551:info.ptTaxLine||"",isALE:isALE,isDrNormal:isDrNormal,showOpening:showOpening,opening:rnd(opening),entries:entries,periodDr:rnd(periodDr),periodCr:rnd(periodCr),closing:rnd(opening+periodDr-periodCr)};}',
'',
'/* ── GL Filter Logic ── */',
'function addFilter(){GL_STATE.filters.push({varId:"desc",op:"contains",val:""});renderGLPanels();updateMoreBtn();}',
'function changeFilterVar(i,varId){var fv=FILTER_VARS.find(function(x){return x.id===varId})||FILTER_VARS[0];var ops=FILTER_OPS[fv.otype]||FILTER_OPS.text;GL_STATE.filters[i].varId=varId;GL_STATE.filters[i].op=ops[0];GL_STATE.filters[i].val="";renderGLPanels();}',
'function applyFilters(entries,acctInfo){',
' if(GL_STATE.filters.length===0)return entries;',
' return entries.filter(function(en){',
' for(var i=0;i<GL_STATE.filters.length;i++){var f=GL_STATE.filters[i];if(!f.val&&f.val!==0)continue;',
' var fv=FILTER_VARS.find(function(x){return x.id===f.varId})||{otype:"text"};',
' var isNum=fv.otype==="num";',
' var rawVal=String(f.val).replace(/,/g,"");',
' var val=isNum?parseFloat(rawVal):rawVal.toLowerCase();',
' var fieldValRaw="";',
' if(f.varId==="desc")fieldValRaw=en.desc||"";',
' else if(f.varId==="name")fieldValRaw=en.name||en.desc||"";',
' else if(f.varId==="ref")fieldValRaw=en.ref||"";',
' else if(f.varId==="source")fieldValRaw=en.source||"";',
' else if(f.varId==="entryId")fieldValRaw=en.entryId||"";',
' else if(f.varId==="dr")fieldValRaw=en.debit||0;',
' else if(f.varId==="cr")fieldValRaw=en.credit||0;',
' else if(f.varId==="net")fieldValRaw=(en.debit||0)-(en.credit||0);',
' else if(f.varId==="acctCode")fieldValRaw=acctInfo?acctInfo.code:"";',
' else if(f.varId==="acctType")fieldValRaw=acctInfo?acctInfo.type:"";',
' else if(f.varId==="fsGroup")fieldValRaw=acctInfo?acctInfo.fsGroup:"";',
' else if(f.varId==="month")fieldValRaw=en.date?en.date.substring(5,7):"";',
' else if(f.varId==="quarter"){var qmo=parseInt((en.date||"").substring(5,7));fieldValRaw=qmo<=3?"Q1":qmo<=6?"Q2":qmo<=9?"Q3":"Q4";}',
' else if(f.varId==="taxLine")fieldValRaw=acctInfo?acctInfo.taxLine:"";',
' else if(f.varId==="noteGroup")fieldValRaw=acctInfo?acctInfo.noteGroup:"";',
' else if(f.varId==="taxLine2551")fieldValRaw=acctInfo?acctInfo.taxLine2551:"";',
' var fieldVal=isNum?parseFloat(fieldValRaw)||0:String(fieldValRaw).toLowerCase();',
' if(isNum){',
' if(f.op==="equals"&&fieldVal!==val)return false;',
' if(f.op==="greater than"&&fieldVal<=val)return false;',
' if(f.op==="less than"&&fieldVal>=val)return false;',
' if(f.op==="between"){var parts=rawVal.split(",");if(parts.length===2){var lo=parseFloat(parts[0]),hi=parseFloat(parts[1]);if(fieldVal<lo||fieldVal>hi)return false;}}',
' }else{',
' if(f.op==="contains"&&fieldVal.indexOf(val)<0)return false;',
' if(f.op==="starts with"&&fieldVal.indexOf(val)!==0)return false;',
' if(f.op==="equals"&&fieldVal!==val)return false;',
' if(f.op==="not contains"&&fieldVal.indexOf(val)>=0)return false;',
' if(f.op==="is"&&fieldVal!==val)return false;',
' if(f.op==="is not"&&fieldVal===val)return false;',
' }',
' }',
' return true;});',
'}',
'',
'/* ── GL Sort ── */',
'function glSort(col){if(GL_STATE.sortCol===col)GL_STATE.sortDir=GL_STATE.sortDir==="asc"?"desc":"asc";else{GL_STATE.sortCol=col;GL_STATE.sortDir="asc";}quietRecompute("gl");}',
'function sortEntries(entries){var col=GL_STATE.sortCol,dir=GL_STATE.sortDir;return entries.slice().sort(function(a,b){var va,vb;',
' if(col==="date"){va=a.date;vb=b.date;}else if(col==="ref"){va=a.ref;vb=b.ref;}else if(col==="desc"){va=a.desc;vb=b.desc;}else if(col==="name"){va=a.name||"";vb=b.name||"";}else if(col==="dr"){va=a.debit||0;vb=b.debit||0;}else if(col==="cr"){va=a.credit||0;vb=b.credit||0;}else if(col==="source"){va=a.source||"";vb=b.source||"";}else{va=a.date;vb=b.date;}',
' if(va<vb)return dir==="asc"?-1:1;if(va>vb)return dir==="asc"?1:-1;return 0;});}',
'',
'/* ── GL Recompute ── */',
'function recomputeGL(){',
' if(!SKEL_SUPPRESS){showSkeleton("gl");SKEL_SUPPRESS=true;setTimeout(function(){recomputeGL();SKEL_SUPPRESS=false;fadeInReport("gl");},200);return;}',
' var fromDate=document.getElementById("glFrom").value,toDate=document.getElementById("glTo").value;',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var search=(document.getElementById("glSearch").value||"").toLowerCase();',
' var selected=Object.keys(GL_STATE.selectedAccounts).filter(function(k){return GL_STATE.selectedAccounts[k]});',
' var ordered=[];for(var i=0;i<DATA.coa.length;i++){var t=String(DATA.coa[i].title).trim();if(selected.indexOf(t)>=0)ordered.push(t);}',
' var glData=[];',
' for(var j=0;j<ordered.length;j++){var d=computeGLForAccount(ordered[j],fromDate,toDate,fyM);if(!d)continue;',
// Accounts to Include filter
' if(GL_STATE.acctInclude==="with_trans"&&d.entries.length===0)continue;',
' if(GL_STATE.acctInclude==="with_bal"&&d.entries.length===0&&d.opening===0)continue;',
// Apply filters
' d.entries=applyFilters(d.entries,d);',
// Apply Search filter on individual entries
' if(search){d.entries=d.entries.filter(function(en){var desc=(en.desc||"").toLowerCase();var ref=(en.ref||"").toLowerCase();var nm=(en.name||"").toLowerCase();var eid=(en.entryId||"").toLowerCase();return desc.indexOf(search)>=0||ref.indexOf(search)>=0||nm.indexOf(search)>=0||eid.indexOf(search)>=0;});}',
// Recalc period totals after filter
' d.periodDr=0;d.periodCr=0;for(var e=0;e<d.entries.length;e++){d.periodDr+=(d.entries[e].debit||0);d.periodCr+=(d.entries[e].credit||0);}',
' d.periodDr=rnd(d.periodDr);d.periodCr=rnd(d.periodCr);d.closing=rnd(d.opening+d.periodDr-d.periodCr);',
' glData.push(d);}',
// Branch by groupMode
' if(GL_STATE.groupMode==="name")renderGL_byName(glData,fromDate,toDate);',
' else renderGL(glData,fromDate,toDate);}',
'',
'/* ── GL Render (by Account - original) ── */',
'function renderGL(glData,fromDate,toDate){',
' var ms=["","January","February","March","April","May","June","July","August","September","October","November","December"];',
' var fMo=parseInt(fromDate.substring(5,7)),fDy=parseInt(fromDate.substring(8,10)),fYr=fromDate.substring(0,4);',
' var tMo=parseInt(toDate.substring(5,7)),tDy=parseInt(toDate.substring(8,10)),tYr=toDate.substring(0,4);',
' var visCols=GL_COLS.filter(function(c){return c.on});',
' var colCount=visCols.length;',
' var sc=GL_STATE.sortCol,sd=GL_STATE.sortDir;',
' function sa(c){return c===sc?"<span class=\\"sort-arrow active\\">"+(sd==="asc"?"\\u2191":"\\u2193")+"</span>":"<span class=\\"sort-arrow inactive\\">\\u2195</span>";}',
'',
' var h=renderReportHeader(getReportMetadata("General Ledger","period",{from:fromDate,to:toDate}));',
' if(glData.length===0){h+="<div class=\\"table-wrap airy-report\\" style=\\"padding:40px;text-align:center;color:#999\\">Select accounts to view.</div>";document.getElementById("report").innerHTML=h;return;}',
'',
' var totalEntries=0;for(var ti=0;ti<glData.length;ti++)totalEntries+=glData[ti].entries.length;',
'',
// Pagination calc
' var ps=GL_STATE.pageSize,pg=GL_STATE.page;',
' var totalPages=Math.ceil(totalEntries/ps)||1;if(pg>totalPages)pg=totalPages;',
' var startIdx=(pg-1)*ps,endIdx=pg*ps,runIdx=0;',
'',
' h+="<div class=\\"table-wrap airy-report\\">";',
' for(var g=0;g<glData.length;g++){',
' var acct=glData[g];var sorted=sortEntries(acct.entries);',
' h+="<table class=\\"tb-table gl-fit-table\\" style=\\"margin-bottom:20px\\">";',
// Account header - airy style (white bg, bold black, bottom border)
' h+="<thead><tr class=\\"gl-acct-hdr\\"><td colspan=\\""+colCount+"\\">"+acct.code+" - "+acct.title+"</td></tr>";',
// Column headers (clickable sort) - airy style (white bg, gray text)
' h+="<tr>";',
' for(var ci=0;ci<visCols.length;ci++){var vc=visCols[ci];var isNum=vc.type==="num";',
' var stl=vc.id==="desc"?" style=\\"min-width:180px\\"":vc.id==="name"?" style=\\"min-width:130px\\"":"";',
' h+="<th class=\\""+(isNum?"num ":"")+"sortable\\" onclick=\\"glSort(\'"+vc.id+"\')\\""+ stl +">"+vc.label+sa(vc.id)+"</th>";}',
' h+="</tr></thead><tbody>";',
// Compute column indices for DR/CR/Balance
' var drIdx=-1,crIdx=-1,balIdx=-1;for(var fi=0;fi<visCols.length;fi++){if(visCols[fi].id==="dr")drIdx=fi;if(visCols[fi].id==="cr")crIdx=fi;if(visCols[fi].id==="bal")balIdx=fi;}',
// Opening balance - show in DR or CR column based on normal direction
' if(acct.showOpening){',
' h+="<tr class=\\"gl-opening\\">";',
' var obAmt=Math.abs(acct.opening);',
' for(var oi=0;oi<visCols.length;oi++){var oc=visCols[oi];',
' if(oi===0)h+="<td colspan=\\""+(drIdx>0?drIdx:1)+"\\">Opening Balance</td>";',
' else if(oi>0&&oi<drIdx)continue;',
' else if(oc.id==="dr")h+="<td class=\\"num\\">"+(acct.isDrNormal?fmt(obAmt):"-")+"</td>";',
' else if(oc.id==="cr")h+="<td class=\\"num\\">"+(!acct.isDrNormal?fmt(obAmt):"-")+"</td>";',
' else if(oc.id==="bal")h+="<td class=\\"num\\">"+fmt(obAmt)+"</td>";',
' else if(oi>drIdx)h+="<td></td>";}',
' h+="</tr>";}',
// Entries with pagination - airy style, no zebra
' if(sorted.length===0){h+="<tr class=\\"gl-empty\\"><td colspan=\\""+colCount+"\\">No transactions in this period</td></tr>";}',
' else{var runBal=acct.showOpening?acct.opening:0;',
' for(var e=0;e<sorted.length;e++){',
' runBal+=(sorted[e].debit||0)-(sorted[e].credit||0);',
' if(runIdx>=startIdx&&runIdx<endIdx){',
' var en=sorted[e];',
' h+="<tr class=\\"gl-entry-row\\">";',
' for(var ci2=0;ci2<visCols.length;ci2++){var vc2=visCols[ci2];',
' if(vc2.id==="date")h+="<td>"+fmtGLDate(en.date)+"</td>";',
' else if(vc2.id==="ref")h+="<td>"+en.ref+"</td>";',
' else if(vc2.id==="desc")h+="<td>"+en.desc+"</td>";',
' else if(vc2.id==="name")h+="<td class=\\"gl-meta-col\\">"+(en.name||"")+"</td>";',
' else if(vc2.id==="dr")h+="<td class=\\"num "+(en.debit>0?"gl-amt-clickable":"")+"\\""+(en.debit>0?" onclick=\\"showJEDetail(\'"+en.date+"\',\'"+escJS(en.ref||"")+"\',\'"+escJS(en.entryId||"")+"\')\\"":"")+">"+(en.debit>0?fmt(en.debit):"-")+"</td>";',
' else if(vc2.id==="cr")h+="<td class=\\"num "+(en.credit>0?"gl-amt-clickable":"")+"\\""+(en.credit>0?" onclick=\\"showJEDetail(\'"+en.date+"\',\'"+escJS(en.ref||"")+"\',\'"+escJS(en.entryId||"")+"\')\\"":"")+">"+(en.credit>0?fmt(en.credit):"-")+"</td>";',
' else if(vc2.id==="bal")h+="<td class=\\"num gl-bal-col\\">"+fmt(Math.abs(runBal))+"</td>";',
' else if(vc2.id==="source")h+="<td>"+en.source+"</td>";',
' else if(vc2.id==="entryId")h+="<td class=\\"gl-entryid-col\\">"+(en.entryId||"")+"</td>";',
' else if(vc2.id==="net")h+="<td class=\\"num\\">"+fmt((en.debit||0)-(en.credit||0))+"</td>";',
' else if(vc2.id==="acctType")h+="<td>"+acct.type+"</td>";',
' else if(vc2.id==="fsGroup")h+="<td>"+acct.fsGroup+"</td>";',
' else if(vc2.id==="taxLine")h+="<td>"+(acct.taxLine||"")+"</td>";',
' else if(vc2.id==="noteGroup")h+="<td>"+(acct.noteGroup||"")+"</td>";',
' else if(vc2.id==="taxLine2551")h+="<td>"+(acct.taxLine2551||"")+"</td>";',
' else if(vc2.id==="month"){var mIdx=parseInt((en.date||"").substring(5,7));h+="<td>"+["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][mIdx]+"</td>";}',
' else if(vc2.id==="quarter"){var qm2=parseInt((en.date||"").substring(5,7));h+="<td>"+(qm2<=3?"Q1":qm2<=6?"Q2":qm2<=9?"Q3":"Q4")+"</td>";}',
' else h+="<td></td>";}',
' h+="</tr>";}',
' runIdx++;}}',
// Transaction Movement row (was Period Total)
' var mvLabel="Transaction Movement ("+fmtGLDate(fromDate)+" - "+fmtGLDate(toDate)+")";',
' h+="<tr class=\\"gl-period\\">";',
' for(var pi=0;pi<visCols.length;pi++){',
' if(pi===0)h+="<td colspan=\\""+(drIdx>0?drIdx:colCount-3)+"\\" style=\\"text-align:right\\">"+mvLabel+"</td>";',
' else if(pi>0&&pi<drIdx)continue;',
' else if(pi===drIdx)h+="<td class=\\"num\\">"+(acct.periodDr>0?fmt(acct.periodDr):"-")+"</td>";',
' else if(pi===crIdx)h+="<td class=\\"num\\">"+(acct.periodCr>0?fmt(acct.periodCr):"-")+"</td>";',
' else if(pi===balIdx)h+="<td class=\\"num\\">"+fmt(Math.abs(acct.periodDr-acct.periodCr))+"</td>";',
' else if(pi>drIdx)h+="<td></td>";}',
' h+="</tr>";',
// Closing Balance row - Opening goes to normal side
' if(acct.showOpening){',
' var obAbs=Math.abs(acct.opening);',
' var closDr=acct.isDrNormal?rnd(obAbs+acct.periodDr):acct.periodDr;',
' var closCr=acct.isDrNormal?acct.periodCr:rnd(obAbs+acct.periodCr);',
' h+="<tr class=\\"gl-closing\\">";',
' for(var cli=0;cli<visCols.length;cli++){',
' if(cli===0)h+="<td colspan=\\""+(drIdx>0?drIdx:1)+"\\" style=\\"text-align:right\\">Closing Balance</td>";',
' else if(cli>0&&cli<drIdx)continue;',
' else if(visCols[cli].id==="dr")h+="<td class=\\"num\\">"+(closDr>0?fmt(closDr):"-")+"</td>";',
' else if(visCols[cli].id==="cr")h+="<td class=\\"num\\">"+(closCr>0?fmt(closCr):"-")+"</td>";',
' else if(visCols[cli].id==="bal")h+="<td class=\\"num\\">"+fmt(Math.abs(acct.closing))+"</td>";',
' else if(cli>drIdx)h+="<td></td>";}',
' h+="</tr>";}',
' else{h+="<tr class=\\"gl-closing\\">";',
' for(var cli2=0;cli2<visCols.length;cli2++){',
' if(cli2===0)h+="<td colspan=\\""+(drIdx>0?drIdx:1)+"\\" style=\\"text-align:right\\">Closing Balance</td>";',
' else if(cli2>0&&cli2<drIdx)continue;',
' else if(visCols[cli2].id==="dr")h+="<td class=\\"num\\">"+(acct.periodDr>0?fmt(acct.periodDr):"-")+"</td>";',
' else if(visCols[cli2].id==="cr")h+="<td class=\\"num\\">"+(acct.periodCr>0?fmt(acct.periodCr):"-")+"</td>";',
' else if(visCols[cli2].id==="bal")h+="<td class=\\"num\\">"+fmt(Math.abs(acct.closing))+"</td>";',
' else if(cli2>drIdx)h+="<td></td>";}',
' h+="</tr>";}',
' h+="</tbody></table>";}',
'',
// Pagination
' if(totalEntries>ps){',
' h+="<div class=\\"pager\\">Showing "+(startIdx+1)+"-"+Math.min(endIdx,totalEntries)+" of "+totalEntries+" entries ";',
' h+="<button class=\\"pager-btn\\" "+(pg<=1?"disabled":"onclick=\\"glPage("+(pg-1)+")\\"")+">« Prev</button>";',
' for(var pi2=1;pi2<=totalPages;pi2++){if(totalPages>10&&Math.abs(pi2-pg)>3&&pi2!==1&&pi2!==totalPages){if(pi2===2||pi2===totalPages-1)h+="<span>...</span>";continue;}h+="<button class=\\"pager-btn"+(pi2===pg?" active":"")+"\\" onclick=\\"glPage("+pi2+")\\">"+pi2+"</button>";}',
' h+="<button class=\\"pager-btn\\" "+(pg>=totalPages?"disabled":"onclick=\\"glPage("+(pg+1)+")\\"")+">» Next</button>";',
' h+=" | Rows: ";',
' [50,100,200].forEach(function(sz){h+="<button class=\\"pager-btn"+(ps===sz?" active":"")+"\\" onclick=\\"GL_STATE.pageSize="+sz+";GL_STATE.page=1;quietRecompute(\'gl\')\\">"+sz+"</button> ";});',
' h+="<button class=\\"pager-btn"+(ps===99999?" active":"")+"\\" onclick=\\"GL_STATE.pageSize=99999;GL_STATE.page=1;quietRecompute(\'gl\')\\">All</button>";',
' h+="</div>";}',
' h+="</div>";',
' document.getElementById("report").innerHTML=h;}',
'function glPage(n){GL_STATE.page=n;quietRecompute("gl");}',
'',
'/* ══════════════════════════════════════',
' GL RENDER - Group by Name (NEW v2.2)',
' ══════════════════════════════════════ */',
'function renderGL_byName(glData,fromDate,toDate){',
' var ms=["","January","February","March","April","May","June","July","August","September","October","November","December"];',
' var fMo=parseInt(fromDate.substring(5,7)),fDy=parseInt(fromDate.substring(8,10)),fYr=fromDate.substring(0,4);',
' var tMo=parseInt(toDate.substring(5,7)),tDy=parseInt(toDate.substring(8,10)),tYr=toDate.substring(0,4);',
'',
' var h=renderReportHeader(getReportMetadata("General Ledger (by Name)","period",{from:fromDate,to:toDate}));',
' if(glData.length===0){h+="<div class=\\"table-wrap airy-report\\" style=\\"padding:40px;text-align:center;color:#999\\">Select accounts to view.</div>";document.getElementById("report").innerHTML=h;return;}',
'',
// Flatten all entries across selected accounts, tag each with account info
' var allEntries=[];',
' for(var g=0;g<glData.length;g++){var acct=glData[g];',
' for(var ei=0;ei<acct.entries.length;ei++){var en=acct.entries[ei];',
' allEntries.push({date:en.date,ref:en.ref||"",desc:en.desc||"",name:en.name||"",source:en.source||"",entryId:en.entryId||"",debit:en.debit||0,credit:en.credit||0,acctCode:acct.code,acctTitle:acct.title});}}',
'',
// Group by name - blank parties go to "(No Name)"
' var nameMap={};var nameOrd=[];',
' for(var ai=0;ai<allEntries.length;ai++){var ent=allEntries[ai];',
' var pkey=(ent.name||"").trim()||"(No Name)";',
' if(!nameMap[pkey]){nameMap[pkey]={name:pkey,entries:[],dr:0,cr:0};nameOrd.push(pkey);}',
' nameMap[pkey].entries.push(ent);',
' nameMap[pkey].dr+=ent.debit;',
' nameMap[pkey].cr+=ent.credit;}',
// Sort: real parties alphabetical, "(No Name)" last
' nameOrd.sort(function(a,b){if(a==="(No Name)")return 1;if(b==="(No Name)")return -1;return a.localeCompare(b);});',
'',
' if(nameOrd.length===0){h+="<div class=\\"table-wrap airy-report\\" style=\\"padding:40px;text-align:center;color:#999\\">No transactions in this period.</div>";document.getElementById("report").innerHTML=h;return;}',
'',
// Pagination across flat entries
' var totalEntries=allEntries.length;',
' var ps=GL_STATE.pageSize,pg=GL_STATE.page;',
' var totalPages=Math.ceil(totalEntries/ps)||1;if(pg>totalPages)pg=totalPages;',
' var startIdx=(pg-1)*ps,endIdx=pg*ps,runIdx=0;',
'',
// Grand totals (all entries across all parties)
' var grandDr=0,grandCr=0;',
' for(var gi=0;gi<nameOrd.length;gi++){grandDr+=nameMap[nameOrd[gi]].dr;grandCr+=nameMap[nameOrd[gi]].cr;}',
' grandDr=rnd(grandDr);grandCr=rnd(grandCr);',
'',
' h+="<div class=\\"table-wrap airy-report\\">";',
' for(var po=0;po<nameOrd.length;po++){',
' var pname=nameOrd[po],grp=nameMap[pname];',
// Sort entries by date within each name group
' var psorted=grp.entries.slice().sort(function(a,b){return a.date<b.date?-1:a.date>b.date?1:0;});',
'',
' h+="<table class=\\"tb-table gl-fit-table\\" style=\\"margin-bottom:20px\\">";',
// Name header
' h+="<thead><tr class=\\"gl-acct-hdr\\"><td colspan=\\"7\\">"+esc_html_(pname)+"</td></tr>";',
// Column headers
' h+="<tr style=\\"background:var(--navy)\\">";',
' h+="<th>Date</th>";',
' h+="<th>Reference</th>";',
' h+="<th style=\\"min-width:180px\\">Description</th>";',
' h+="<th>Account</th>";',
' h+="<th>Source</th>";',
' h+="<th class=\\"num\\">Debit</th>";',
' h+="<th class=\\"num\\">Credit</th>";',
' h+="</tr></thead><tbody>";',
'',
// Entry rows (paginated across global index)
' var pDr=0,pCr=0;',
' for(var ei2=0;ei2<psorted.length;ei2++){',
' pDr+=psorted[ei2].debit;pCr+=psorted[ei2].credit;',
' if(runIdx>=startIdx&&runIdx<endIdx){',
' var en=psorted[ei2];',
' h+="<tr class=\\"gl-entry-row\\">";',
' h+="<td>"+fmtGLDate(en.date)+"</td>";',
' h+="<td>"+en.ref+"</td>";',
' h+="<td>"+en.desc+"</td>";',
' h+="<td class=\\"gl-meta-col\\">"+en.acctCode+" - "+en.acctTitle+"</td>";',
' h+="<td class=\\"gl-meta-col\\">"+en.source+"</td>";',
' h+="<td class=\\"num\\">"+(en.debit>0?fmt(en.debit):"-")+"</td>";',
' h+="<td class=\\"num\\">"+(en.credit>0?fmt(en.credit):"-")+"</td>";',
' h+="</tr>";}',
' runIdx++;}',
'',
// Name subtotal
' h+="<tr class=\\"gl-period\\">";',
' h+="<td colspan=\\"5\\" style=\\"text-align:right;font-weight:600\\">Subtotal ("+psorted.length+" "+(psorted.length===1?"transaction":"transactions")+")</td>";',
' h+="<td class=\\"num\\" style=\\"font-weight:600\\">"+fmt(rnd(pDr))+"</td>";',
' h+="<td class=\\"num\\" style=\\"font-weight:600\\">"+fmt(rnd(pCr))+"</td>";',
' h+="</tr>";',
' h+="</tbody></table>";}',
'',
// Grand total
' h+="<table class=\\"tb-table\\" style=\\"margin-bottom:20px\\"><tbody>";',
' h+="<tr class=\\"gl-closing\\">";',
' h+="<td colspan=\\"5\\" style=\\"text-align:right\\">Grand Total ("+nameOrd.length+" "+(nameOrd.length===1?"name":"names")+")</td>";',
' h+="<td class=\\"num\\">"+fmt(grandDr)+"</td>";',
' h+="<td class=\\"num\\">"+fmt(grandCr)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table>";',
'',
// Pagination
' if(totalEntries>ps){',
' h+="<div class=\\"pager\\">Showing "+(startIdx+1)+"-"+Math.min(endIdx,totalEntries)+" of "+totalEntries+" entries ";',
' h+="<button class=\\"pager-btn\\" "+(pg<=1?"disabled":"onclick=\\"glPage("+(pg-1)+")\\"")+">« Prev</button>";',
' for(var pi2=1;pi2<=totalPages;pi2++){if(totalPages>10&&Math.abs(pi2-pg)>3&&pi2!==1&&pi2!==totalPages){if(pi2===2||pi2===totalPages-1)h+="<span>...</span>";continue;}h+="<button class=\\"pager-btn"+(pi2===pg?" active":"")+"\\" onclick=\\"glPage("+pi2+")\\">"+pi2+"</button>";}',
' h+="<button class=\\"pager-btn\\" "+(pg>=totalPages?"disabled":"onclick=\\"glPage("+(pg+1)+")\\"")+">» Next</button>";',
' h+=" | Rows: ";',
' [50,100,200].forEach(function(sz){h+="<button class=\\"pager-btn"+(ps===sz?" active":"")+"\\" onclick=\\"GL_STATE.pageSize="+sz+";GL_STATE.page=1;quietRecompute(\'gl\')\\">"+sz+"</button> ";});',
' h+="<button class=\\"pager-btn"+(ps===99999?" active":"")+"\\" onclick=\\"GL_STATE.pageSize=99999;GL_STATE.page=1;quietRecompute(\'gl\')\\">All</button>";',
' h+="</div>";}',
' h+="</div>";',
' document.getElementById("report").innerHTML=h;}',
'',
// Helper for HTML-escaping name values (may have & or < etc)
'function esc_html_(s){return String(s||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");}',
'',
'/* ══════════════════════════════════════',
' INCOME STATEMENT',
' ══════════════════════════════════════ */',
'var IS_FSGROUPS=["Revenue","Cost of Sales","Other Income","Operating Expenses","Other Expenses","Income Tax"];',
'var IS_SECTION_LABEL={Revenue:"Income","Cost of Sales":"Less: Cost of Sales","Other Income":"Add: Other Income","Operating Expenses":"Less: Operating Expenses","Other Expenses":"Less: Other Expenses","Income Tax":"Less: Income Tax"};',
'var IS_SECTION_NORMAL={Revenue:"Credit","Cost of Sales":"Debit","Other Income":"Credit","Operating Expenses":"Debit","Other Expenses":"Debit","Income Tax":"Debit"};',
'var IS_TOTAL_LABEL={Revenue:"Total Income","Cost of Sales":"Total Cost of Sales","Other Income":"Total Other Income","Operating Expenses":"Total Operating Expenses","Other Expenses":"Total Other Expenses","Income Tax":"Total Income Tax"};',
'var IS_DEDUCTIVE={Revenue:false,"Cost of Sales":true,"Other Income":false,"Operating Expenses":true,"Other Expenses":true,"Income Tax":true};',
'',
'function buildIS(){buildISControls();recomputeIS();}',
'',
'function buildISControls(){',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(function(d){return d&&d.length>=10}).sort();',
' var minD=dates[0]||"2025-01-01",maxD=dates[dates.length-1]||"2026-12-31";',
' var h="";',
// Row 1: Date + Compare + Variance + Columns + More
' h+="<div class=\\"ctrl-bar\\">";',
' h+="<div class=\\"ctrl-group\\"><label>Date Range</label><select id=\\"isPreset\\" onchange=\\"applyISPreset()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' h+="<option value=\\"tfy\\" selected>This Financial Year</option>";',
' h+="<option value=\\"ytd\\">Year-to-Date</option>";',
' h+="<option value=\\"lfy\\">Last Financial Year</option>";',
' h+="<option value=\\"tq\\">This Quarter</option>";',
' h+="<option value=\\"qtd\\">Quarter-to-Date</option>";',
' h+="<option value=\\"lq\\">Last Quarter</option>";',
' h+="<option value=\\"tm\\">This Month</option>";',
' h+="<option value=\\"mtd\\">Month-to-Date</option>";',
' h+="<option value=\\"lm\\">Last Month</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"q1\\">Quarter 1</option><option value=\\"q2\\">Quarter 2</option><option value=\\"q3\\">Quarter 3</option><option value=\\"q4\\">Quarter 4</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"custom\\">Custom</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\"><label>From</label><input type=\\"date\\" id=\\"isFrom\\" value=\\""+minD+"\\" onchange=\\"document.getElementById(\'isPreset\').value=\'custom\';recomputeIS()\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>To</label><input type=\\"date\\" id=\\"isTo\\" value=\\""+maxD+"\\" onchange=\\"document.getElementById(\'isPreset\').value=\'custom\';recomputeIS()\\"></div>";',
' h+="<div class=\\"is-ctrl-divider\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Compare</label><select id=\\"isCmp\\" onchange=\\"onISCmpChange()\\">";',
' h+="<option value=\\"sppy\\">Same Period Last Year</option>";',
' h+="<option value=\\"fpy\\">Full Prior Year</option>";',
' h+="<option value=\\"pq\\">Previous Quarter</option>";',
' h+="<option value=\\"multi\\">Multi-Period</option>";',
' h+="<option value=\\"custom\\">Custom</option>";',
' h+="<option value=\\"none\\" selected>None</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"isMultiIntGrp\\" style=\\"display:none\\"><label>Interval</label><select id=\\"isMultiInt\\" onchange=\\"IS_STATE.multiInterval=this.value;recomputeIS()\\">";',
' h+="<option value=\\"monthly\\">Monthly</option><option value=\\"quarterly\\">Quarterly</option><option value=\\"annually\\">Annually</option></select></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"isMultiNumGrp\\" style=\\"display:none\\"><label>Periods</label><select id=\\"isMultiNum\\" onchange=\\"IS_STATE.multiCount=parseInt(this.value);recomputeIS()\\">";',
' for(var pp=2;pp<=12;pp++)h+="<option value=\\""+pp+"\\""+(pp===4?" selected":"")+">"+pp+"</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"isCustomCmpGrp\\" style=\\"display:none\\"><label>Custom Period End</label><input type=\\"date\\" id=\\"isCustomCmpDate\\" value=\\""+maxD+"\\" onchange=\\"IS_STATE.customCompareDate=this.value;recomputeIS()\\"></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"isVarGrp\\"><label>Variance</label><select id=\\"isVar\\" onchange=\\"IS_STATE.showVariance=this.value===\'yes\';recomputeIS()\\">";',
' h+="<option value=\\"no\\" selected>Hide</option>";',
' h+="<option value=\\"yes\\">Show</option>";',
' h+="</select></div>";',
' h+="<div class=\\"is-ctrl-divider\\"></div>";',
' h+="<div class=\\"ctrl-group\\" style=\\"position:relative\\"><label>Columns</label>"+renderISColsBtn()+"<div id=\\"isColsWrap\\"></div></div>";',
' h+="<div class=\\"ctrl-group\\" style=\\"position:relative\\"><label> </label><button id=\\"isMoreBtn\\" class=\\"ctrl-btn"+(IS_STATE.showIS_More?" on":"")+"\\" onclick=\\"toggleISMore()\\">More \\u25BE</button><div id=\\"isMoreWrap\\"></div></div>";',
' h+="</div>";',
// Row 2: Expand/Collapse + Hide Decimals
' h+="<div class=\\"ctrl-bar\\">";',
' h+="<button id=\\"isExpandBtn\\" class=\\"ctrl-btn\\" onclick=\\"toggleISAll()\\">"+isExpandLabel()+"</button>";',
' h+=renderHideDecimalsToggle();',
' h+="</div>";',
' document.getElementById("controls").innerHTML=h;',
' applyISPreset();',
' onISCmpChange();',
'}',
'',
'function isColsCount(){var n=0;if(IS_STATE.showTaxLine1701)n++;if(IS_STATE.showTaxLine2551)n++;if(IS_STATE.showNoteGroup)n++;if(IS_STATE.showTrend)n++;return n;}',
'function renderISColsBtn(){',
' var n=isColsCount();',
' if(n===0)return "<button id=\\"isColsBtn\\" class=\\"ctrl-dropdown-btn\\" onclick=\\"toggleISCols()\\">Columns \\u25BE</button>";',
' return "<button id=\\"isColsBtn\\" class=\\"more-btn-active\\" onclick=\\"toggleISCols()\\"><span>Columns ("+n+") \\u25BE</span><span class=\\"more-btn-active-x\\" onclick=\\"event.stopPropagation();resetAllISCols()\\">\\u2715</span></button>";',
'}',
'function updateISColsBtn(){var b=document.getElementById("isColsBtn");if(b&&b.parentNode)b.outerHTML=renderISColsBtn();}',
'function resetAllISCols(){IS_STATE.showTaxLine1701=false;IS_STATE.showTaxLine2551=false;IS_STATE.showNoteGroup=false;IS_STATE.showTrend=false;var wrap=document.getElementById("isColsWrap");if(wrap)wrap.innerHTML="";updateISColsBtn();quietRecompute("is");}',
'',
'function isExpandLabel(){var allCollapsed=IS_FSGROUPS.every(function(g){return IS_STATE.collapsedSections[g]===true;});return allCollapsed?"Expand All":"Collapse All";}',
'function updateISExpandBtn(){var b=document.getElementById("isExpandBtn");if(b)b.innerText=isExpandLabel();}',
'',
'function toggleISAll(){',
' var allCollapsed=IS_FSGROUPS.every(function(g){return IS_STATE.collapsedSections[g]===true;});',
' if(allCollapsed){IS_STATE.collapsedSections={};}',
' else{IS_STATE.collapsedSections={};IS_FSGROUPS.forEach(function(g){IS_STATE.collapsedSections[g]=true;});}',
' recomputeIS();',
'}',
'',
'function toggleISSection(fsGroup){',
' if(IS_STATE.collapsedSections[fsGroup])delete IS_STATE.collapsedSections[fsGroup];',
' else IS_STATE.collapsedSections[fsGroup]=true;',
' recomputeIS();',
'}',
'',
'function applyISPreset(){',
' var v=document.getElementById("isPreset").value;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' var maxD=dates[dates.length-1]||"2026-12-31",minD=dates[0]||"2025-01-01";',
' var today=new Date();var todayStr=today.getFullYear()+"-"+pad(today.getMonth()+1)+"-"+pad(today.getDate());',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' if(v==="custom"){IS_STATE.from=document.getElementById("isFrom").value;IS_STATE.to=document.getElementById("isTo").value;}',
' else{var r=computeDateRange(v,maxD,fyM,todayStr,minD);if(r){IS_STATE.from=r.from;IS_STATE.to=r.to;document.getElementById("isFrom").value=r.from;document.getElementById("isTo").value=r.to;}}',
' IS_STATE.preset=v;',
' recomputeIS();',
'}',
'',
'function onISCmpChange(){',
' IS_STATE.compareMode=document.getElementById("isCmp").value;',
' var cg=document.getElementById("isCustomCmpGrp");if(cg)cg.style.display=IS_STATE.compareMode==="custom"?"flex":"none";',
' var mi=document.getElementById("isMultiIntGrp");if(mi)mi.style.display=IS_STATE.compareMode==="multi"?"flex":"none";',
' var mn=document.getElementById("isMultiNumGrp");if(mn)mn.style.display=IS_STATE.compareMode==="multi"?"flex":"none";',
// Variance hidden when Multi or None
' var vg=document.getElementById("isVarGrp");if(vg)vg.style.display=(IS_STATE.compareMode==="multi"||IS_STATE.compareMode==="none")?"none":"flex";',
' recomputeIS();',
'}',
'',
'/* ── IS Columns Panel ── */',
'function toggleISCols(){',
' var wrap=document.getElementById("isColsWrap");',
' if(wrap.innerHTML){wrap.innerHTML="";return;}',
' var h="<div class=\\"panel\\" style=\\"width:220px\\">";',
' h+="<div class=\\"panel-hdr\\"><span class=\\"panel-title\\">Columns</span><button class=\\"panel-close\\" onclick=\\"toggleISCols()\\">\\u2715</button></div>";',
' h+="<label class=\\"col-check\\"><input type=\\"checkbox\\" "+(IS_STATE.showTrend?"checked":"")+" onchange=\\"IS_STATE.showTrend=this.checked;updateISColsBtn();quietRecompute(\'is\')\\"> Trend</label>";',
' h+="<label class=\\"col-check\\"><input type=\\"checkbox\\" "+(IS_STATE.showTaxLine1701?"checked":"")+" onchange=\\"IS_STATE.showTaxLine1701=this.checked;updateISColsBtn();quietRecompute(\'is\')\\"> Tax Line (1701)</label>";',
' h+="<label class=\\"col-check\\"><input type=\\"checkbox\\" "+(IS_STATE.showTaxLine2551?"checked":"")+" onchange=\\"IS_STATE.showTaxLine2551=this.checked;updateISColsBtn();quietRecompute(\'is\')\\"> Tax Line (2551Q)</label>";',
' h+="<label class=\\"col-check\\"><input type=\\"checkbox\\" "+(IS_STATE.showNoteGroup?"checked":"")+" onchange=\\"IS_STATE.showNoteGroup=this.checked;updateISColsBtn();quietRecompute(\'is\')\\"> Note Group</label>";',
' h+="</div>";',
' wrap.innerHTML=h;',
'}',
'',
'/* ── IS More Panel ── */',
'function toggleISMore(){',
' var wrap=document.getElementById("isMoreWrap");',
' if(wrap.innerHTML){wrap.innerHTML="";return;}',
' var h="<div class=\\"panel\\" style=\\"width:240px\\">";',
' h+="<div class=\\"panel-hdr\\"><span class=\\"panel-title\\">More Options</span><button class=\\"panel-close\\" onclick=\\"toggleISMore()\\">\\u2715</button></div>";',
' h+="<label class=\\"col-check\\"><input type=\\"checkbox\\" "+(IS_STATE.showPctRevenue?"checked":"")+" onchange=\\"IS_STATE.showPctRevenue=this.checked;quietRecompute(\'is\')\\"> Show % of Total Revenue</label>";',
' h+="</div>";',
' wrap.innerHTML=h;',
'}',
'',
'/* ── IS Computation ── */',
'function computeIS(jeRows,coa,fromDate,toDate,fyM){',
' var acctMap={},acctOrd=[];',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var t=String(a.title).trim(),tp=String(a.type).trim();',
' if(tp!=="Revenue"&&tp!=="Expense")continue;',
' var fg=String(a.fsGroup||"").trim();',
' if(IS_FSGROUPS.indexOf(fg)<0)continue;',
' acctMap[t]={code:String(a.code||""),title:t,type:tp,fsGroup:fg,taxLine:a.taxLine||"",taxLine2551:a.ptTaxLine||"",noteGroup:a.noteGroup||"",normal:String(a.normal||"").trim(),net:0};',
' acctOrd.push(t);',
' }',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(!acctMap[je.account])continue;var d=je.date||"";if(d<fromDate||d>toDate)continue;acctMap[je.account].net+=(je.debit||0)-(je.credit||0);}',
// Build sections
' var sections={};IS_FSGROUPS.forEach(function(g){sections[g]={fsGroup:g,accounts:[],total:0};});',
' for(var k=0;k<acctOrd.length;k++){var t=acctOrd[k],a=acctMap[t];',
// FIX (Apr 25, 2026): Sign normalization MUST be FSGroup-based, not per-account Normal Balance.
// Per-account flipping was wrong for contra-revenue (Sales Returns, Sales Discounts - Revenue FSGroup but Debit normal).
// Aligns with GSheets IS_Helper formula: IF(FSGroup=Revenue OR Other Income, Cr-Dr, Dr-Cr).
' var sectionIsCreditSide=(a.fsGroup==="Revenue"||a.fsGroup==="Other Income");',
' var displayAmt=sectionIsCreditSide?-a.net:a.net;',
' displayAmt=rnd(displayAmt);',
' if(displayAmt===0)continue;',
' sections[a.fsGroup].accounts.push({code:a.code,title:t,amount:displayAmt,normal:a.normal,taxLine:a.taxLine,taxLine2551:a.taxLine2551,noteGroup:a.noteGroup});',
' sections[a.fsGroup].total+=displayAmt;',
' }',
// Sort accounts within each section by code asc
' IS_FSGROUPS.forEach(function(g){sections[g].total=rnd(sections[g].total);sections[g].accounts.sort(function(x,y){return x.code<y.code?-1:x.code>y.code?1:0;});});',
// Key totals
' var rev=sections.Revenue.total||0;',
' var cos=sections["Cost of Sales"].total||0;',
' var oi=sections["Other Income"].total||0;',
' var opex=sections["Operating Expenses"].total||0;',
' var oexp=sections["Other Expenses"].total||0;',
' var it=sections["Income Tax"].total||0;',
' var grossProfit=rnd(rev-cos);',
' var nibt=rnd(grossProfit+oi-opex-oexp);',
' var netIncome=rnd(nibt-it);',
' return{sections:sections,totalRevenue:rev,totalCOS:cos,totalOI:oi,totalOpEx:opex,totalOExp:oexp,totalIT:it,grossProfit:grossProfit,nibt:nibt,netIncome:netIncome};',
'}',
'',
'function computeISPeriods(){',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var from=document.getElementById("isFrom").value||IS_STATE.from;',
' var to=document.getElementById("isTo").value||IS_STATE.to;',
' IS_STATE.from=from;IS_STATE.to=to;',
' var je=jeView_();',
' var periods=[{label:fmtRangeLabel(from,to),from:from,to:to,is:computeIS(je,DATA.coa,from,to,fyM)}];',
' var mode=IS_STATE.compareMode;',
' if(mode==="sppy"){var pyFrom=shiftYear(from,-1),pyTo=shiftYear(to,-1);periods.push({label:fmtRangeLabel(pyFrom,pyTo),from:pyFrom,to:pyTo,is:computeIS(je,DATA.coa,pyFrom,pyTo,fyM)});}',
' else if(mode==="fpy"){var yr=parseInt(from.substring(0,4));var pyFrom,pyTo;if(fyM===1){pyFrom=(yr-1)+"-01-01";pyTo=(yr-1)+"-12-31";}else{pyFrom=(yr-1)+"-"+pad(fyM)+"-01";pyTo=yr+"-"+pad(fyM-1)+"-"+ld(fyM-1,yr);}periods.push({label:fmtRangeLabel(pyFrom,pyTo),from:pyFrom,to:pyTo,is:computeIS(je,DATA.coa,pyFrom,pyTo,fyM)});}',
' else if(mode==="pq"){var pqFrom=shiftMonths(from,-3),pqTo=shiftMonths(to,-3);periods.push({label:fmtRangeLabel(pqFrom,pqTo),from:pqFrom,to:pqTo,is:computeIS(je,DATA.coa,pqFrom,pqTo,fyM)});}',
' else if(mode==="custom"){var cd=IS_STATE.customCompareDate||document.getElementById("isCustomCmpDate").value;var cTo=cd;var fYr=parseInt(from.substring(0,4)),cYr=parseInt(cd.substring(0,4));var yrDiff=cYr-fYr;var cFrom=shiftYear(from,yrDiff);periods.push({label:fmtRangeLabel(cFrom,cTo),from:cFrom,to:cTo,is:computeIS(je,DATA.coa,cFrom,cTo,fyM)});}',
' else if(mode==="multi"){periods=[];var interval=IS_STATE.multiInterval||"monthly";var count=IS_STATE.multiCount||4;for(var i=0;i<count;i++){var pFrom,pTo;if(interval==="monthly"){pFrom=shiftMonths(from,-i);pTo=shiftMonths(to,-i);}else if(interval==="quarterly"){pFrom=shiftMonths(from,-i*3);pTo=shiftMonths(to,-i*3);}else{pFrom=shiftYear(from,-i);pTo=shiftYear(to,-i);}periods.push({label:fmtRangeLabel(pFrom,pTo),from:pFrom,to:pTo,is:computeIS(je,DATA.coa,pFrom,pTo,fyM)});}}',
' return periods;',
'}',
'function fmtRangeLabel(from,to){var mos=["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];var fMo=parseInt(from.substring(5,7)),fDy=parseInt(from.substring(8,10)),fYr=from.substring(2,4);var tMo=parseInt(to.substring(5,7)),tDy=parseInt(to.substring(8,10)),tYr=to.substring(2,4);if(from===to)return mos[fMo]+" "+fDy+", "+from.substring(0,4);if(fYr===tYr&&fMo===tMo)return mos[fMo]+" "+from.substring(0,4);return mos[fMo]+" - "+mos[tMo]+" "+to.substring(0,4);}',
'',
'function recomputeIS(){if(!document.getElementById("isFrom"))return;',
' if(!SKEL_SUPPRESS){showSkeleton("is");SKEL_SUPPRESS=true;setTimeout(function(){recomputeIS();SKEL_SUPPRESS=false;fadeInReport("is");},200);return;}',
' var periods=computeISPeriods();',
' renderIS(periods);',
' updateISExpandBtn();',
'}',
'',
'/* ── IS Render ── */',
'function renderIS(periods){',
' var primary=periods[0],data=primary.is;',
' var numP=periods.length;',
' var showVar=IS_STATE.showVariance&&numP===2;',
' var h=renderReportHeader(getReportMetadata("Income Statement","period",{from:primary.from,to:primary.to}));',
// Meta cols
' var metaCols=[];',
' if(IS_STATE.showTaxLine1701)metaCols.push({id:"taxLine",label:"Tax Line (1701)"});',
' if(IS_STATE.showTaxLine2551)metaCols.push({id:"taxLine2551",label:"Tax Line (2551Q)"});',
' if(IS_STATE.showNoteGroup)metaCols.push({id:"noteGroup",label:"Note Group"});',
' var showTrend=IS_STATE.showTrend&&numP===1;',
' var showPct=IS_STATE.showPctRevenue&&data.totalRevenue!==0;',
// Total column count: particulars + meta + trend? + (amount+pct?)*N + variance?*2
' var colAfterTitle=metaCols.length+(showTrend?1:0)+numP*(1+(showPct?1:0))+(showVar?2:0);',
' var totalCols=1+colAfterTitle;',
'',
' h+="<div class=\\"table-wrap airy-report\\"><table class=\\"tb-table\\"><thead><tr>";',
' h+="<th style=\\"min-width:220px\\">Particulars</th>";',
' for(var mi=0;mi<metaCols.length;mi++)h+="<th>"+metaCols[mi].label+"</th>";',
' if(showTrend)h+="<th style=\\"text-align:center;min-width:80px\\">Trend</th>";',
' for(var p=0;p<numP;p++){h+="<th class=\\"num\\">"+periods[p].label+"</th>";if(showPct)h+="<th class=\\"num\\" style=\\"width:55px\\">%</th>";}',
' if(showVar)h+="<th class=\\"num\\">Variance</th><th class=\\"num\\">Var %</th>";',
' h+="</tr></thead><tbody>";',
'',
// Empty check: any data in any period?
' var anyData=periods.some(function(pp){return IS_FSGROUPS.some(function(g){return pp.is.sections[g].accounts.length>0;});});',
' if(!anyData){h+="<tr class=\\"is-empty-row\\"><td colspan=\\""+totalCols+"\\">No transactions in this period.</td></tr>";h+="</tbody></table></div>";document.getElementById("report").innerHTML=h;return;}',
'',
// Canonical rendering - with hasCOS/hasIT detection across ALL periods
' var hasCOS=periods.some(function(pp){return pp.is.sections["Cost of Sales"].accounts.length>0;});',
' var hasIT=periods.some(function(pp){return pp.is.sections["Income Tax"].accounts.length>0;});',
'',
' h+=renderISSection(periods,"Revenue",metaCols,showPct,showVar,showTrend);',
' if(hasCOS){h+=renderISSection(periods,"Cost of Sales",metaCols,showPct,showVar,showTrend);',
' h+=renderISKeyTotal(periods,"Gross Profit","grossProfit",metaCols,showPct,showVar,showTrend,false);}',
' h+=renderISSection(periods,"Other Income",metaCols,showPct,showVar,showTrend);',
' h+=renderISSection(periods,"Operating Expenses",metaCols,showPct,showVar,showTrend);',
' h+=renderISSection(periods,"Other Expenses",metaCols,showPct,showVar,showTrend);',
' if(hasIT){',
' h+=renderISKeyTotal(periods,"Net Income Before Income Tax","nibt",metaCols,showPct,showVar,showTrend,false);',
' h+=renderISSection(periods,"Income Tax",metaCols,showPct,showVar,showTrend);',
' }',
' h+=renderISKeyTotal(periods,"Net Income","netIncome",metaCols,showPct,showVar,showTrend,true);',
'',
' h+="</tbody></table>";',
' h+="</div>";',
' document.getElementById("report").innerHTML=h;',
'}',
'',
// Renders a FSGroup section (header + optional accounts + subtotal) across N periods
'function renderISSection(periods,fsGroup,metaCols,showPct,showVar,showTrend){',
' var hasAny=periods.some(function(p){return p.is.sections[fsGroup].accounts.length>0;});',
' if(!hasAny)return "";',
' var isCollapsed=IS_STATE.collapsedSections[fsGroup]===true;',
' var isIncomeLike=(fsGroup==="Revenue"||fsGroup==="Other Income");',
' var sectionNormal=IS_SECTION_NORMAL[fsGroup];',
' var chev=isCollapsed?"\\u25B8":"\\u25BE";',
' var numP=periods.length;',
' var totalColCount=1+metaCols.length+(showTrend?1:0)+numP*(1+(showPct?1:0))+(showVar?2:0);',
' var h="";',
// Section header row: plain white bg, bold black, chevron
// When collapsed: show amounts on this row (compact mode)
// When expanded: no amounts on this row (accounts + Total row handle display)
' h+="<tr class=\\"is-sec-row"+(isCollapsed?" is-sec-row-collapsed":"")+"\\" onclick=\\"toggleISSection(\'"+fsGroup+"\')\\">";',
' if(isCollapsed){',
// Label cell (narrow, no colspan - amounts fill remaining columns)
' h+="<td><span class=\\"is-chev\\">"+chev+"</span>"+IS_SECTION_LABEL[fsGroup]+"</td>";',
' for(var mh=0;mh<metaCols.length;mh++)h+="<td></td>";',
' if(showTrend)h+="<td></td>";',
' for(var ph=0;ph<periods.length;ph++){',
' var shAmt=periods[ph].is.sections[fsGroup].total;',
' h+="<td class=\\"num\\">"+fmtISNum(shAmt)+"</td>";',
' if(showPct){var shRev=periods[ph].is.totalRevenue;var shPct=(fsGroup==="Revenue")?"100.0%":fmtISPct(shAmt,shRev);h+="<td class=\\"is-pct-col\\">"+shPct+"</td>";}',
' }',
' if(showVar){',
' var shCY=periods[0].is.sections[fsGroup].total;var shPY=periods[1].is.sections[fsGroup].total;',
' var shVA=rnd(shCY-shPY);var shVP=shPY!==0?rnd((shVA/Math.abs(shPY))*100):(shCY!==0?100:0);',
' h+=fmtISVarTDs(shVA,shVP,isIncomeLike,false);',
' }',
' }else{',
// Expanded: label spans full width, no amount
' h+="<td colspan=\\""+totalColCount+"\\"><span class=\\"is-chev\\">"+chev+"</span>"+IS_SECTION_LABEL[fsGroup]+"</td>";',
' }',
' h+="</tr>";',
' if(!isCollapsed){',
// Account rows - union of accounts across all periods
' var acctMapUnion={};',
' for(var pp=0;pp<periods.length;pp++){var accts=periods[pp].is.sections[fsGroup].accounts;for(var ai=0;ai<accts.length;ai++){if(!acctMapUnion[accts[ai].title])acctMapUnion[accts[ai].title]=accts[ai];}}',
' var unionTitles=Object.keys(acctMapUnion);',
// Sort by code ascending
' unionTitles.sort(function(x,y){var cx=acctMapUnion[x].code,cy=acctMapUnion[y].code;return cx<cy?-1:cx>cy?1:0;});',
' for(var i=0;i<unionTitles.length;i++){var title=unionTitles[i];var sampleA=acctMapUnion[title];',
// Contra detection: account's normal balance != section's natural normal
' var isContra=sampleA.normal&§ionNormal&&sampleA.normal!==sectionNormal;',
// "Less:" prefix for contra accounts (any section), skip if title already starts with "less:"
' var displayTitle=title;',
' if(isContra&&title.toLowerCase().indexOf("less:")!==0)displayTitle="Less: "+title;',
' h+="<tr class=\\"is-acct-row\\">";',
' h+="<td>"+displayTitle+"</td>";',
' for(var mi2=0;mi2<metaCols.length;mi2++){var mc=metaCols[mi2];h+="<td class=\\"is-meta-col\\">"+(sampleA[mc.id]||"")+"</td>";}',
' if(showTrend)h+="<td style=\\"text-align:center\\">"+makeISSparkline(title,isIncomeLike)+"</td>";',
// Amounts per period - each amount cell clickable independently; contra amounts displayed as negative
' var amts=[];',
' for(var pi=0;pi<periods.length;pi++){',
' var acct=findAcctByTitle(periods[pi].is.sections[fsGroup].accounts,title);var rawAmt=acct?acct.amount:0;',
// Flip to negative if contra (e.g., refunds, contra expenses)
' var dispAmt=isContra?-Math.abs(rawAmt):rawAmt;',
' amts.push(dispAmt);',
' var pFrom=periods[pi].from||IS_STATE.from;var pTo=periods[pi].to||IS_STATE.to;',
' var onclickAttr=" onclick=\\"drillFromISPeriod(\'"+escJS(title)+"\',\'"+pFrom+"\',\'"+pTo+"\')\\"";',
' h+="<td class=\\"num is-acct-amt\\""+onclickAttr+">"+fmtISNum(dispAmt)+"</td>";',
' if(showPct){h+="<td class=\\"is-pct-col\\">"+fmtISPct(dispAmt,periods[pi].is.totalRevenue)+"</td>";}',
' }',
' if(showVar){var vCY=amts[0]||0,vPY=amts[1]||0;var va2=rnd(vCY-vPY);var vp2=vPY!==0?rnd((va2/Math.abs(vPY))*100):(vCY!==0?100:0);h+=fmtISVarTDs(va2,vp2,isIncomeLike,false);}',
' h+="</tr>";',
' }',
// Section total row - only rendered when expanded
' h+="<tr class=\\"is-sec-total\\"><td colspan=\\""+(1+metaCols.length)+"\\">"+IS_TOTAL_LABEL[fsGroup]+"</td>";',
' if(showTrend)h+="<td></td>";',
' for(var p2=0;p2<periods.length;p2++){',
' var secAmt2=periods[p2].is.sections[fsGroup].total;',
' h+="<td class=\\"num\\">"+fmtISNum(secAmt2)+"</td>";',
' if(showPct){var rev2=periods[p2].is.totalRevenue;var pct3=(fsGroup==="Revenue")?"100.0%":fmtISPct(secAmt2,rev2);h+="<td class=\\"is-pct-col\\">"+pct3+"</td>";}',
' }',
' if(showVar){',
' var cy3=periods[0].is.sections[fsGroup].total;var py3=periods[1].is.sections[fsGroup].total;',
' var va3=rnd(cy3-py3);var vp3=py3!==0?rnd((va3/Math.abs(py3))*100):(cy3!==0?100:0);',
' h+=fmtISVarTDs(va3,vp3,isIncomeLike,false);',
' }',
' h+="</tr>";',
' }',
' return h;',
'}',
'',
// Key total row (Gross Profit, NIBT, NI). `isNI` = navy emphasis styling
'function renderISKeyTotal(periods,label,field,metaCols,showPct,showVar,showTrend,isNI){',
' var rowClass=isNI?"is-ni-row":"is-key-total";',
' var pctColor=isNI?"color:rgba(255,255,255,.9)":"";',
' var h="<tr class=\\""+rowClass+"\\"><td colspan=\\""+(1+metaCols.length)+"\\">"+label+"</td>";',
' if(showTrend)h+="<td></td>";',
' for(var p=0;p<periods.length;p++){',
' var v=periods[p].is[field];',
' h+="<td class=\\"num\\">"+fmtISNum(v)+"</td>";',
' if(showPct){h+="<td class=\\"is-pct-col\\" style=\\""+pctColor+"\\">"+fmtISPct(v,periods[p].is.totalRevenue)+"</td>";}',
' }',
' if(showVar){',
' var cy=periods[0].is[field],py=periods[1].is[field];',
' var va=rnd(cy-py);var vp=py!==0?rnd((va/Math.abs(py))*100):(cy!==0?100:0);',
// Net income lines are income-like (increase = good)
' h+=fmtISVarTDs(va,vp,true,false,isNI);',
' }',
' h+="</tr>";',
' return h;',
'}',
'',
// Helper: finds account by title within a section
'function findAcctByTitle(accts,title){for(var i=0;i<accts.length;i++){if(accts[i].title===title)return accts[i];}return null;}',
'',
// IS-specific formatters: negatives wrapped in parens
'function fmtISNum(n){if(n===0||n===null||n===undefined)return"-";var abs=Math.abs(n);var s=APP_STATE.hideDecimals?Math.round(abs).toLocaleString("en-US"):abs.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});return n<0?"("+s+")":s;}',
'function fmtISAcctAmt(n){if(n===0||n===null||n===undefined)return"-";var abs=Math.abs(n);return APP_STATE.hideDecimals?Math.round(abs).toLocaleString("en-US"):abs.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});}',
'function fmtISPct(n,rev){if(!rev||rev===0)return"-";var p=(n/rev)*100;return p.toFixed(1)+"%";}',
'',
// Variance color logic - signed-aware:
// Income-like (Revenue/Other Income/NI): increase = green (good); decrease = red
// Expense-like (COS/OpEx/OExp/IT): increase = red (bad); decrease = green
// We pass `isIncomeLike` flag and the displayed delta (already sign-corrected for expense sections)
'function fmtISVarTDs(va,vp,isIncomeLike,onSection,onNavy){',
' var color;',
' if(va===0)color="#9CA3AF";',
' else{var good=isIncomeLike?(va>0):(va<0);color=good?"#2E7D32":"#C62828";}',
' var vaStr=va===0?"-":(va>0?"+":"-")+fmtISAcctAmt(Math.abs(va));',
' var vpStr=va===0?"-":(vp>0?"+":"-")+Math.abs(vp).toFixed(1)+"%";',
' return "<td class=\\"num var-cell\\" style=\\"color:"+color+"\\">"+vaStr+"</td>"+"<td class=\\"num var-cell\\" style=\\"color:"+color+"\\">"+vpStr+"</td>";',
'}',
'',
// Trend sparkline for IS accounts: last 6 periods ending at current To date, monthly cadence
// Sign-corrected coloring: income-like account up=green, expense up=red
'function makeISSparkline(acctTitle,isIncomeLike){',
' var to=IS_STATE.to||document.getElementById("isTo").value;',
' var from=IS_STATE.from||document.getElementById("isFrom").value;',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var pts=[];',
// Build 6 periods, each = same width as current period, shifted back monthly
' var monthsSpan=monthsBetween(from,to);if(monthsSpan<1)monthsSpan=1;',
' for(var i=5;i>=0;i--){var pFrom=shiftMonths(from,-i*monthsSpan);var pTo=shiftMonths(to,-i*monthsSpan);',
' var is=computeIS(jeView_(),DATA.coa,pFrom,pTo,fyM);',
// Find account across all sections
' var amt=0;for(var gi=0;gi<IS_FSGROUPS.length;gi++){var a=findAcctByTitle(is.sections[IS_FSGROUPS[gi]].accounts,acctTitle);if(a){amt=a.amount;break;}}',
' pts.push(amt);}',
' if(pts.every(function(p){return p===0}))return"";',
' var max=Math.max.apply(null,pts.map(Math.abs));if(max===0)return"";',
' var w=70,h=18,points=[];',
' for(var j=0;j<pts.length;j++){var x=(j/(pts.length-1))*w;var y=h-((pts[j]+max)/(2*max))*(h-4)-2;points.push(x.toFixed(1)+","+y.toFixed(1));}',
' var last=pts[pts.length-1],first=pts[0];',
// For income: up (last>first) = good = green. For expense: up = bad = red.
' var up=last>first,down=last<first;',
' var col;if(!up&&!down)col="#888";else{var good=isIncomeLike?up:down;col=good?"#2E7D32":"#C62828";}',
' var lp=points[points.length-1].split(",");',
' return "<svg width=\\""+w+"\\" height=\\""+h+"\\" style=\\"vertical-align:middle\\"><polyline points=\\""+points.join(" ")+"\\" fill=\\"none\\" stroke=\\""+col+"\\" stroke-width=\\"1.5\\"/><circle cx=\\""+lp[0]+"\\" cy=\\""+lp[1]+"\\" r=\\"2\\" fill=\\""+col+"\\"/></svg>";',
'}',
'',
// Compute months between two ISO dates (integer)
'function monthsBetween(from,to){var fy=parseInt(from.substring(0,4)),fm=parseInt(from.substring(5,7));var ty=parseInt(to.substring(0,4)),tm=parseInt(to.substring(5,7));return(ty-fy)*12+(tm-fm)+1;}',
'',
'/* ── IS Export ── */',
'function expIS(){',
' var periods=computeISPeriods();',
' var primary=periods[0];',
' var numP=periods.length;',
' var showVar=IS_STATE.showVariance&&numP===2;',
' var metaCols=[];',
' if(IS_STATE.showTaxLine1701)metaCols.push({id:"taxLine",label:"Tax Line (1701)"});',
' if(IS_STATE.showTaxLine2551)metaCols.push({id:"taxLine2551",label:"Tax Line (2551Q)"});',
' if(IS_STATE.showNoteGroup)metaCols.push({id:"noteGroup",label:"Note Group"});',
' var showPct=IS_STATE.showPctRevenue&&primary.is.totalRevenue!==0;',
// Header row
' var hdr=["Particulars"];for(var mi=0;mi<metaCols.length;mi++)hdr.push(metaCols[mi].label);',
' for(var p=0;p<numP;p++){hdr.push(periods[p].label);if(showPct)hdr.push("%");}',
' if(showVar){hdr.push("Variance");hdr.push("Var %");}',
' var ws=[hdr];',
' var hasCOS=periods.some(function(pp){return pp.is.sections["Cost of Sales"].accounts.length>0;});',
' var hasIT=periods.some(function(pp){return pp.is.sections["Income Tax"].accounts.length>0;});',
// Helper: push a section (header + accounts + subtotal) across all periods
' function pushSection(fsGroup){',
' var hasAny=periods.some(function(p){return p.is.sections[fsGroup].accounts.length>0;});',
' if(!hasAny)return;',
' var deductive=IS_DEDUCTIVE[fsGroup];',
// Section header row
' var hdrRow=[IS_SECTION_LABEL[fsGroup]];',
' for(var m=0;m<metaCols.length;m++)hdrRow.push("");',
' for(var sp=0;sp<numP;sp++){var secAmt=periods[sp].is.sections[fsGroup].total;var disp=deductive?-secAmt:secAmt;hdrRow.push(disp);if(showPct){var rev=periods[sp].is.totalRevenue;hdrRow.push(fsGroup==="Revenue"?1:(rev?disp/rev:0));}}',
' if(showVar){var cy=periods[0].is.sections[fsGroup].total,py=periods[1].is.sections[fsGroup].total;var cyD=deductive?-cy:cy,pyD=deductive?-py:py;var va=rnd(cyD-pyD);var vp=pyD!==0?(va/Math.abs(pyD)):(cyD!==0?1:0);hdrRow.push(va);hdrRow.push(vp);}',
' ws.push(hdrRow);',
// Account rows (union across periods)
' var acctUnion={};',
' for(var pp=0;pp<numP;pp++){var accts=periods[pp].is.sections[fsGroup].accounts;for(var ai=0;ai<accts.length;ai++){if(!acctUnion[accts[ai].title])acctUnion[accts[ai].title]=accts[ai];}}',
' var titles=Object.keys(acctUnion);titles.sort(function(x,y){var cx=acctUnion[x].code,cy2=acctUnion[y].code;return cx<cy2?-1:cx>cy2?1:0;});',
' for(var ti=0;ti<titles.length;ti++){var title=titles[ti];var sample=acctUnion[title];',
' var row=[" "+title];',
' for(var m2=0;m2<metaCols.length;m2++)row.push(sample[metaCols[m2].id]||"");',
' var amts=[];',
' for(var pi=0;pi<numP;pi++){var acct=findAcctByTitle(periods[pi].is.sections[fsGroup].accounts,title);var amt=acct?acct.amount:0;amts.push(amt);row.push(amt);if(showPct){var rev2=periods[pi].is.totalRevenue;row.push(rev2?amt/rev2:0);}}',
' if(showVar){var aCY=amts[0]||0,aPY=amts[1]||0;var vaA=rnd(aCY-aPY);var vpA=aPY!==0?(vaA/Math.abs(aPY)):(aCY!==0?1:0);row.push(vaA);row.push(vpA);}',
' ws.push(row);',
' }',
// Subtotal row
' var subRow=[IS_TOTAL_LABEL[fsGroup]];',
' for(var m3=0;m3<metaCols.length;m3++)subRow.push("");',
' for(var sp2=0;sp2<numP;sp2++){var secAmt2=periods[sp2].is.sections[fsGroup].total;var disp2=deductive?-secAmt2:secAmt2;subRow.push(disp2);if(showPct){var rev3=periods[sp2].is.totalRevenue;subRow.push(fsGroup==="Revenue"?1:(rev3?disp2/rev3:0));}}',
' if(showVar){var cy2=periods[0].is.sections[fsGroup].total,py2=periods[1].is.sections[fsGroup].total;var cyD2=deductive?-cy2:cy2,pyD2=deductive?-py2:py2;var va2=rnd(cyD2-pyD2);var vp2=pyD2!==0?(va2/Math.abs(pyD2)):(cyD2!==0?1:0);subRow.push(va2);subRow.push(vp2);}',
' ws.push(subRow);',
' }',
// Helper: push key total row (GP, NIBT, NI)
' function pushKeyTotal(label,field){',
' var row=[label];',
' for(var m4=0;m4<metaCols.length;m4++)row.push("");',
' for(var kp=0;kp<numP;kp++){var v=periods[kp].is[field];row.push(v);if(showPct){var rev4=periods[kp].is.totalRevenue;row.push(rev4?v/rev4:0);}}',
' if(showVar){var kCY=periods[0].is[field],kPY=periods[1].is[field];var vaK=rnd(kCY-kPY);var vpK=kPY!==0?(vaK/Math.abs(kPY)):(kCY!==0?1:0);row.push(vaK);row.push(vpK);}',
' ws.push(row);',
' }',
' pushSection("Revenue");',
' if(hasCOS){pushSection("Cost of Sales");pushKeyTotal("Gross Profit","grossProfit");}',
' pushSection("Other Income");',
' pushSection("Operating Expenses");',
' pushSection("Other Expenses");',
' if(hasIT){pushKeyTotal("Net Income Before Income Tax","nibt");pushSection("Income Tax");}',
' pushKeyTotal("Net Income","netIncome");',
' var wb=XLSX.utils.book_new();var sheet=XLSX.utils.aoa_to_sheet(ws);',
// Column widths
' var colW=[{wch:38}];',
' for(var cw=0;cw<metaCols.length;cw++)colW.push({wch:18});',
' for(var cp=0;cp<numP;cp++){colW.push({wch:16});if(showPct)colW.push({wch:8});}',
' if(showVar){colW.push({wch:14});colW.push({wch:10});}',
' sheet["!cols"]=colW;',
' XLSX.utils.book_append_sheet(wb,sheet,"Income Statement");',
' XLSX.writeFile(wb,((DATA.settings.businessName||"ECTAXPH").replace(/[^a-zA-Z0-9]/g,"_"))+"_IS_"+primary.from+"_"+primary.to+".xlsx");',
'}',
'',
'/* ══════════════════════════════════════',
' BALANCE SHEET',
' ══════════════════════════════════════ */',
'var BS_TYPES=["Asset","Liability","Equity"];',
'var BS_TYPE_ORDER={"Asset":1,"Liability":2,"Equity":3};',
// Proper plural labels for TOTAL rows
'var BS_TYPE_LABEL_PLURAL={"Asset":"ASSETS","Liability":"LIABILITIES","Equity":"EQUITY"};',
'var BS_TYPE_HUMAN={"Asset":"What You Own (Assets)","Liability":"What You Owe (Liabilities)","Equity":"Your Net Worth (Equity)"};',
// FSGroup display order within each Type (from SoT)
'var BS_FSGROUP_ORDER={"Cash & Cash Equivalents":1,"Current Assets":2,"Non-Current Assets":3,"Current Liabilities":1,"Long-Term Liabilities":2,"Equity":1};',
'',
'',
'/* ═══════════════════════════════════════════════════════════════',
' ═══ 1701Q v4 - Dropdown-based, theme-aligned ═══',
' ═══════════════════════════════════════════════════════════════ */',
'',
'function computeGraduatedTax2023(taxableIncome){',
' var ti=Math.max(0,taxableIncome||0);',
' if(ti<=250000)return 0;',
' if(ti<=400000)return rnd((ti-250000)*0.15);',
' if(ti<=800000)return rnd(22500+(ti-400000)*0.20);',
' if(ti<=2000000)return rnd(102500+(ti-800000)*0.25);',
' if(ti<=8000000)return rnd(402500+(ti-2000000)*0.30);',
' return rnd(2202500+(ti-8000000)*0.35);',
'}',
'',
'function q1701QuarterEnd(year,quarter){',
' var endMap={Q1:\"03-31\",Q2:\"06-30\",Q3:\"09-30\"};',
' return year+\"-\"+(endMap[quarter]||\"03-31\");',
'}',
'',
'function deriveATC(incomeType,taxRateType){',
' var g=(taxRateType===\"GRADUATED\");',
' if(incomeType===\"MIXED_INCOME\")return g?\"II013\":\"II016\";',
' if(incomeType===\"PURE_PROFESSION\")return g?\"II014\":\"II017\";',
' return g?\"II012\":\"II015\";',
'}',
'',
'function atcLabel(code){',
' var map={II012:\"Business Income - Graduated IT Rates\",II013:\"Mixed Income - Graduated IT Rates\",II014:\"Income from Profession - Graduated IT Rates\",II015:\"Business Income - 8% IT Rate\",II016:\"Mixed Income - 8% IT Rate\",II017:\"Income from Profession - 8% IT Rate\"};',
' return code+\" - \"+(map[code]||\"\");',
'}',
'',
'function q1701QuartersForView(viewMode){',
' var order=[\"Q1\",\"Q2\",\"Q3\"];',
' var ret=[];',
' order.forEach(function(q){if((viewMode||\"\").indexOf(q)>=0)ret.push(q);});',
' return ret.length>0?ret:[\"Q1\",\"Q2\",\"Q3\"];',
'}',
'',
'function toggle1701QQuarter(q,on){',
' var order=[\"Q1\",\"Q2\",\"Q3\"];',
' if(order.indexOf(q)<0)return;',
' var current={};',
' order.forEach(function(qq){if(Q1701_STATE.viewMode.indexOf(qq)>=0)current[qq]=true;});',
' if(on)current[q]=true;else delete current[q];',
' var active=order.filter(function(qq){return current[qq];});',
' if(active.length===0){current[q]=true;active=[q];}',
' Q1701_STATE.viewMode=active.join(\"\");',
' build1701Q();',
'}',
'',
'function filerTypeLabel(ft){',
' return ({SINGLE_PROPRIETOR:\"Single Proprietor\",PROFESSIONAL:\"Professional\",ESTATE:\"Estate\",TRUST:\"Trust\"})[ft]||ft;',
'}',
'',
'function incomeTypeLabel(it){',
' return ({PURE_BUSINESS:\"Pure Business\",PURE_PROFESSION:\"Pure Profession\",MIXED_INCOME:\"Mixed Income\"})[it]||it;',
'}',
'',
'function compute1701Q(jeRows,coa,settings,controls){',
' if(!controls)controls={};',
' if(!settings)settings={};',
' var taxYear=parseInt(controls.taxYear)||new Date().getFullYear();',
' var quarter=controls.quarter||\"Q1\";',
' var filerType=controls.filerType||\"SINGLE_PROPRIETOR\";',
' var incomeType=controls.taxpayerIncomeType||\"PURE_BUSINESS\";',
' var rateType=controls.taxRateType||\"GRADUATED\";',
' var dedMethod=controls.deductionMethod||\"ITEMIZED\";',
' var amended=!!controls.amendedReturn;',
' var claimingFTC=!!controls.claimingForeignTaxCredits;',
'',
' if([\"Q1\",\"Q2\",\"Q3\"].indexOf(quarter)<0)return{success:false,error:\"Invalid quarter\"};',
' var fromDate,toDate;',
' if(quarter===\"Q1\"){fromDate=taxYear+\"-01-01\";toDate=taxYear+\"-03-31\";}',
' else if(quarter===\"Q2\"){fromDate=taxYear+\"-04-01\";toDate=taxYear+\"-06-30\";}',
' else{fromDate=taxYear+\"-07-01\";toDate=taxYear+\"-09-30\";}',
'',
' if(rateType===\"EIGHT_PERCENT\")dedMethod=\"NONE\";',
' else if(dedMethod===\"NONE\")dedMethod=\"ITEMIZED\";',
'',
' var acctTaxLine={};',
' for(var ci=0;ci<(coa||[]).length;ci++){',
' var a=coa[ci];if(!a||!a.title)continue;',
' if(a.active&&String(a.active).toUpperCase()===\"NO\")continue;',
' acctTaxLine[String(a.title).trim()]={tl:String(a.taxLine||\"\").trim(),type:String(a.type||\"\").trim(),fsg:String(a.fsGroup||\"\").trim()};',
' }',
'',
' var grossSales=0,costOfSales=0,itemizedExpenses=0,nonOpAuto=0;',
' for(var j=0;j<(jeRows||[]).length;j++){',
' var je=jeRows[j];if(!je||!je.date)continue;',
' if(je.date<fromDate||je.date>toDate)continue;',
' if(je.status&&String(je.status).toLowerCase()!==\"posted\")continue;',
' var info=acctTaxLine[je.account]||{tl:\"\",type:\"\",fsg:\"\"};',
' var net=(je.debit||0)-(je.credit||0);',
' if(info.tl===\"Sales / Revenues / Receipts / Fees\")grossSales+=(-net);',
' else if(info.tl===\"Cost of Sales / Services\")costOfSales+=net;',
' else if(info.tl&&/^\\d+[a-d]? - /.test(info.tl))itemizedExpenses+=net;',
' else if(info.fsg===\"Other Income\")nonOpAuto+=(-net);',
' }',
' grossSales=rnd(grossSales);costOfSales=rnd(costOfSales);',
' itemizedExpenses=rnd(itemizedExpenses);nonOpAuto=rnd(nonOpAuto);',
'',
' var nonOpManual=parseFloat(rateType===\"EIGHT_PERCENT\"?controls.nonOperatingIncome8pct:controls.nonOperatingIncomeGraduated);',
' var useManualNonOp=!!controls.useManualNonOpIncome;',
' var nonOpInc=useManualNonOp?(isNaN(nonOpManual)?0:nonOpManual):nonOpAuto;',
' var gppShare=parseFloat(controls.gppShare)||0;',
'',
' var priorPeriodNI=parseFloat(controls.priorPeriodNI)||0;',
' var priorPeriodIncome=parseFloat(controls.priorPeriodIncome)||0;',
' var grossIncome=0,osd=0,taxableIncome=0,taxDue=0,netIncomeThisQuarter=0;',
' if(rateType===\"EIGHT_PERCENT\"){',
' var thisQIncome=grossSales+nonOpInc;',
' var allow250K=((incomeType===\"PURE_BUSINESS\"||incomeType===\"PURE_PROFESSION\")&&(filerType===\"SINGLE_PROPRIETOR\"||filerType===\"PROFESSIONAL\"));',
' var cumIncome=thisQIncome+priorPeriodIncome;',
' var reduction=allow250K?250000:0;',
' taxableIncome=rnd(Math.max(0,cumIncome-reduction));',
' taxDue=rnd(taxableIncome*0.08);',
' netIncomeThisQuarter=0;costOfSales=0;itemizedExpenses=0;osd=0;grossIncome=0;gppShare=0;',
' }else{',
' if(dedMethod===\"OSD\"){',
' osd=rnd(grossSales*0.40);',
' netIncomeThisQuarter=rnd(grossSales-osd+nonOpInc+gppShare);',
' grossIncome=grossSales;costOfSales=0;itemizedExpenses=0;',
' }else{',
' grossIncome=rnd(grossSales-costOfSales);',
' netIncomeThisQuarter=rnd(grossIncome+nonOpInc+gppShare-itemizedExpenses);osd=0;',
' }',
' taxableIncome=rnd(netIncomeThisQuarter+priorPeriodNI);',
' taxDue=rnd(computeGraduatedTax2023(Math.max(0,taxableIncome)));',
' }',
'',
' var c55=parseFloat(controls.priorYearExcessCredits)||0;',
' var c56=parseFloat(controls.taxPaymentsPreviousQuarters)||0;',
' var c57=parseFloat(controls.creditableTaxWithheldPrevQ)||0;',
' var c58=parseFloat(controls.creditableTaxWithheldThisQ)||0;',
' var c59=parseFloat(controls.taxPaidInReturnPreviouslyFiled)||0;if(!amended)c59=0;',
' var c60=parseFloat(controls.foreignTaxCredits)||0;if(!claimingFTC)c60=0;',
' var c61=parseFloat(controls.otherTaxCredits)||0;',
' if(quarter===\"Q1\"){c56=0;c57=0;}',
' var totalCredits=rnd(c55+c56+c57+c58+c59+c60+c61);',
' var taxPayableBeforePenalties=rnd(taxDue-totalCredits);',
'',
' var sur=parseFloat(controls.surcharge)||0;',
' var intr=parseFloat(controls.interest)||0;',
' var comp=parseFloat(controls.compromise)||0;',
' var totalPenalties=rnd(sur+intr+comp);',
' var finalAmountPayable=rnd(Math.max(taxPayableBeforePenalties,0)+totalPenalties);',
' var overpayment=taxPayableBeforePenalties<0?rnd(-taxPayableBeforePenalties):0;',
'',
' return{success:true,quarter:quarter,period:{taxYear:taxYear,quarter:quarter,fromDate:fromDate,toDate:toDate},',
' totals:{grossSalesReceipts:grossSales,nonOpIncomeAuto:nonOpAuto,nonOpIncomeManual:isNaN(nonOpManual)?null:nonOpManual,nonOperatingIncome:rnd(nonOpInc),useManualNonOp:useManualNonOp,gppShare:rnd(gppShare),costOfSalesOrServices:costOfSales,grossIncome:grossIncome,osd:osd,itemizedExpenses:itemizedExpenses,netIncomeThisQuarter:netIncomeThisQuarter,priorPeriodNI:priorPeriodNI,priorPeriodIncome:priorPeriodIncome,taxableIncome:taxableIncome,taxDue:taxDue,c55:rnd(c55),c56:rnd(c56),c57:rnd(c57),c58:rnd(c58),c59:rnd(c59),c60:rnd(c60),c61:rnd(c61),totalCredits:totalCredits,taxPayableBeforePenalties:taxPayableBeforePenalties,surcharge:rnd(sur),interest:rnd(intr),compromise:rnd(comp),totalPenalties:totalPenalties,finalAmountPayable:finalAmountPayable,overpayment:overpayment}};',
'}',
'',
'function compute1701QMulti(jeRows,coa,settings,baseControls,quarters){',
' var allResults={};',
' var runningNI=0;var runningIncome=0;',
' var allQ=["Q1","Q2","Q3"];',
' for(var ai=0;ai<allQ.length;ai++){',
' var q=allQ[ai];var qs=Q1701_STATE.perQuarter[q]||{};',
' var ctrls={taxYear:baseControls.taxYear,quarter:q,filerType:baseControls.filerType,taxpayerIncomeType:baseControls.taxpayerIncomeType,taxRateType:baseControls.taxRateType,deductionMethod:baseControls.deductionMethod,amendedReturn:baseControls.amendedReturn,claimingForeignTaxCredits:baseControls.claimingForeignTaxCredits,nonOperatingIncomeGraduated:qs.nonOperatingIncomeGraduated,nonOperatingIncome8pct:qs.nonOperatingIncome8pct,useManualNonOpIncome:qs.useManualNonOpIncome,gppShare:qs.gppShare||0,priorYearExcessCredits:qs.priorYearExcessCredits||0,taxPaymentsPreviousQuarters:qs.taxPaymentsPreviousQuarters||0,creditableTaxWithheldPrevQ:qs.creditableTaxWithheldPrevQ||0,creditableTaxWithheldThisQ:qs.creditableTaxWithheldThisQ||0,taxPaidInReturnPreviouslyFiled:qs.taxPaidInReturnPreviouslyFiled||0,foreignTaxCredits:qs.foreignTaxCredits||0,otherTaxCredits:qs.otherTaxCredits||0,surcharge:qs.surcharge||0,interest:qs.interest||0,compromise:qs.compromise||0,priorPeriodNI:runningNI,priorPeriodIncome:runningIncome};',
' var r=compute1701Q(jeRows,coa,settings,ctrls);',
' allResults[q]=r;',
' runningNI+=r.totals.netIncomeThisQuarter||0;',
' runningIncome+=(r.totals.grossSalesReceipts||0)+(r.totals.nonOperatingIncome||0);',
' }',
' var results={};',
' for(var ri=0;ri<quarters.length;ri++){if(allResults[quarters[ri]])results[quarters[ri]]=allResults[quarters[ri]];}',
' return results;',
'}',
'',
'function build1701Q(){build1701QControls();render1701Q();}',
'',
'function build1701QControls(){',
' var yrNow=new Date().getFullYear();var s=Q1701_STATE;',
' var is8=(s.taxRateType===\"EIGHT_PERCENT\");',
' var h=\"<div class=\\\"ctrl-bar\\\">\";',
' h+=\"<div class=\\\"ctrl-group\\\"><label>1. Tax Year</label><input type=\\\"number\\\" value=\\\"\"+s.taxYear+\"\\\" min=\\\"2018\\\" max=\\\"\"+(yrNow+1)+\"\\\" onchange=\\\"Q1701_STATE.taxYear=parseInt(this.value)||\"+yrNow+\";render1701Q()\\\" style=\\\"width:80px\\\"></div>\";',
' h+=\"<div class=\\\"ctrl-group\\\"><label>2. Quarter</label>\";',
' h+=\"<div style=\\\"display:flex;gap:12px;padding:5px 8px;align-items:center;border:1px solid #D0D0D0;border-radius:4px;\\\">\";',
' [\"Q1\",\"Q2\",\"Q3\"].forEach(function(q){',
' var checked=s.viewMode.indexOf(q)>=0;',
' h+=\"<label style=\\\"display:flex;align-items:center;gap:4px;font-size:13px;color:var(--navy);cursor:pointer;font-weight:500;\\\"><input type=\\\"checkbox\\\" \"+(checked?\"checked\":\"\")+\" onchange=\\\"toggle1701QQuarter(\'\"+q+\"\',this.checked)\\\">\"+q+\"</label>\";',
' });',
' h+=\"</div></div>\";',
' h+=\"<div class=\\\"ctrl-group\\\"><label>3. Amended?</label><select onchange=\\\"Q1701_STATE.amendedReturn=this.value===\'yes\';render1701Q()\\\">\";',
' h+=\"<option value=\\\"no\\\"\"+(!s.amendedReturn?\" selected\":\"\")+\">No</option>\";',
' h+=\"<option value=\\\"yes\\\"\"+(s.amendedReturn?\" selected\":\"\")+\">Yes</option>\";',
' h+=\"</select></div>\";',
' h+=\"<div class=\\\"ctrl-group\\\"><label>7. Filer Type</label><select onchange=\\\"Q1701_STATE.filerType=this.value;build1701Q()\\\">\";',
' [[\"SINGLE_PROPRIETOR\",\"Single Proprietor\"],[\"PROFESSIONAL\",\"Professional\"],[\"ESTATE\",\"Estate\"],[\"TRUST\",\"Trust\"]].forEach(function(o){h+=\"<option value=\\\"\"+o[0]+\"\\\"\"+(s.filerType===o[0]?\" selected\":\"\")+\">\"+o[1]+\"</option>\";});',
' h+=\"</select></div>\";',
' h+=\"<div class=\\\"ctrl-group\\\"><label>Income Type</label><select onchange=\\\"Q1701_STATE.taxpayerIncomeType=this.value;build1701Q()\\\">\";',
' [[\"PURE_BUSINESS\",\"Pure Business\"],[\"PURE_PROFESSION\",\"Pure Profession\"],[\"MIXED_INCOME\",\"Mixed Income\"]].forEach(function(o){h+=\"<option value=\\\"\"+o[0]+\"\\\"\"+(s.taxpayerIncomeType===o[0]?\" selected\":\"\")+\">\"+o[1]+\"</option>\";});',
' h+=\"</select></div>\";',
' h+=\"<div class=\\\"ctrl-group\\\"><label>15. FTC?</label><select onchange=\\\"Q1701_STATE.claimingForeignTaxCredits=this.value===\'yes\';render1701Q()\\\">\";',
' h+=\"<option value=\\\"no\\\"\"+(!s.claimingForeignTaxCredits?\" selected\":\"\")+\">No</option>\";',
' h+=\"<option value=\\\"yes\\\"\"+(s.claimingForeignTaxCredits?\" selected\":\"\")+\">Yes</option>\";',
' h+=\"</select></div>\";',
' h+=\"<div style=\\\"flex-basis:100%;height:0;\\\"></div>\";',
' h+=\"<div class=\\\"ctrl-group\\\"><label>16. Tax Rate</label><select onchange=\\\"Q1701_STATE.taxRateType=this.value;build1701Q()\\\">\";',
' h+=\"<option value=\\\"GRADUATED\\\"\"+(s.taxRateType===\"GRADUATED\"?\" selected\":\"\")+\">Graduated</option>\";',
' h+=\"<option value=\\\"EIGHT_PERCENT\\\"\"+(s.taxRateType===\"EIGHT_PERCENT\"?\" selected\":\"\")+\">8% Optional</option>\";',
' h+=\"</select></div>\";',
' h+=\"<div class=\\\"ctrl-group\\\"><label>16A. Deduction</label><select\"+(is8?\" disabled\":\"\")+\" onchange=\\\"Q1701_STATE.deductionMethod=this.value;render1701Q()\\\" style=\\\"\"+(is8?\"background:#F3F4F6;color:#9CA3AF;cursor:not-allowed;\":\"\")+\"\\\">\";',
' if(is8){h+=\"<option selected>N/A (8% mode)</option>\";}',
' else{',
' h+=\"<option value=\\\"ITEMIZED\\\"\"+(s.deductionMethod===\"ITEMIZED\"?\" selected\":\"\")+\">Itemized</option>\";',
' h+=\"<option value=\\\"OSD\\\"\"+(s.deductionMethod===\"OSD\"?\" selected\":\"\")+\">OSD (40%)</option>\";',
' }',
' h+=\"</select></div>\";',
' h+=\"</div>\";',
' document.getElementById(\"controls\").innerHTML=h;',
'}',
'',
'function fmt1701(n){if(n===null||n===undefined||n===\"\")return \"\";if(typeof n!==\"number\")n=parseFloat(n)||0;n=Math.round(n);return n.toLocaleString(\"en-PH\",{minimumFractionDigits:2,maximumFractionDigits:2});}',
'',
'function render1701Q(){',
' var s=Q1701_STATE;',
' var quarters=q1701QuartersForView(s.viewMode);',
' var baseControls={taxYear:s.taxYear,filerType:s.filerType,taxpayerIncomeType:s.taxpayerIncomeType,taxRateType:s.taxRateType,deductionMethod:s.deductionMethod,amendedReturn:s.amendedReturn,claimingForeignTaxCredits:s.claimingForeignTaxCredits};',
' var results=compute1701QMulti(DATA.jeRows,DATA.coa,DATA.settings,baseControls,quarters);',
' s.lastResults=results;',
' var is8=(s.taxRateType===\"EIGHT_PERCENT\");',
' var atcCode=deriveATC(s.taxpayerIncomeType,s.taxRateType);',
' var settings=DATA.settings||{};',
'',
' function pickCol(fn){var x=\"\";for(var i=0;i<quarters.length;i++)x+=\"<td class=\\\"num\\\">\"+fn(quarters[i])+\"</td>\";return x;}',
' function drillCol(lineType,fn){var x=\"\";for(var i=0;i<quarters.length;i++){var q=quarters[i];var v=fn(q);x+=\"<td class=\\\"num gl-amt-clickable\\\" onclick=\\\"drill1701Q(\'\"+q+\"\',\'\"+lineType+\"\')\\\" title=\\\"Open General Ledger for \"+q+\"\\\">\"+v+\"</td>\";}return x;}',
' function inputCol(field,gated){',
' var x=\"\";',
' for(var i=0;i<quarters.length;i++){',
' var q=quarters[i];var v=(s.perQuarter[q]||{})[field]||0;',
' var isGated=(typeof gated===\"function\")?!!gated(q):!!gated;',
' var dis=isGated?\" disabled\":\"\";',
' var dispVal=isGated?\"\":(v?fmt1701(v):\"\");',
' x+=\"<td class=\\\"num\\\"><input type=\\\"text\\\" value=\\\"\"+dispVal+\"\\\" placeholder=\\\"0.00\\\"\"+dis+\" onkeydown=\\\"if(event.key==\'.\')event.preventDefault();\\\" onchange=\\\"Q1701_STATE.perQuarter[\'\"+q+\"\'][\'\"+field+\"\']=Math.round(parseFloat(this.value.replace(/,/g,\'\'))||0);render1701Q()\\\" class=\\\"q1701-inp\\\"></td>\";',
' }',
' return x;',
' }',
' function nonOpInputCol(){',
' var x=\"\";var field=is8?\"nonOperatingIncome8pct\":\"nonOperatingIncomeGraduated\";',
' for(var i=0;i<quarters.length;i++){',
' var q=quarters[i];var qs=s.perQuarter[q]||{};',
' var useManual=!!qs.useManualNonOpIncome;',
' var auto=results[q].totals.nonOpIncomeAuto;',
' var manual=qs[field];',
' var lbl=useManual?\"manual\":\"auto\";',
' x+=\"<td class=\\\"num\\\"><div class=\\\"q1701-cell-wrap\\\">\";',
' x+=\"<input type=\\\"text\\\" value=\\\"\"+(useManual?(manual?fmt1701(manual):\"\"):\"\")+\"\\\" placeholder=\\\"\"+fmt1701(auto)+\"\\\"\"+(useManual?\"\":\" disabled\")+\" onkeydown=\\\"if(event.key==\'.\')event.preventDefault();\\\" onchange=\\\"Q1701_STATE.perQuarter[\'\"+q+\"\'][\'\"+field+\"\']=Math.round(parseFloat(this.value.replace(/,/g,\'\'))||0);render1701Q()\\\" class=\\\"q1701-inp\\\">\";',
' x+=\"<button type=\\\"button\\\" class=\\\"q1701-tog\"+(useManual?\" on\":\"\")+\"\\\" onclick=\\\"Q1701_STATE.perQuarter[\'\"+q+\"\'].useManualNonOpIncome=!Q1701_STATE.perQuarter[\'\"+q+\"\'].useManualNonOpIncome;render1701Q()\\\">\"+lbl+\"</button>\";',
' x+=\"</div></td>\";',
' }',
' return x;',
' }',
' function item42Col(){',
' var x=\"\";',
' for(var i=0;i<quarters.length;i++){',
' var q=quarters[i];var qIdx=parseInt(q.substring(1));var val=0;',
' if(qIdx>1){var prevQ=\"Q\"+(qIdx-1);if(results[prevQ])val=results[prevQ].totals.taxableIncome;else{var pr=compute1701Q(DATA.jeRows,DATA.coa,DATA.settings,Object.assign({},baseControls,{quarter:prevQ}));val=pr.totals.taxableIncome;}}',
' x+=\"<td class=\\\"num q1701-auto\\\">\"+fmt1701(val)+\"</td>\";',
' }',
' return x;',
' }',
' function hdr(){var x=\"<tr><th class=\\\"item\\\">#</th><th class=\\\"desc\\\">Particulars</th>\";for(var i=0;i<quarters.length;i++){x+=\"<th class=\\\"num\\\">\"+quarters[i]+\"</th>\";}x+=\"</tr>\";return x;}',
'',
' var h=\"<div class=\\\"q1701-report\\\">\";',
' // Header',
' h+=\"<div class=\\\"q1701-section q1701-titleblock\\\">\";',
' h+=\"<div class=\\\"q1701-bn\\\">\"+esc(settings.businessName||\"[Business Name]\")+\"</div>\";',
' h+=\"<div class=\\\"q1701-ttl\\\">BIR Form 1701Q - Quarterly Income Tax Return</div>\";',
' h+=\"<div class=\\\"q1701-meta\\\">Tax Year \"+s.taxYear+\" · \"+quarters.join(\", \")+(s.amendedReturn?\" · AMENDED\":\"\")+\" · ATC \"+atcCode+\"</div>\";',
' h+=\"</div>\";',
'',
' // Background info (auto from settings + controls)',
' h+=\"<div class=\\\"q1701-section\\\"><div class=\\\"q1701-sh\\\">Part I - Background Information</div>\";',
' h+=\"<table class=\\\"q1701-bg\\\">\";',
' // 1701Q Filer Name: Individual = Last, First Middle; Non-Individual = Registered Name',
' var fName;',
' if(settings.taxpayerClass===\"Non-Individual\"){',
' fName=settings.registeredNameNonInd||settings.businessName||\"\";',
' }else{',
' var fnLast=settings.ownerLast||\"\",fnFirst=settings.ownerFirst||\"\",fnMid=settings.ownerMiddle||\"\";',
' var fnFM=[fnFirst,fnMid].filter(function(x){return x;}).join(\" \");',
' fName=fnLast?(fnFM?fnLast+\", \"+fnFM:fnLast):(settings.businessName||\"\");',
' }',
' h+=\"<tr><td class=\\\"lbl\\\">5. TIN</td><td>\"+esc(settings.tin||\"\")+\"</td><td class=\\\"lbl\\\">6. RDO Code</td><td>\"+esc(settings.rdo||\"\")+\"</td></tr>\";',
' h+=\"<tr><td class=\\\"lbl\\\">7. Filer Type</td><td>\"+filerTypeLabel(s.filerType)+\"</td><td class=\\\"lbl\\\">8. ATC</td><td>\"+esc(atcLabel(atcCode))+\"</td></tr>\";',
' h+=\"<tr><td class=\\\"lbl\\\">9. Filer\'s Name</td><td colspan=\\\"3\\\">\"+esc(fName)+\"</td></tr>\";',
' h+=\"<tr><td class=\\\"lbl\\\">10. Registered Address</td><td colspan=\\\"3\\\">\"+esc(settings.address||\"\")+\"</td></tr>\";',
' h+=\"<tr><td class=\\\"lbl\\\">10A. ZIP Code</td><td>\"+esc(settings.zipCode||\"\")+\"</td><td class=\\\"lbl\\\">11. Date of Birth</td><td>\"+esc(settings.dateOfBirth||\"\")+\"</td></tr>\";',
' h+=\"<tr><td class=\\\"lbl\\\">12. Email</td><td>\"+esc(settings.email||\"\")+\"</td><td class=\\\"lbl\\\">13. Citizenship</td><td>\"+esc(settings.citizenship||\"\")+\"</td></tr>\";',
' h+=\"<tr><td class=\\\"lbl\\\">16. Tax Rate</td><td>\"+(is8?\"8% Optional\":\"Graduated\")+\"</td><td class=\\\"lbl\\\">16A. Deduction</td><td>\"+(is8?\"N/A (8%)\":s.deductionMethod)+\"</td></tr>\";',
' h+=\"</table></div>\";',
'',
' // Schedule I or II',
' if(!is8){',
' h+=\"<div class=\\\"q1701-section\\\"><div class=\\\"q1701-sh\\\">Part V - Schedule I (Graduated IT Rate)</div>\";',
' h+=\"<table class=\\\"q1701-tbl\\\"><thead>\"+hdr()+\"</thead><tbody>\";',
' h+=\"<tr><td class=\\\"item\\\">36</td><td>Sales/Revenues/Receipts/Fees</td>\"+drillCol(\"sales\",function(q){return fmt1701(results[q].totals.grossSalesReceipts);})+\"</tr>\";',
' if(s.deductionMethod===\"ITEMIZED\"){',
' h+=\"<tr><td class=\\\"item\\\">37</td><td>Less: Cost of Sales/Services</td>\"+drillCol(\"cogs\",function(q){return \"(\"+fmt1701(results[q].totals.costOfSalesOrServices)+\")\";})+\"</tr>\";',
' h+=\"<tr class=\\\"sub\\\"><td class=\\\"item\\\">38</td><td>Gross Income from Operation</td>\"+pickCol(function(q){return fmt1701(results[q].totals.grossIncome);})+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">39</td><td>Less: Total Allowable Itemized Deductions</td>\"+drillCol(\"itemized\",function(q){return \"(\"+fmt1701(results[q].totals.itemizedExpenses)+\")\";})+\"</tr>\";',
' }else{',
' h+=\"<tr><td class=\\\"item\\\">40</td><td>Less: OSD (40% of Item 36)</td>\"+pickCol(function(q){return \"(\"+fmt1701(results[q].totals.osd)+\")\";})+\"</tr>\";',
' }',
' h+=\"<tr><td class=\\\"item\\\">41</td><td>Net Income/(Loss) This Quarter</td>\"+pickCol(function(q){return fmt1701(results[q].totals.netIncomeThisQuarter);})+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">42</td><td>Add: Taxable Income Previous Quarter/s <span class=\\\"q1701-hint\\\">(auto)</span></td>\"+pickCol(function(q){return fmt1701(results[q].totals.priorPeriodNI);})+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">43</td><td>Add: Non-Operating Income <span class=\\\"q1701-hint\\\">(auto = Other Income; click toggle to override)</span></td>\"+nonOpInputCol()+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">44</td><td>Add: GPP Partner Share</td>\"+inputCol(\"gppShare\")+\"</tr>\";',
' h+=\"<tr class=\\\"sub\\\"><td class=\\\"item\\\">45</td><td>Total Taxable Income To Date</td>\"+pickCol(function(q){return fmt1701(results[q].totals.taxableIncome);})+\"</tr>\";',
' h+=\"<tr class=\\\"hl\\\"><td class=\\\"item\\\">46</td><td>TAX DUE (Graduated)</td>\"+pickCol(function(q){return fmt1701(results[q].totals.taxDue);})+\"</tr>\";',
' h+=\"</tbody></table></div>\";',
' }else{',
' h+=\"<div class=\\\"q1701-section\\\"><div class=\\\"q1701-sh\\\">Part V - Schedule II (8% IT Rate)</div>\";',
' h+=\"<table class=\\\"q1701-tbl\\\"><thead>\"+hdr()+\"</thead><tbody>\";',
' h+=\"<tr><td class=\\\"item\\\">47</td><td>Sales/Revenues/Receipts/Fees</td>\"+drillCol(\"sales\",function(q){return fmt1701(results[q].totals.grossSalesReceipts);})+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">48</td><td>Add: Non-Operating Income <span class=\\\"q1701-hint\\\">(auto/manual)</span></td>\"+nonOpInputCol()+\"</tr>\";',
' h+=\"<tr class=\\\"sub\\\"><td class=\\\"item\\\">49</td><td>Total Income for the Quarter</td>\"+pickCol(function(q){var t=results[q].totals;return fmt1701(t.grossSalesReceipts+t.nonOperatingIncome);})+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">50</td><td>Add: Cumulative Income Previous Quarter <span class=\\\"q1701-hint\\\">(auto)</span></td>\"+pickCol(function(q){return fmt1701(results[q].totals.priorPeriodIncome);})+\"</tr>\";',
' h+=\"<tr class=\\\"sub\\\"><td class=\\\"item\\\">51</td><td>Cumulative Taxable Income</td>\"+pickCol(function(q){var t=results[q].totals;return fmt1701(t.priorPeriodIncome+t.grossSalesReceipts+t.nonOperatingIncome);})+\"</tr>\";',
' var allow250K=((s.taxpayerIncomeType===\"PURE_BUSINESS\"||s.taxpayerIncomeType===\"PURE_PROFESSION\")&&(s.filerType===\"SINGLE_PROPRIETOR\"||s.filerType===\"PROFESSIONAL\"));',
' h+=\"<tr><td class=\\\"item\\\">52</td><td>Less: Allowable Reduction (250,000)\"+(allow250K?\"\":\" <span class=\\\"q1701-hint\\\">(N/A)</span>\")+\"</td>\"+pickCol(function(q){return \"(\"+(allow250K?\"250,000.00\":\"0.00\")+\")\";})+\"</tr>\";',
' h+=\"<tr class=\\\"sub\\\"><td class=\\\"item\\\">53</td><td>Taxable Income To Date</td>\"+pickCol(function(q){return fmt1701(results[q].totals.taxableIncome);})+\"</tr>\";',
' h+=\"<tr class=\\\"hl\\\"><td class=\\\"item\\\">54</td><td>TAX DUE (8%)</td>\"+pickCol(function(q){return fmt1701(results[q].totals.taxDue);})+\"</tr>\";',
' h+=\"</tbody></table></div>\";',
' }',
'',
' // Schedule III',
' h+=\"<div class=\\\"q1701-section\\\"><div class=\\\"q1701-sh\\\">Schedule III - Tax Credits/Payments</div>\";',
' h+=\"<table class=\\\"q1701-tbl\\\"><thead>\"+hdr()+\"</thead><tbody>\";',
' h+=\"<tr><td class=\\\"item\\\">55</td><td>Prior Year\'s Excess Credits</td>\"+inputCol(\"priorYearExcessCredits\")+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">56</td><td>Tax Payment/s for Previous Quarter/s <span class=\\\"q1701-hint\\\">(N/A in Q1)</span></td>\"+inputCol(\"taxPaymentsPreviousQuarters\",function(q){return q===\"Q1\";})+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">57</td><td>Creditable Tax Withheld Previous Quarter/s <span class=\\\"q1701-hint\\\">(N/A in Q1)</span></td>\"+inputCol(\"creditableTaxWithheldPrevQ\",function(q){return q===\"Q1\";})+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">58</td><td>Creditable Tax Withheld per 2307 This Quarter</td>\"+inputCol(\"creditableTaxWithheldThisQ\")+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">59</td><td>Tax Paid in Previously Filed Return\"+(s.amendedReturn?\"\":\" <span class=\\\"q1701-hint\\\">(amended only)</span>\")+\"</td>\"+inputCol(\"taxPaidInReturnPreviouslyFiled\",!s.amendedReturn)+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">60</td><td>Foreign Tax Credits\"+(s.claimingForeignTaxCredits?\"\":\" <span class=\\\"q1701-hint\\\">(N/A)</span>\")+\"</td>\"+inputCol(\"foreignTaxCredits\",!s.claimingForeignTaxCredits)+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">61</td><td>Other Tax Credits/Payments</td>\"+inputCol(\"otherTaxCredits\")+\"</tr>\";',
' h+=\"<tr class=\\\"sub\\\"><td class=\\\"item\\\">62</td><td>Total Tax Credits/Payments</td>\"+pickCol(function(q){return fmt1701(results[q].totals.totalCredits);})+\"</tr>\";',
' h+=\"<tr class=\\\"sub\\\"><td class=\\\"item\\\">63</td><td>Tax Payable/(Overpayment)</td>\"+pickCol(function(q){return fmt1701(results[q].totals.taxPayableBeforePenalties);})+\"</tr>\";',
' h+=\"</tbody></table></div>\";',
'',
' // Schedule IV',
' h+=\"<div class=\\\"q1701-section\\\"><div class=\\\"q1701-sh\\\">Schedule IV - Penalties</div>\";',
' h+=\"<table class=\\\"q1701-tbl\\\"><thead>\"+hdr()+\"</thead><tbody>\";',
' h+=\"<tr><td class=\\\"item\\\">64</td><td>Surcharge</td>\"+inputCol(\"surcharge\")+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">65</td><td>Interest</td>\"+inputCol(\"interest\")+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">66</td><td>Compromise</td>\"+inputCol(\"compromise\")+\"</tr>\";',
' h+=\"<tr class=\\\"sub\\\"><td class=\\\"item\\\">67</td><td>Total Penalties</td>\"+pickCol(function(q){return fmt1701(results[q].totals.totalPenalties);})+\"</tr>\";',
' h+=\"<tr class=\\\"tot\\\"><td class=\\\"item\\\">68</td><td>TOTAL AMOUNT PAYABLE/(Overpayment)</td>\"+pickCol(function(q){return fmt1701(results[q].totals.finalAmountPayable);})+\"</tr>\";',
' h+=\"</tbody></table></div>\";',
'',
' // Part III summary',
' h+=\"<div class=\\\"q1701-section\\\"><div class=\\\"q1701-sh\\\">Part III - Summary</div>\";',
' h+=\"<table class=\\\"q1701-tbl\\\"><thead>\"+hdr()+\"</thead><tbody>\";',
' h+=\"<tr><td class=\\\"item\\\">26</td><td>Tax Due</td>\"+pickCol(function(q){return fmt1701(results[q].totals.taxDue);})+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">27</td><td>Less: Tax Credits/Payments</td>\"+pickCol(function(q){return \"(\"+fmt1701(results[q].totals.totalCredits)+\")\";})+\"</tr>\";',
' h+=\"<tr class=\\\"sub\\\"><td class=\\\"item\\\">28</td><td>Tax Payable/(Overpayment)</td>\"+pickCol(function(q){return fmt1701(results[q].totals.taxPayableBeforePenalties);})+\"</tr>\";',
' h+=\"<tr><td class=\\\"item\\\">29</td><td>Add: Total Penalties</td>\"+pickCol(function(q){return fmt1701(results[q].totals.totalPenalties);})+\"</tr>\";',
' h+=\"<tr class=\\\"tot\\\"><td class=\\\"item\\\">30</td><td>Total Amount Payable/(Overpayment)</td>\"+pickCol(function(q){return fmt1701(results[q].totals.finalAmountPayable);})+\"</tr>\";',
' h+=\"</tbody></table></div>\";',
'',
' h+=\"</div>\";',
' document.getElementById(\"report\").innerHTML=h;',
' ensureQ1701Style_();',
'}',
'',
'function drill1701Q(quarter,lineType){',
' var year=Q1701_STATE.taxYear;',
' var endMap={Q1:"03-31",Q2:"06-30",Q3:"09-30"};',
' var fromDate=year+"-01-01";',
' var toDate=year+"-"+(endMap[quarter]||"03-31");',
' if(typeof GL_STATE!=="undefined"){',
' GL_STATE.selectedAccounts={};',
' if(lineType&&typeof DATA!=="undefined"&&DATA.coa){',
' for(var i=0;i<DATA.coa.length;i++){',
' var a=DATA.coa[i];',
' if(!a||!a.title)continue;',
' if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var tl=String(a.taxLine||"").trim();',
' var match=false;',
' if(lineType==="sales")match=(tl==="Sales / Revenues / Receipts / Fees");',
' else if(lineType==="cogs")match=(tl==="Cost of Sales / Services");',
' else if(lineType==="itemized")match=!!(tl&&/^\\d+[a-d]? - /.test(tl));',
' if(match)GL_STATE.selectedAccounts[String(a.title).trim()]=true;',
' }',
' }',
' GL_STATE.page=1;',
' GL_STATE.groupMode="account";',
' GL_STATE._pendingFrom=fromDate;',
' GL_STATE._pendingTo=toDate;',
' GL_STATE._pendingPreset="custom";',
' }',
' if(typeof switchTab==="function")switchTab("gl");',
'}',
'',
'function buildBS(){buildBSControls();recomputeBS();}',
'',
'function toggleBSVariance(){BS_STATE.showVariance=!BS_STATE.showVariance;var b=document.getElementById("bsVarBtn");if(b){b.innerText=BS_STATE.showVariance?"Hide Difference":"Show Difference";if(BS_STATE.showVariance)b.classList.add("on");else b.classList.remove("on");}recomputeBS();}',
'',
'function buildBSControls(){',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(function(d){return d&&d.length>=10}).sort();',
' var maxD=dates[dates.length-1]||"2026-12-31";',
' var h="";',
// Single mode: control bar wrapped in rounded card, proper case labels, no emojis, no bold
' h+="<div class=\\"bs-adv-controls\\">";',
// Row 1: Date + Compare + (dynamic fields) + Variance + Columns + More
' h+="<div class=\\"ctrl-bar\\" style=\\"padding:8px 12px;\\">";',
' h+="<div class=\\"ctrl-group\\"><label>As of Date</label><select id=\\"bsPreset\\" onchange=\\"applyBSPreset()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' h+="<option value=\\"latest\\" selected>Latest Transaction</option>";',
' h+="<option value=\\"today\\">Today</option>";',
' h+="<option value=\\"eolm\\">End of Last Month</option>";',
' h+="<option value=\\"eolq\\">End of Last Quarter</option>";',
' h+="<option value=\\"tfye\\">This Financial Year End</option>";',
' h+="<option value=\\"lfye\\">Last Financial Year End</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"custom\\">Custom</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Date</label><input type=\\"date\\" id=\\"bsDate\\" value=\\""+maxD+"\\" onchange=\\"document.getElementById(\'bsPreset\').value=\'custom\';recomputeBS()\\"></div>";',
' h+="<div class=\\"is-ctrl-divider\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Compare</label><select id=\\"bsCmp\\" onchange=\\"onBSCmpChange()\\">";',
' h+="<option value=\\"pye\\">Previous Year End</option>";',
' h+="<option value=\\"pqe\\">Previous Quarter End</option>";',
' h+="<option value=\\"pme\\">Previous Month End</option>";',
' h+="<option value=\\"pfye\\">Previous FY End</option>";',
' h+="<option value=\\"multi\\">Multi-Period</option>";',
' h+="<option value=\\"custom\\">Custom</option>";',
' h+="<option value=\\"none\\" selected>None</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"bsMultiIntGrp\\" style=\\"display:none\\"><label>Interval</label><select id=\\"bsMultiInt\\" onchange=\\"BS_STATE.multiInterval=this.value;recomputeBS()\\">";',
' h+="<option value=\\"monthly\\">Monthly</option><option value=\\"quarterly\\">Quarterly</option><option value=\\"annually\\">Annually</option></select></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"bsMultiNumGrp\\" style=\\"display:none\\"><label>Periods</label><select id=\\"bsMultiNum\\" onchange=\\"BS_STATE.multiCount=parseInt(this.value);recomputeBS()\\">";',
' for(var pp=2;pp<=12;pp++)h+="<option value=\\""+pp+"\\""+(pp===4?" selected":"")+">"+pp+"</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"bsCustomCmpGrp\\" style=\\"display:none\\"><label>Custom As of</label><input type=\\"date\\" id=\\"bsCustomCmpDate\\" value=\\""+maxD+"\\" onchange=\\"BS_STATE.customCompareDate=this.value;recomputeBS()\\"></div>";',
// Variance toggle button, hidden when Compare=None/Multi
' h+="<div class=\\"ctrl-group\\" id=\\"bsVarGrp\\" style=\\"display:none\\"><label> </label>";',
' h+="<button id=\\"bsVarBtn\\" class=\\"ctrl-btn"+(BS_STATE.showVariance?" on":"")+"\\" onclick=\\"toggleBSVariance()\\">"+(BS_STATE.showVariance?"Hide Difference":"Show Difference")+"</button>";',
' h+="</div>";',
' h+="<div class=\\"is-ctrl-divider\\"></div>";',
' h+="<div class=\\"ctrl-group\\" style=\\"position:relative\\"><label>Columns</label>"+renderBSColsBtn()+"<div id=\\"bsColsWrap\\"></div></div>";',
' h+="<div class=\\"ctrl-group\\" style=\\"position:relative\\"><label> </label>"+renderBSMoreBtn()+"<div id=\\"bsMoreWrap\\"></div></div>";',
' h+="</div>";',
// Row 2: Expand/Collapse + Hide Decimals
' h+="<div class=\\"ctrl-bar\\" style=\\"padding:6px 12px;\\">";',
' h+="<button id=\\"bsExpandBtn\\" class=\\"ctrl-btn\\" onclick=\\"toggleBSAll()\\">"+bsExpandLabel()+"</button>";',
' h+="<div style=\\"flex:1\\"></div>";',
' h+=renderHideDecimalsToggle();',
' h+="</div>";',
' h+="</div>";',
' document.getElementById("controls").innerHTML=h;',
' applyBSPreset();',
' onBSCmpChange();',
'}',
'',
'function bsColsCount(){var n=0;if(BS_STATE.showTrend)n++;if(BS_STATE.showNoteGroup)n++;return n;}',
'function renderBSColsBtn(){',
' var n=bsColsCount();',
' if(n===0)return "<button id=\\"bsColsBtn\\" class=\\"ctrl-dropdown-btn\\" onclick=\\"toggleBSCols()\\">Columns \\u25BE</button>";',
' return "<button id=\\"bsColsBtn\\" class=\\"more-btn-active\\" onclick=\\"toggleBSCols()\\"><span>Columns ("+n+") \\u25BE</span><span class=\\"more-btn-active-x\\" onclick=\\"event.stopPropagation();resetAllBSCols()\\">\\u2715</span></button>";',
'}',
'function updateBSColsBtn(){var b=document.getElementById("bsColsBtn");if(b&&b.parentNode)b.outerHTML=renderBSColsBtn();}',
'function resetAllBSCols(){BS_STATE.showTrend=false;BS_STATE.showNoteGroup=false;var w=document.getElementById("bsColsWrap");if(w)w.innerHTML="";updateBSColsBtn();quietRecompute("bs");}',
'',
'function bsMoreCount(){var n=0;if(BS_STATE.showPctTotalAssets)n++;return n;}',
'function renderBSMoreBtn(){',
' var n=bsMoreCount();',
' if(n===0)return "<button id=\\"bsMoreBtn\\" class=\\"ctrl-btn\\" onclick=\\"toggleBSMore()\\">More \\u25BE</button>";',
' return "<button id=\\"bsMoreBtn\\" class=\\"more-btn-active\\" onclick=\\"toggleBSMore()\\"><span>More ("+n+") \\u25BE</span><span class=\\"more-btn-active-x\\" onclick=\\"event.stopPropagation();resetAllBSMore()\\">\\u2715</span></button>";',
'}',
'function updateBSMoreBtn(){var b=document.getElementById("bsMoreBtn");if(b&&b.parentNode)b.outerHTML=renderBSMoreBtn();}',
'function resetAllBSMore(){BS_STATE.showPctTotalAssets=false;var w=document.getElementById("bsMoreWrap");if(w)w.innerHTML="";updateBSMoreBtn();quietRecompute("bs");}',
'',
'function bsExpandLabel(){',
' var anyCollapsed=Object.keys(BS_STATE.collapsedTypes).some(function(k){return BS_STATE.collapsedTypes[k]===true;})||Object.keys(BS_STATE.collapsedFSGroups).some(function(k){return BS_STATE.collapsedFSGroups[k]===true;});',
' return anyCollapsed?"Expand All":"Collapse All";',
'}',
'function updateBSExpandBtn(){var b=document.getElementById("bsExpandBtn");if(b)b.innerText=bsExpandLabel();}',
'',
'function toggleBSAll(){',
// If anything is collapsed, expand all. Else collapse all Types and FSGroups.
' var anyCollapsed=Object.keys(BS_STATE.collapsedTypes).some(function(k){return BS_STATE.collapsedTypes[k]===true;})||Object.keys(BS_STATE.collapsedFSGroups).some(function(k){return BS_STATE.collapsedFSGroups[k]===true;});',
' if(anyCollapsed){BS_STATE.collapsedTypes={};BS_STATE.collapsedFSGroups={};}',
' else{',
// Collapse all Types AND FSGroups
' BS_STATE.collapsedTypes={};BS_TYPES.forEach(function(t){BS_STATE.collapsedTypes[t]=true;});',
' BS_STATE.collapsedFSGroups={};',
' for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;var tp=String(a.type).trim();if(tp!=="Asset"&&tp!=="Liability")continue;var fsg=String(a.fsGroup||"").trim();if(!fsg)fsg=tp;BS_STATE.collapsedFSGroups[tp+"|"+fsg]=true;}',
' }',
' recomputeBS();',
'}',
'',
'function toggleBSType(typeName){',
' if(BS_STATE.collapsedTypes[typeName]===true)delete BS_STATE.collapsedTypes[typeName];',
' else BS_STATE.collapsedTypes[typeName]=true;',
' recomputeBS();',
'}',
'',
'function toggleBSFSGroup(typeName,fsGroup){',
' var key=typeName+"|"+fsGroup;',
' if(BS_STATE.collapsedFSGroups[key]===true)delete BS_STATE.collapsedFSGroups[key];',
' else BS_STATE.collapsedFSGroups[key]=true;',
' recomputeBS();',
'}',
'',
// Legacy alias (in case called anywhere); forwards to FSGroup toggle
'function toggleBSSection(){recomputeBS();}',
'',
'function applyBSPreset(){',
' var v=document.getElementById("bsPreset").value;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' var maxD=dates[dates.length-1]||"2026-12-31";',
' var today=new Date();var todayStr=today.getFullYear()+"-"+pad(today.getMonth()+1)+"-"+pad(today.getDate());',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var asOf="";',
' if(v==="custom"){asOf=document.getElementById("bsDate").value;}',
' else if(v==="latest"){asOf=maxD;}',
' else if(v==="today"){asOf=todayStr;}',
' else if(v==="eolm"){',
' var td=new Date(todayStr);td.setDate(1);td.setDate(0);',
' asOf=td.getFullYear()+"-"+pad(td.getMonth()+1)+"-"+pad(td.getDate());',
' }',
' else if(v==="eolq"){',
' var td2=new Date(todayStr);var curQ=Math.floor(td2.getMonth()/3);var lastMoOfPrevQ=curQ*3;',
' var yr=td2.getFullYear();if(lastMoOfPrevQ===0){yr=yr-1;lastMoOfPrevQ=12;}',
' asOf=yr+"-"+pad(lastMoOfPrevQ)+"-"+ld(lastMoOfPrevQ,yr);',
' }',
' else if(v==="tfye"){',
// This Financial Year end - based on today's date, not latest transaction
' var curYr=parseInt(todayStr.substring(0,4));var curMo=parseInt(todayStr.substring(5,7));',
' if(fyM===1){asOf=curYr+"-12-31";}',
' else{',
// If today is in the FY that started this calendar year, end = fyM-1 of next year. Else end = fyM-1 of current year.
' var endYr=(curMo>=fyM)?(curYr+1):curYr;',
' asOf=endYr+"-"+pad(fyM-1)+"-"+ld(fyM-1,endYr);',
' }',
' }',
' else if(v==="lfye"){',
// Last Financial Year end - based on today's date
' var curYr2=parseInt(todayStr.substring(0,4));var curMo2=parseInt(todayStr.substring(5,7));',
' if(fyM===1){asOf=(curYr2-1)+"-12-31";}',
' else{',
' var endYr2=(curMo2>=fyM)?curYr2:(curYr2-1);',
' asOf=endYr2+"-"+pad(fyM-1)+"-"+ld(fyM-1,endYr2);',
' }',
' }',
' BS_STATE.asOfDate=asOf;BS_STATE.preset=v;',
' document.getElementById("bsDate").value=asOf;',
' recomputeBS();',
'}',
'',
'function onBSCmpChange(){',
' BS_STATE.compareMode=document.getElementById("bsCmp").value;',
' var cg=document.getElementById("bsCustomCmpGrp");if(cg)cg.style.display=BS_STATE.compareMode==="custom"?"flex":"none";',
' var mi=document.getElementById("bsMultiIntGrp");if(mi)mi.style.display=BS_STATE.compareMode==="multi"?"flex":"none";',
' var mn=document.getElementById("bsMultiNumGrp");if(mn)mn.style.display=BS_STATE.compareMode==="multi"?"flex":"none";',
' var vg=document.getElementById("bsVarGrp");if(vg)vg.style.display=(BS_STATE.compareMode==="multi"||BS_STATE.compareMode==="none")?"none":"flex";',
' recomputeBS();',
'}',
'',
'/* ── BS Columns Panel ── */',
'function toggleBSCols(){',
' var wrap=document.getElementById("bsColsWrap");',
' if(wrap.innerHTML){wrap.innerHTML="";return;}',
' var h="<div class=\\"panel\\" style=\\"width:220px\\">";',
' h+="<div class=\\"panel-hdr\\"><span class=\\"panel-title\\">Columns</span><button class=\\"panel-close\\" onclick=\\"toggleBSCols()\\">\\u2715</button></div>";',
' h+="<label class=\\"col-check\\"><input type=\\"checkbox\\" "+(BS_STATE.showTrend?"checked":"")+" onchange=\\"BS_STATE.showTrend=this.checked;updateBSColsBtn();quietRecompute(\'bs\')\\"> Trend</label>";',
' h+="<label class=\\"col-check\\"><input type=\\"checkbox\\" "+(BS_STATE.showNoteGroup?"checked":"")+" onchange=\\"BS_STATE.showNoteGroup=this.checked;updateBSColsBtn();quietRecompute(\'bs\')\\"> Note Group</label>";',
' h+="</div>";',
' wrap.innerHTML=h;',
'}',
'',
'/* ── BS More Panel ── */',
'function toggleBSMore(){',
' var wrap=document.getElementById("bsMoreWrap");',
' if(wrap.innerHTML){wrap.innerHTML="";return;}',
' var h="<div class=\\"panel\\" style=\\"width:260px\\">";',
' h+="<div class=\\"panel-hdr\\"><span class=\\"panel-title\\">More Options</span><button class=\\"panel-close\\" onclick=\\"toggleBSMore()\\">\\u2715</button></div>";',
' h+="<label class=\\"col-check\\"><input type=\\"checkbox\\" "+(BS_STATE.showPctTotalAssets?"checked":"")+" onchange=\\"BS_STATE.showPctTotalAssets=this.checked;updateBSMoreBtn();recomputeBS()\\"> Show % of Total Assets</label>";',
' h+="</div>";',
' wrap.innerHTML=h;',
'}',
'',
'/* ── BS Computation ══',
' Given an as-of date, compute balances for all Asset/Liability/Equity accounts',
' as running sum of JEs through that date.',
' Return structure: {types:{Asset:{sections:{FSGroup1:{notegroups:{NG1:{accounts:[],total,noteNum}},total}},total},...}}',
' Equity total includes YTD Net Income from FY start of as-of date through as-of date. ═══ */',
'function computeBS(jeRows,coa,asOfDate,fyM){',
// Build account map, filter to Asset/Liability/Equity only
' var acctMap={},bsCapAcct="",bsReAcct="";',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var tp=String(a.type).trim();',
' if(tp!=="Asset"&&tp!=="Liability"&&tp!=="Equity")continue;',
' var t=String(a.title).trim();var nrm=String(a.normal||"").trim();',
' acctMap[t]={code:String(a.code||""),title:t,type:tp,fsGroup:String(a.fsGroup||"").trim(),noteGroup:String(a.noteGroup||"").trim(),normal:nrm,isContraEq:(tp==="Equity"&&nrm==="Debit"),net:0};',
' if(!bsCapAcct&&tp==="Equity"&&nrm==="Credit")bsCapAcct=t;',
' if(tp==="Equity"&&nrm==="Credit"&&t==="Retained Earnings")bsReAcct=t;',
' }',
' var bsIsCorp=String((typeof DATA!=="undefined"&&DATA.settings&&DATA.settings.taxpayerClass)||"").toUpperCase().indexOf("NON")===0;',
' var bsRollT=(bsIsCorp&&bsReAcct)?bsReAcct:bsCapAcct;',
// Compute Equity NI split: Current Year NI (FY start → as-of) + Cumulative Profits (1900 → FY start - 1 day)
' var fyStartDateStr=computeFYStart(asOfDate,fyM);',
// Accumulate balances up to as-of date. Contra-Equity (Owner Withdrawals): CY-only; prior-FY rolls into Capital (matches TB/SCE FY-reset).
' var bsPyContraEq=0;',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(!acctMap[je.account])continue;var d=je.date||"";if(d>asOfDate)continue;var bm=acctMap[je.account];if(bm.isContraEq&&d<fyStartDateStr){bsPyContraEq+=(je.debit||0)-(je.credit||0);continue;}bm.net+=(je.debit||0)-(je.credit||0);}',
' if(bsRollT&&bsPyContraEq!==0)acctMap[bsRollT].net+=bsPyContraEq;',
' var dayBefore=addDaysISO(fyStartDateStr,-1);',
' var isCY=computeIS(jeRows,coa,fyStartDateStr,asOfDate,fyM);',
' var currentYearNI=isCY.netIncome;',
' var cumulativeProfits=0;',
' if(fyStartDateStr>"1900-01-01"){',
' var isPY=computeIS(jeRows,coa,"1900-01-01",dayBefore,fyM);',
' cumulativeProfits=isPY.netIncome;',
' }',
' var cumNI=rnd(currentYearNI+cumulativeProfits);',
// CORP (2026-06-05, D2): prior-FY profits fold INTO the Retained Earnings account line
// (no synthetic Cumulative Profits row). Current Year Net Income pseudo-row stays.
' if(bsIsCorp&&bsReAcct&&cumulativeProfits!==0){acctMap[bsReAcct].net+=-cumulativeProfits;cumulativeProfits=0;}',
// Group into Types / FSGroups / NoteGroups
' var types={};BS_TYPES.forEach(function(t){types[t]={name:t,sections:{},total:0,order:BS_TYPE_ORDER[t]};});',
' var acctTitles=Object.keys(acctMap);',
' for(var k=0;k<acctTitles.length;k++){var at=acctTitles[k];var acc=acctMap[at];',
// Type-normal balance: Asset=Debit, Liability=Credit, Equity=Credit
' var typeNormal=(acc.type==="Asset")?"Debit":"Credit";',
' var isContra=(acc.normal!==typeNormal);',
// Natural display amount: for Debit-normal type (Asset), net>0 = positive. For Credit-normal type (Liab/Equity), net<0 = positive, so flip.
' var naturalDisplay=(typeNormal==="Credit")?-acc.net:acc.net;',
// Contra accounts (e.g., Accum Depr under Asset, Owner Withdrawals under Equity) display as negative to reduce Type total
' var displayAmt=isContra?-Math.abs(naturalDisplay):naturalDisplay;',
' displayAmt=rnd(displayAmt);',
' if(displayAmt===0)continue;',
' var fsg=acc.fsGroup||acc.type;',
' var ng=acc.noteGroup||acc.title;',
' if(!types[acc.type].sections[fsg])types[acc.type].sections[fsg]={name:fsg,noteGroups:{},total:0,order:BS_FSGROUP_ORDER[fsg]||99};',
' var sec=types[acc.type].sections[fsg];',
' if(!sec.noteGroups[ng]){var noteNum=extractNoteNum(ng);sec.noteGroups[ng]={name:ng,noteNum:noteNum,displayTitle:formatNoteGroupTitle(ng),accounts:[],total:0};}',
' sec.noteGroups[ng].accounts.push({code:acc.code,title:at,amount:displayAmt,normal:acc.normal,fsGroup:fsg,noteGroup:ng});',
' sec.noteGroups[ng].total+=displayAmt;',
' sec.total+=displayAmt;',
' types[acc.type].total+=displayAmt;',
' }',
// Add two pseudo-accounts to Equity: Current Year Net Income + Cumulative Profits
' var eqSectionKeys=Object.keys(types.Equity.sections);',
' var targetSec=null,targetNG=null;',
' if(eqSectionKeys.length>0){targetSec=types.Equity.sections[eqSectionKeys[0]];var ngKeys=Object.keys(targetSec.noteGroups);if(ngKeys.length>0)targetNG=targetSec.noteGroups[ngKeys[0]];}',
// If no existing Equity section/NG, create one
' if(!targetSec){targetSec={name:"Equity",noteGroups:{},total:0,order:99};types.Equity.sections["Equity"]=targetSec;}',
' if(!targetNG){targetNG={name:"Equity",noteNum:"",displayTitle:"Equity",accounts:[],total:0};targetSec.noteGroups["Equity"]=targetNG;}',
' if(currentYearNI!==0){',
' targetNG.accounts.push({code:"NI01",title:"Current Year Net Income",amount:currentYearNI,normal:"Credit",isCurrentYearNI:true,fsGroup:targetSec.name,noteGroup:targetNG.name});',
' targetNG.total+=currentYearNI;targetSec.total+=currentYearNI;types.Equity.total+=currentYearNI;',
' }',
' if(cumulativeProfits!==0){',
' targetNG.accounts.push({code:"NI02",title:"Cumulative Profits",amount:cumulativeProfits,normal:"Credit",isCumulativeProfits:true,fsGroup:targetSec.name,noteGroup:targetNG.name});',
' targetNG.total+=cumulativeProfits;targetSec.total+=cumulativeProfits;types.Equity.total+=cumulativeProfits;',
' }',
// Round totals
' BS_TYPES.forEach(function(t){types[t].total=rnd(types[t].total);var sk=Object.keys(types[t].sections);sk.forEach(function(sn){var s=types[t].sections[sn];s.total=rnd(s.total);var ngk=Object.keys(s.noteGroups);ngk.forEach(function(ngn){s.noteGroups[ngn].total=rnd(s.noteGroups[ngn].total);});});});',
// Compute grand totals
' var totalAssets=types.Asset.total;',
' var totalLiab=types.Liability.total;',
' var totalEquity=types.Equity.total;',
' var totalLE=rnd(totalLiab+totalEquity);',
' var imbalance=rnd(totalAssets-totalLE);',
' return{types:types,totalAssets:totalAssets,totalLiab:totalLiab,totalEquity:totalEquity,totalLE:totalLE,imbalance:imbalance,balanced:Math.abs(imbalance)<0.01,asOf:asOfDate,cumNI:cumNI,currentYearNI:currentYearNI,cumulativeProfits:cumulativeProfits,fyStart:fyStartDateStr};',
'}',
'',
// Helper: get FY start date for a given as-of date
'function getFYStartForDate(dt,fyM){',
' var yr=parseInt(dt.substring(0,4));var mo=parseInt(dt.substring(5,7));',
' if(mo>=fyM)return yr+"-"+pad(fyM)+"-01";',
' return(yr-1)+"-"+pad(fyM)+"-01";',
'}',
'',
// Helper: extract note number from NoteGroup label like "4 - Cash and Cash Equivalents" → "4"
'function extractNoteNum(ng){if(!ng)return"";var m=String(ng).match(/^(\\d+)/);return m?m[1]:"";}',
'',
// Helper: format NoteGroup into display title. "4 - Cash and Cash Equivalents" → "Cash and Cash Equivalents (Note 4)"
'function formatNoteGroupTitle(ng){if(!ng)return"";var m=String(ng).match(/^(\\d+(?:\\.\\d+)?)\\s*-\\s*(.+)$/);if(m)return m[2].trim()+" (Note "+m[1]+")";return String(ng);}',
'',
'/* ── BS Periods (Compare) ── */',
'function computeBSPeriods(){',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var asOf=document.getElementById("bsDate").value||BS_STATE.asOfDate;',
' BS_STATE.asOfDate=asOf;',
' var je=jeView_();',
' var periods=[{label:fmtAsOfLabel(asOf),asOf:asOf,bs:computeBS(je,DATA.coa,asOf,fyM)}];',
' var mode=BS_STATE.compareMode;',
' if(mode==="pye"){var p=shiftYear(asOf,-1);periods.push({label:fmtAsOfLabel(p),asOf:p,bs:computeBS(je,DATA.coa,p,fyM)});}',
' else if(mode==="pqe"){var pq=shiftMonths(asOf,-3);periods.push({label:fmtAsOfLabel(pq),asOf:pq,bs:computeBS(je,DATA.coa,pq,fyM)});}',
' else if(mode==="pme"){var pm=shiftMonths(asOf,-1);periods.push({label:fmtAsOfLabel(pm),asOf:pm,bs:computeBS(je,DATA.coa,pm,fyM)});}',
' else if(mode==="pfye"){',
// Previous fiscal year end
' var yr=parseInt(asOf.substring(0,4));var mo=parseInt(asOf.substring(5,7));',
' var pfy;if(fyM===1){pfy=(yr-1)+"-12-31";}else{if(mo>=fyM)pfy=yr+"-"+pad(fyM-1)+"-"+ld(fyM-1,yr);else pfy=(yr-1)+"-"+pad(fyM-1)+"-"+ld(fyM-1,yr-1);}',
' periods.push({label:fmtAsOfLabel(pfy),asOf:pfy,bs:computeBS(je,DATA.coa,pfy,fyM)});',
' }',
' else if(mode==="custom"){var cd=BS_STATE.customCompareDate||document.getElementById("bsCustomCmpDate").value;periods.push({label:fmtAsOfLabel(cd),asOf:cd,bs:computeBS(je,DATA.coa,cd,fyM)});}',
' else if(mode==="multi"){periods=[];var interval=BS_STATE.multiInterval||"monthly";var count=BS_STATE.multiCount||4;for(var i=0;i<count;i++){var pdt;if(interval==="monthly"){pdt=shiftMonths(asOf,-i);}else if(interval==="quarterly"){pdt=shiftMonths(asOf,-i*3);}else{pdt=shiftYear(asOf,-i);}periods.push({label:fmtAsOfLabel(pdt),asOf:pdt,bs:computeBS(je,DATA.coa,pdt,fyM)});}}',
' return periods;',
'}',
'function fmtAsOfLabel(d){var mos=["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];var mo=parseInt(d.substring(5,7)),dy=parseInt(d.substring(8,10)),yr=d.substring(0,4);return mos[mo]+" "+dy+", "+yr;}',
'',
'function recomputeBS(){if(!document.getElementById("bsDate"))return;',
' if(!SKEL_SUPPRESS){showSkeleton("bs");SKEL_SUPPRESS=true;setTimeout(function(){recomputeBS();SKEL_SUPPRESS=false;fadeInReport("bs");},200);return;}',
' var periods=computeBSPeriods();',
' renderBS(periods);',
' updateBSExpandBtn();',
'}',
'',
'/* ── BS Render (Xero-inspired flat hierarchy) ── */',
'function renderBS(periods){',
' var primary=periods[0];',
' var numP=periods.length;',
' var showVar=BS_STATE.showVariance&&numP===2;',
' var showTrend=BS_STATE.showTrend&&numP===1;',
' var showPct=BS_STATE.showPctTotalAssets;',
' var showNG=BS_STATE.showNoteGroup;',
' var h=renderReportHeader(getReportMetadata("Balance Sheet","asof",{date:primary.asOf}));',
' var metaCols=[];if(showNG)metaCols.push({id:"noteGroup",label:"Note Group"});',
' var totalCols=1+metaCols.length+(showTrend?1:0)+numP*(1+(showPct?1:0))+(showVar?2:0);',
'',
' h+="<div class=\\"table-wrap airy-report\\"><table class=\\"tb-table\\"><thead><tr>";',
' h+="<th style=\\"min-width:260px\\">Account</th>";',
' for(var mi=0;mi<metaCols.length;mi++)h+="<th>"+metaCols[mi].label+"</th>";',
' if(showTrend)h+="<th style=\\"text-align:center;min-width:80px\\">Trend</th>";',
' for(var p=0;p<numP;p++){h+="<th class=\\"num\\">"+periods[p].label+"</th>";if(showPct)h+="<th class=\\"num\\" style=\\"width:55px\\">%</th>";}',
' if(showVar)h+="<th class=\\"num\\">Variance</th><th class=\\"num\\">Var %</th>";',
' h+="</tr></thead><tbody>";',
'',
' var anyData=periods.some(function(pp){return BS_TYPES.some(function(t){return Object.keys(pp.bs.types[t].sections).length>0;});});',
' if(!anyData){h+="<tr class=\\"is-empty-row\\"><td colspan=\\""+totalCols+"\\">No transactions as of this date.</td></tr>";h+="</tbody></table></div>";document.getElementById("report").innerHTML=h;return;}',
'',
// For each Type: Type row with chevron, then FSGroups (Asset/Liability) or flat accounts (Equity), then Type total
' for(var ti=0;ti<BS_TYPES.length;ti++){',
' var tn=BS_TYPES[ti];',
' var hasType=periods.some(function(pp){return Object.keys(pp.bs.types[tn].sections).length>0;});',
' if(!hasType)continue;',
' var typeCollapsed=BS_STATE.collapsedTypes[tn]===true;',
' h+=renderBSTypeHeaderRow(periods,tn,typeCollapsed,metaCols,showPct,showVar,showTrend,totalCols);',
' if(!typeCollapsed){',
' if(tn==="Equity"){',
' h+=renderBSEquityAccounts(periods,metaCols,showPct,showVar,showTrend);',
' }else{',
' var fsUnion={};periods.forEach(function(pp){var sk=Object.keys(pp.bs.types[tn].sections);sk.forEach(function(s){if(!fsUnion[s])fsUnion[s]={order:pp.bs.types[tn].sections[s].order};});});',
' var fsList=Object.keys(fsUnion).sort(function(x,y){return(fsUnion[x].order||99)-(fsUnion[y].order||99);});',
' for(var fi=0;fi<fsList.length;fi++){var fg=fsList[fi];h+=renderBSFSGroupBlock(periods,tn,fg,metaCols,showPct,showVar,showTrend,totalCols);}',
' }',
// Type total row (always shown)
' h+=renderBSTypeTotalRow(periods,tn,metaCols,showPct,showVar,showTrend);',
' }',
' h+="<tr class=\\"bs-spacer-row\\"><td colspan=\\""+totalCols+"\\"> </td></tr>";',
' }',
// Balance check: silent when OK, red warning when not
' var allBalanced=periods.every(function(pp){return pp.bs.balanced;});',
' if(!allBalanced){var msgs=[];periods.forEach(function(pp){if(!pp.bs.balanced){var v=pp.bs.imbalance;msgs.push(pp.label+": off by "+Math.abs(v).toFixed(2));}});h+="<tr class=\\"bs-check-bad\\"><td colspan=\\""+totalCols+"\\">\\u26A0 Not Balanced - "+msgs.join(" | ")+"</td></tr>";}',
'',
' h+="</tbody></table>";',
' h+="</div>";',
' document.getElementById("report").innerHTML=h;',
'}',
'',
// Type header row: chevron + human label, amounts empty on this row (shown on Type total row below)
'function renderBSTypeHeaderRow(periods,typeName,collapsed,metaCols,showPct,showVar,showTrend,totalCols){',
' var chev=collapsed?"\\u25B8":"\\u25BE";',
' var isAssetLike=(typeName==="Asset"||typeName==="Equity");',
' var numP=periods.length;',
' var h="<tr class=\\"bs-type-row"+(collapsed?" bs-type-row-collapsed":"")+"\\" onclick=\\"toggleBSType(\'"+typeName+"\')\\">";',
' if(collapsed){',
// Label cell + amounts (like IS collapsed pattern)
' h+="<td><span class=\\"bs-chev\\">"+chev+"</span>"+BS_TYPE_HUMAN[typeName]+"</td>";',
' for(var mh=0;mh<metaCols.length;mh++)h+="<td></td>";',
' if(showTrend)h+="<td></td>";',
' for(var p=0;p<numP;p++){var v=periods[p].bs.types[typeName].total;h+="<td class=\\"num\\">"+fmtBSNum(v)+"</td>";if(showPct){var ta=periods[p].bs.totalAssets;h+="<td class=\\"bs-pct\\">"+fmtBSPct(v,ta)+"</td>";}}',
' if(showVar){var cy=periods[0].bs.types[typeName].total,py=periods[1].bs.types[typeName].total;var va=rnd(cy-py);var vp=py!==0?rnd((va/Math.abs(py))*100):(cy!==0?100:0);h+=fmtBSVarTDs(va,vp,isAssetLike,false);}',
' }else{',
// Expanded: label spans full width, no amount
' h+="<td colspan=\\""+totalCols+"\\"><span class=\\"bs-chev\\">"+chev+"</span>"+BS_TYPE_HUMAN[typeName]+"</td>";',
' }',
' h+="</tr>";',
' return h;',
'}',
'',
// Type total row: bold black, amounts shown
'function renderBSTypeTotalRow(periods,typeName,metaCols,showPct,showVar,showTrend){',
' var isAssetLike=(typeName==="Asset"||typeName==="Equity");',
' var numP=periods.length;',
' var h="<tr class=\\"bs-type-total\\"><td>Total "+BS_TYPE_HUMAN[typeName]+"</td>";',
' for(var mi=0;mi<metaCols.length;mi++)h+="<td></td>";',
' if(showTrend)h+="<td></td>";',
' for(var p=0;p<numP;p++){var v=periods[p].bs.types[typeName].total;h+="<td class=\\"num\\">"+fmtBSNum(v)+"</td>";if(showPct){var ta=periods[p].bs.totalAssets;h+="<td class=\\"bs-pct\\">"+fmtBSPct(v,ta)+"</td>";}}',
' if(showVar){var cy=periods[0].bs.types[typeName].total,py=periods[1].bs.types[typeName].total;var va=rnd(cy-py);var vp=py!==0?rnd((va/Math.abs(py))*100):(cy!==0?100:0);h+=fmtBSVarTDs(va,vp,isAssetLike,false);}',
' h+="</tr>";',
' return h;',
'}',
'',
// FSGroup block: header row (chevron) + account rows + FSGroup total row
'function renderBSFSGroupBlock(periods,typeName,fsGroup,metaCols,showPct,showVar,showTrend,totalCols){',
' var hasAny=periods.some(function(p){return p.bs.types[typeName].sections[fsGroup]&&Object.keys(p.bs.types[typeName].sections[fsGroup].noteGroups).length>0;});',
' if(!hasAny)return "";',
' var isAssetLike=(typeName==="Asset"||typeName==="Equity");',
' var fsgKey=typeName+"|"+fsGroup;',
' var collapsed=BS_STATE.collapsedFSGroups[fsgKey]===true;',
' var chev=collapsed?"\\u25B8":"\\u25BE";',
' var numP=periods.length;',
' var h="";',
// FSGroup header row (clickable)
' h+="<tr class=\\"bs-fsg-row"+(collapsed?" bs-fsg-row-collapsed":"")+"\\" onclick=\\"toggleBSFSGroup(\'"+typeName+"\',\'"+fsGroup.replace(/\'/g,"'")+"\')\\">";',
' if(collapsed){',
// Label cell + amounts
' h+="<td><span class=\\"bs-chev\\">"+chev+"</span>"+fsGroup+"</td>";',
' for(var mh=0;mh<metaCols.length;mh++)h+="<td></td>";',
' if(showTrend)h+="<td></td>";',
' for(var pf=0;pf<numP;pf++){var secF=periods[pf].bs.types[typeName].sections[fsGroup];var amtF=secF?secF.total:0;h+="<td class=\\"num\\">"+fmtBSNum(amtF)+"</td>";if(showPct){var taF=periods[pf].bs.totalAssets;h+="<td class=\\"bs-pct\\">"+fmtBSPct(amtF,taF)+"</td>";}}',
' if(showVar){var cyF=periods[0].bs.types[typeName].sections[fsGroup]?periods[0].bs.types[typeName].sections[fsGroup].total:0;var pyF=periods[1].bs.types[typeName].sections[fsGroup]?periods[1].bs.types[typeName].sections[fsGroup].total:0;var vaF=rnd(cyF-pyF);var vpF=pyF!==0?rnd((vaF/Math.abs(pyF))*100):(cyF!==0?100:0);h+=fmtBSVarTDs(vaF,vpF,isAssetLike,false);}',
' h+="</tr>";',
' return h;',
' }',
// Expanded: no amount on FSGroup row
' h+="<td colspan=\\""+totalCols+"\\"><span class=\\"bs-chev\\">"+chev+"</span>"+fsGroup+"</td>";',
' h+="</tr>";',
// Union of accounts across all NoteGroups in this FSGroup across periods
' var acctUnion={};',
' periods.forEach(function(pp){var sec=pp.bs.types[typeName].sections[fsGroup];if(!sec)return;var ngks=Object.keys(sec.noteGroups);ngks.forEach(function(ng){sec.noteGroups[ng].accounts.forEach(function(a){if(!acctUnion[a.title])acctUnion[a.title]={code:a.code,title:a.title,noteGroup:sec.noteGroups[ng].displayTitle};});});});',
' var acctTitles=Object.keys(acctUnion).sort(function(x,y){var cx=acctUnion[x].code,cy=acctUnion[y].code;return cx<cy?-1:cx>cy?1:0;});',
' for(var ai=0;ai<acctTitles.length;ai++){var acctTitle=acctTitles[ai];',
' h+=renderBSAccountRow(periods,typeName,fsGroup,acctTitle,acctUnion[acctTitle],metaCols,showPct,showVar,showTrend,isAssetLike,false,false);',
' }',
// FSGroup total row (only rendered when expanded)
' h+="<tr class=\\"bs-fsg-total\\"><td>Total "+fsGroup+"</td>";',
' for(var mmi=0;mmi<metaCols.length;mmi++)h+="<td></td>";',
' if(showTrend)h+="<td></td>";',
' for(var ps=0;ps<periods.length;ps++){var sec2=periods[ps].bs.types[typeName].sections[fsGroup];var amtS=sec2?sec2.total:0;h+="<td class=\\"num\\">"+fmtBSNum(amtS)+"</td>";if(showPct){var taS=periods[ps].bs.totalAssets;h+="<td class=\\"bs-pct\\">"+fmtBSPct(amtS,taS)+"</td>";}}',
' if(showVar){var cyS=periods[0].bs.types[typeName].sections[fsGroup]?periods[0].bs.types[typeName].sections[fsGroup].total:0;var pyS=periods[1].bs.types[typeName].sections[fsGroup]?periods[1].bs.types[typeName].sections[fsGroup].total:0;var vaS=rnd(cyS-pyS);var vpS=pyS!==0?rnd((vaS/Math.abs(pyS))*100):(cyS!==0?100:0);h+=fmtBSVarTDs(vaS,vpS,isAssetLike,false);}',
' h+="</tr>";',
' return h;',
'}',
'',
// Equity accounts: flat list (no FSGroup wrapper, no chevron), sorted by code, NI pseudo-accounts at end
'function renderBSEquityAccounts(periods,metaCols,showPct,showVar,showTrend){',
' var isAssetLike=true;',
// Union across all FSGroups/NoteGroups in Equity
' var acctUnion={};',
' periods.forEach(function(pp){',
' var secs=pp.bs.types["Equity"].sections;',
' Object.keys(secs).forEach(function(fg){',
' var ngs=secs[fg].noteGroups;',
' Object.keys(ngs).forEach(function(ng){',
' ngs[ng].accounts.forEach(function(a){',
' var key=a.title;',
' if(!acctUnion[key])acctUnion[key]={code:a.code,title:a.title,isCurrentYearNI:!!a.isCurrentYearNI,isCumulativeProfits:!!a.isCumulativeProfits,noteGroup:ngs[ng].displayTitle};',
' });',
' });',
' });',
' });',
// Sort: regular by code, NI pseudo-accounts last (Current Year NI before Cumulative Profits)
' var titles=Object.keys(acctUnion);',
' var regular=titles.filter(function(t){return !acctUnion[t].isCurrentYearNI&&!acctUnion[t].isCumulativeProfits;});',
' var cyNI=titles.filter(function(t){return acctUnion[t].isCurrentYearNI;});',
' var cumP=titles.filter(function(t){return acctUnion[t].isCumulativeProfits;});',
' regular.sort(function(x,y){var cx=acctUnion[x].code,cy=acctUnion[y].code;return cx<cy?-1:cx>cy?1:0;});',
' var ordered=regular.concat(cyNI).concat(cumP);',
' var h="";',
' for(var ai=0;ai<ordered.length;ai++){var at=ordered[ai];var info=acctUnion[at];',
' h+=renderBSAccountRow(periods,"Equity",null,at,info,metaCols,showPct,showVar,showTrend,isAssetLike,!!info.isCurrentYearNI,!!info.isCumulativeProfits);',
' }',
' return h;',
'}',
'',
// Render single account row with drillable amounts
'function renderBSAccountRow(periods,typeName,fsGroupOrNull,acctTitle,info,metaCols,showPct,showVar,showTrend,isAssetLike,isCurrentYearNI,isCumulativeProfits){',
' var h="<tr class=\\"bs-acct-row\\"><td>"+acctTitle+"</td>";',
' for(var mi=0;mi<metaCols.length;mi++){var mc=metaCols[mi];var metaVal=(mc.id==="noteGroup")?(info.noteGroup||""):"";h+="<td class=\\"bs-meta-col\\">"+metaVal+"</td>";}',
' if(showTrend)h+="<td></td>";',
' for(var pi=0;pi<periods.length;pi++){',
' var amt=0;',
// Find amount for this account in this period
' if(isCurrentYearNI)amt=periods[pi].bs.currentYearNI||0;',
' else if(isCumulativeProfits)amt=periods[pi].bs.cumulativeProfits||0;',
' else{',
' if(fsGroupOrNull){',
' var sec=periods[pi].bs.types[typeName].sections[fsGroupOrNull];',
' if(sec){var ngks=Object.keys(sec.noteGroups);for(var ki=0;ki<ngks.length;ki++){var accs=sec.noteGroups[ngks[ki]].accounts;for(var ai2=0;ai2<accs.length;ai2++){if(accs[ai2].title===acctTitle){amt=accs[ai2].amount;break;}}if(amt!==0)break;}}',
' }else{',
// Equity search across all FSGroups/NoteGroups
' var secsE=periods[pi].bs.types["Equity"].sections;',
' Object.keys(secsE).forEach(function(fgE){Object.keys(secsE[fgE].noteGroups).forEach(function(ngE){secsE[fgE].noteGroups[ngE].accounts.forEach(function(aE){if(aE.title===acctTitle)amt+=aE.amount;});});});',
' }',
' }',
// Amount cell - blue clickable unless Cumulative Profits
' var periodAsOf=periods[pi].asOf;',
' var amtClass=isCumulativeProfits?"bs-acct-amt not-drillable":"bs-acct-amt";',
' var onclickAttr="";',
' if(!isCumulativeProfits){',
' if(isCurrentYearNI)onclickAttr=" onclick=\\"drillToISFromBS(\'"+periodAsOf+"\')\\"";',
' else onclickAttr=" onclick=\\"drillFromBSPeriod(\'"+escJS(acctTitle)+"\',\'"+periodAsOf+"\')\\"";',
' }',
' h+="<td class=\\"num "+amtClass+"\\""+onclickAttr+">"+fmtBSAmt(amt)+"</td>";',
' if(showPct){var ta=periods[pi].bs.totalAssets;h+="<td class=\\"bs-pct\\">"+fmtBSPct(amt,ta)+"</td>";}',
' }',
' if(showVar){',
' var aCY=0,aPY=0;',
' if(isCurrentYearNI){aCY=periods[0].bs.currentYearNI||0;aPY=periods[1].bs.currentYearNI||0;}',
' else if(isCumulativeProfits){aCY=periods[0].bs.cumulativeProfits||0;aPY=periods[1].bs.cumulativeProfits||0;}',
' else{',
// Look up amounts for CY and PY
' [0,1].forEach(function(idx){',
' var acc=0;',
' if(fsGroupOrNull){var sec=periods[idx].bs.types[typeName].sections[fsGroupOrNull];if(sec){var ngks=Object.keys(sec.noteGroups);for(var ki=0;ki<ngks.length;ki++){var accs=sec.noteGroups[ngks[ki]].accounts;for(var ai2=0;ai2<accs.length;ai2++){if(accs[ai2].title===acctTitle){acc=accs[ai2].amount;break;}}if(acc!==0)break;}}}',
' else{var secsE=periods[idx].bs.types["Equity"].sections;Object.keys(secsE).forEach(function(fgE){Object.keys(secsE[fgE].noteGroups).forEach(function(ngE){secsE[fgE].noteGroups[ngE].accounts.forEach(function(aE){if(aE.title===acctTitle)acc+=aE.amount;});});});}',
' if(idx===0)aCY=acc;else aPY=acc;',
' });',
' }',
' var vaA=rnd(aCY-aPY);var vpA=aPY!==0?rnd((vaA/Math.abs(aPY))*100):(aCY!==0?100:0);h+=fmtBSVarTDs(vaA,vpA,isAssetLike,false);',
' }',
' h+="</tr>";',
' return h;',
'}',
'',
// BS formatters
'function fmtBSNum(n){if(n===0||n===null||n===undefined)return"-";var abs=Math.abs(n);var s=APP_STATE.hideDecimals?Math.round(abs).toLocaleString("en-US"):abs.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});return n<0?"("+s+")":s;}',
'function fmtBSAmt(n){if(n===0||n===null||n===undefined)return"-";var abs=Math.abs(n);var s=APP_STATE.hideDecimals?Math.round(abs).toLocaleString("en-US"):abs.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});return n<0?"("+s+")":s;}',
'function fmtBSPct(n,denom){if(!denom||denom===0)return"-";return((n/denom)*100).toFixed(1)+"%";}',
'',
// Variance color: isAssetLike = Asset/Equity (up = green). For Liability, up = red.
'function fmtBSVarTDs(va,vp,isAssetLike,onSection,onNavy){',
' var color;',
' if(va===0)color="#9CA3AF";',
' else{var good=isAssetLike?(va>0):(va<0);color=good?"#2E7D32":"#C62828";}',
' var vaStr=va===0?"-":(va>0?"+":"-")+fmtBSAmt(Math.abs(va));',
' var vpStr=va===0?"-":(vp>0?"+":"-")+Math.abs(vp).toFixed(1)+"%";',
' return "<td class=\\"num var-cell\\" style=\\"color:"+color+"\\">"+vaStr+"</td>"+"<td class=\\"num var-cell\\" style=\\"color:"+color+"\\">"+vpStr+"</td>";',
'}',
'',
// Sparkline for NoteGroup across last 6 as-of snapshots
'function makeBSSparklineNG(typeName,fsGroup,noteGroup,isAssetLike){',
' var asOf=BS_STATE.asOfDate||document.getElementById("bsDate").value;',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var pts=[];',
// Last 6 monthly snapshots
' for(var i=5;i>=0;i--){var dt=shiftMonths(asOf,-i);var bs=computeBS(jeView_(),DATA.coa,dt,fyM);var sec=bs.types[typeName].sections[fsGroup];var amt=(sec&&sec.noteGroups[noteGroup])?sec.noteGroups[noteGroup].total:0;pts.push(amt);}',
' if(pts.every(function(p){return p===0}))return"";',
' var max=Math.max.apply(null,pts.map(Math.abs));if(max===0)return"";',
' var w=70,h=18,points=[];',
' for(var j=0;j<pts.length;j++){var x=(j/(pts.length-1))*w;var y=h-((pts[j]+max)/(2*max))*(h-4)-2;points.push(x.toFixed(1)+","+y.toFixed(1));}',
' var last=pts[pts.length-1],first=pts[0];',
' var up=last>first,down=last<first;',
' var col;if(!up&&!down)col="#888";else{var good=isAssetLike?up:down;col=good?"#2E7D32":"#C62828";}',
' var lp=points[points.length-1].split(",");',
' return "<svg width=\\""+w+"\\" height=\\""+h+"\\" style=\\"vertical-align:middle\\"><polyline points=\\""+points.join(" ")+"\\" fill=\\"none\\" stroke=\\""+col+"\\" stroke-width=\\"1.5\\"/><circle cx=\\""+lp[0]+"\\" cy=\\""+lp[1]+"\\" r=\\"2\\" fill=\\""+col+"\\"/></svg>";',
'}',
'',
'/* ── BS Export ── */',
'function expBS(){',
' var periods=computeBSPeriods();',
' var primary=periods[0];',
' var numP=periods.length;',
' var showVar=BS_STATE.showVariance&&numP===2;',
' var showPct=BS_STATE.showPctTotalAssets;',
' var showNG=BS_STATE.showNoteGroup;',
' var metaCols=[];if(showNG)metaCols.push({id:"noteGroup",label:"Note Group"});',
' var hdr=["Account"];for(var mi=0;mi<metaCols.length;mi++)hdr.push(metaCols[mi].label);for(var p=0;p<numP;p++){hdr.push(periods[p].label);if(showPct)hdr.push("%");}if(showVar){hdr.push("Variance");hdr.push("Var %");}',
' var ws=[hdr];',
' function pushRow(cells){ws.push(cells);}',
' function emptyMetas(){var a=[];for(var x=0;x<metaCols.length;x++)a.push("");return a;}',
' for(var ti=0;ti<BS_TYPES.length;ti++){var tn=BS_TYPES[ti];',
' var hasType=periods.some(function(pp){return Object.keys(pp.bs.types[tn].sections).length>0;});if(!hasType)continue;',
// Type header row (label only, no totals here)
' var typeHdr=[BS_TYPE_HUMAN[tn]].concat(emptyMetas());',
' for(var j=0;j<numP;j++){typeHdr.push("");if(showPct)typeHdr.push("");}',
' if(showVar){typeHdr.push("");typeHdr.push("");}',
' pushRow(typeHdr);',
' if(tn==="Equity"){',
// Equity flat: union across FSGroups/NoteGroups, sort by code, NI pseudo-accounts last
' var acctUE={};',
' periods.forEach(function(pp){var secs=pp.bs.types["Equity"].sections;Object.keys(secs).forEach(function(fg){var ngs=secs[fg].noteGroups;Object.keys(ngs).forEach(function(ng){ngs[ng].accounts.forEach(function(a){if(!acctUE[a.title])acctUE[a.title]={code:a.code,title:a.title,isCurrentYearNI:!!a.isCurrentYearNI,isCumulativeProfits:!!a.isCumulativeProfits,noteGroup:ngs[ng].displayTitle};});});});});',
' var titles=Object.keys(acctUE);',
' var regular=titles.filter(function(t){return !acctUE[t].isCurrentYearNI&&!acctUE[t].isCumulativeProfits;});',
' var cyNI=titles.filter(function(t){return acctUE[t].isCurrentYearNI;});',
' var cumP=titles.filter(function(t){return acctUE[t].isCumulativeProfits;});',
' regular.sort(function(x,y){var cx=acctUE[x].code,cy=acctUE[y].code;return cx<cy?-1:cx>cy?1:0;});',
' var ordered=regular.concat(cyNI).concat(cumP);',
' for(var aE=0;aE<ordered.length;aE++){var atE=ordered[aE];var infoE=acctUE[atE];',
' var rE=[" "+atE];',
' for(var m2=0;m2<metaCols.length;m2++){var mc=metaCols[m2];rE.push(mc.id==="noteGroup"?(infoE.noteGroup||""):"");}',
' var aCYE=0,aPYE=0;',
' for(var piE=0;piE<numP;piE++){',
' var amtE=0;',
' if(infoE.isCurrentYearNI)amtE=periods[piE].bs.currentYearNI||0;',
' else if(infoE.isCumulativeProfits)amtE=periods[piE].bs.cumulativeProfits||0;',
' else{var secsE=periods[piE].bs.types["Equity"].sections;Object.keys(secsE).forEach(function(fgE){Object.keys(secsE[fgE].noteGroups).forEach(function(ngE){secsE[fgE].noteGroups[ngE].accounts.forEach(function(aEa){if(aEa.title===atE)amtE+=aEa.amount;});});});}',
' rE.push(amtE);',
' if(showPct){var taE2=periods[piE].bs.totalAssets;rE.push(taE2?amtE/taE2:0);}',
' if(piE===0)aCYE=amtE;if(piE===1)aPYE=amtE;',
' }',
' if(showVar){var vaE=rnd(aCYE-aPYE);rE.push(vaE);rE.push(aPYE!==0?vaE/Math.abs(aPYE):(aCYE!==0?1:0));}',
' pushRow(rE);',
' }',
' }else{',
// Asset/Liability: FSGroup header + account rows + FSGroup subtotal, no NoteGroup summary
' var fsUnion={};periods.forEach(function(pp){var sk=Object.keys(pp.bs.types[tn].sections);sk.forEach(function(s){if(!fsUnion[s])fsUnion[s]={order:pp.bs.types[tn].sections[s].order};});});',
' var fsList=Object.keys(fsUnion).sort(function(x,y){return(fsUnion[x].order||99)-(fsUnion[y].order||99);});',
' for(var fi=0;fi<fsList.length;fi++){var fg=fsList[fi];',
' var hasFG=periods.some(function(pp){var sec=pp.bs.types[tn].sections[fg];return sec&&Object.keys(sec.noteGroups).length>0;});if(!hasFG)continue;',
// FSGroup header row
' var fgRow=[" "+fg].concat(emptyMetas());',
' for(var k=0;k<numP;k++){fgRow.push("");if(showPct)fgRow.push("");}',
' if(showVar){fgRow.push("");fgRow.push("");}',
' pushRow(fgRow);',
// Account rows under this FSGroup (union across NoteGroups)
' var acctU={};periods.forEach(function(pp){var sec5=pp.bs.types[tn].sections[fg];if(!sec5)return;var ngks=Object.keys(sec5.noteGroups);ngks.forEach(function(ng){sec5.noteGroups[ng].accounts.forEach(function(a){if(!acctU[a.title])acctU[a.title]={code:a.code,noteGroup:sec5.noteGroups[ng].displayTitle};});});});',
' var atList=Object.keys(acctU).sort(function(x,y){var cx=acctU[x].code,cy2=acctU[y].code;return cx<cy2?-1:cx>cy2?1:0;});',
' for(var aai=0;aai<atList.length;aai++){var at=atList[aai];var info2=acctU[at];',
' var r2=[" "+at];',
' for(var m3=0;m3<metaCols.length;m3++){var mc3=metaCols[m3];r2.push(mc3.id==="noteGroup"?(info2.noteGroup||""):"");}',
' var aCY=0,aPY=0;',
' for(var pi2=0;pi2<numP;pi2++){',
' var sec6=periods[pi2].bs.types[tn].sections[fg];var amtX=0;',
' if(sec6){var ngks2=Object.keys(sec6.noteGroups);for(var kg=0;kg<ngks2.length;kg++){var accs=sec6.noteGroups[ngks2[kg]].accounts;for(var ka=0;ka<accs.length;ka++){if(accs[ka].title===at){amtX=accs[ka].amount;break;}}if(amtX!==0)break;}}',
' r2.push(amtX);',
' if(showPct){var ta5=periods[pi2].bs.totalAssets;r2.push(ta5?amtX/ta5:0);}',
' if(pi2===0)aCY=amtX;if(pi2===1)aPY=amtX;',
' }',
' if(showVar){var vaA=rnd(aCY-aPY);r2.push(vaA);r2.push(aPY!==0?vaA/Math.abs(aPY):(aCY!==0?1:0));}',
' pushRow(r2);',
' }',
// FSGroup subtotal
' var subRow=[" Total "+fg].concat(emptyMetas());',
' for(var ks=0;ks<numP;ks++){var sec7=periods[ks].bs.types[tn].sections[fg];subRow.push(sec7?sec7.total:0);if(showPct){var ta6=periods[ks].bs.totalAssets;subRow.push(ta6?(sec7?sec7.total:0)/ta6:0);}}',
' if(showVar){var cy5=periods[0].bs.types[tn].sections[fg]?periods[0].bs.types[tn].sections[fg].total:0;var py5=periods[1].bs.types[tn].sections[fg]?periods[1].bs.types[tn].sections[fg].total:0;var va5=rnd(cy5-py5);subRow.push(va5);subRow.push(py5!==0?va5/Math.abs(py5):(cy5!==0?1:0));}',
' pushRow(subRow);',
' }',
' }',
// Type total row
' var tT=["Total "+BS_TYPE_HUMAN[tn]].concat(emptyMetas());',
' for(var kT=0;kT<numP;kT++){tT.push(periods[kT].bs.types[tn].total);if(showPct){var taT=periods[kT].bs.totalAssets;tT.push(taT?periods[kT].bs.types[tn].total/taT:0);}}',
' if(showVar){var cyT=periods[0].bs.types[tn].total,pyT=periods[1].bs.types[tn].total;var vaT=rnd(cyT-pyT);tT.push(vaT);tT.push(pyT!==0?vaT/Math.abs(pyT):(cyT!==0?1:0));}',
' pushRow(tT);',
' pushRow([""]);',
' }',
' var wb=XLSX.utils.book_new();var sheet=XLSX.utils.aoa_to_sheet(ws);',
' var colW=[{wch:40}];for(var cw=0;cw<metaCols.length;cw++)colW.push({wch:20});for(var cp=0;cp<numP;cp++){colW.push({wch:16});if(showPct)colW.push({wch:8});}if(showVar){colW.push({wch:14});colW.push({wch:10});}',
' sheet["!cols"]=colW;',
' XLSX.utils.book_append_sheet(wb,sheet,"Balance Sheet");',
' XLSX.writeFile(wb,((DATA.settings.businessName||"ECTAXPH").replace(/[^a-zA-Z0-9]/g,"_"))+"_BS_"+primary.asOf+".xlsx");',
'}',
'',
'',
'/* ══════════════════════════════════════',
' CASH FLOW STATEMENT',
' ══════════════════════════════════════ */',
'',
'/* ================================',
' FINANCIAL HEALTH DASHBOARD (db)',
' ================================ */',
'',
'function buildDB(){buildDBControls();recomputeDB();}',
'',
'function buildDBControls(){',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(function(d){return d&&d.length>=10}).sort();',
' var minD=dates[0]||"2025-01-01",maxD=dates[dates.length-1]||"2026-12-31";',
' var h="";',
' h+="<div class=\\"ctrl-bar\\">";',
' h+="<div class=\\"ctrl-group\\"><label>Period</label><select id=\\"dbPreset\\" onchange=\\"applyDBPreset()\\">";',
' h+="<option value=\\"tfy\\" selected>This Financial Year</option>";',
' h+="<option value=\\"ytd\\">Year-to-Date</option>";',
' h+="<option value=\\"lfy\\">Last Financial Year</option>";',
' h+="<option value=\\"tq\\">This Quarter</option>";',
' h+="<option value=\\"qtd\\">Quarter-to-Date</option>";',
' h+="<option value=\\"lq\\">Last Quarter</option>";',
' h+="<option value=\\"tm\\">This Month</option>";',
' h+="<option value=\\"mtd\\">Month-to-Date</option>";',
' h+="<option value=\\"lm\\">Last Month</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"q1\\">Quarter 1</option><option value=\\"q2\\">Quarter 2</option><option value=\\"q3\\">Quarter 3</option><option value=\\"q4\\">Quarter 4</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"custom\\">Custom</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\"><label>From</label><input type=\\"date\\" id=\\"dbFrom\\" value=\\""+minD+"\\" onchange=\\"document.getElementById(\'dbPreset\').value=\'custom\';recomputeDB()\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>To</label><input type=\\"date\\" id=\\"dbTo\\" value=\\""+maxD+"\\" onchange=\\"document.getElementById(\'dbPreset\').value=\'custom\';recomputeDB()\\"></div>";',
' h+="<div class=\\"ctrl-spacer\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label> </label><span style=\\"font-size:11.5px;color:#6B7280\\">Deltas compare vs the prior period of the same length</span></div>";',
' h+="</div>";',
' document.getElementById("controls").innerHTML=h;',
' applyDBPreset();',
'}',
'',
'function applyDBPreset(){',
' var v=document.getElementById("dbPreset").value;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' var maxD=dates[dates.length-1]||"2026-12-31",minD=dates[0]||"2025-01-01";',
' var today=new Date();var todayStr=today.getFullYear()+"-"+pad(today.getMonth()+1)+"-"+pad(today.getDate());',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' if(v==="custom"){DB_STATE.from=document.getElementById("dbFrom").value;DB_STATE.to=document.getElementById("dbTo").value;}',
' else{var r=computeDateRange(v,maxD,fyM,todayStr,minD);if(r){DB_STATE.from=r.from;DB_STATE.to=r.to;document.getElementById("dbFrom").value=r.from;document.getElementById("dbTo").value=r.to;}}',
' DB_STATE.preset=v;',
' recomputeDB();',
'}',
'',
'function recomputeDB(){if(!document.getElementById("dbFrom"))return;',
' if(!SKEL_SUPPRESS){showSkeleton("db");SKEL_SUPPRESS=true;setTimeout(function(){recomputeDB();SKEL_SUPPRESS=false;fadeInReport("db");},200);return;}',
' var from=document.getElementById("dbFrom").value||DB_STATE.from;',
' var to=document.getElementById("dbTo").value||DB_STATE.to;',
' DB_STATE.from=from;DB_STATE.to=to;',
' renderDB(computeDashboard(from,to));',
'}',
'',
'function dbCore_(from,to){',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var isData=computeIS(jeView_(),DATA.coa,from,to,fyM);',
' var rev=rnd((isData.totalRevenue||0)+(isData.totalOI||0));',
' var exp=rnd((isData.totalCOS||0)+(isData.totalOpEx||0)+(isData.totalOExp||0)+(isData.totalIT||0));',
' var cashSet={};',
' for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;if(classifyForCF(a)==="Cash")cashSet[String(a.title).trim()]=1;}',
' var cashBal=0,cashIn=0,cashOut=0;',
' var dbCashJe=jeView_();for(var j=0;j<dbCashJe.length;j++){var je=dbCashJe[j];var d=je.date||"";if(!d||d>to)continue;if(!cashSet[je.account])continue;var dr=parseFloat(je.debit)||0,cr=parseFloat(je.credit)||0;cashBal+=dr-cr;if(d>=from){cashIn+=dr;cashOut+=cr;}}',
' return{rev:rev,exp:exp,ni:rnd(isData.netIncome||0),cashBal:rnd(cashBal),cashIn:rnd(cashIn),cashOut:rnd(cashOut),isData:isData};',
'}',
'',
'function computeDashboard(from,to){',
' var cur=dbCore_(from,to);',
' var days=Math.round((new Date(to+"T00:00:00")-new Date(from+"T00:00:00"))/86400000)+1;if(days<1)days=1;',
' var prior=dbCore_(addDaysISO(from,-days),addDaysISO(from,-1));',
' var typeMap={},assets=0,liabs=0;var cashT=[],revT=[],expT=[],assetT=[],liabT=[];',
' for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;var t=String(a.title).trim(),tp=String(a.type).trim();typeMap[t]=tp;',
' if(tp==="Asset")assetT.push(t);else if(tp==="Liability")liabT.push(t);else if(tp==="Revenue")revT.push(t);else if(tp==="Expense")expT.push(t);',
' if(classifyForCF(a)==="Cash")cashT.push(t);}',
' var expNet={};',
' var dbTypeJe=jeView_();for(var j=0;j<dbTypeJe.length;j++){var je=dbTypeJe[j];var d=je.date||"";if(!d||d>to)continue;var tp2=typeMap[je.account];if(!tp2)continue;var dr=parseFloat(je.debit)||0,cr=parseFloat(je.credit)||0;',
' if(tp2==="Asset")assets+=dr-cr;else if(tp2==="Liability")liabs+=cr-dr;',
' if(tp2==="Expense"&&d>=from){if(!expNet[je.account])expNet[je.account]=0;expNet[je.account]+=dr-cr;}}',
' var top=[];for(var k in expNet){var amt=rnd(expNet[k]);if(amt>0.004)top.push({title:k,amt:amt});}',
' top.sort(function(x,y){return y.amt-x.amt;});top=top.slice(0,5);',
' var revSales=cur.isData.totalRevenue||0;',
' var gpm=revSales===0?0:(cur.isData.grossProfit||0)/revSales;',
' var npm=cur.rev===0?0:cur.ni/cur.rev;',
' var burn=cur.cashOut/(days/30.44);',
' var runway=burn<=0?999:cur.cashBal/burn;',
' return{from:from,to:to,days:days,cur:cur,prior:prior,assets:rnd(assets),liabs:rnd(liabs),nw:rnd(assets-liabs),top:top,gpm:gpm,npm:npm,runway:runway,acc:{cash:cashT,rev:revT,exp:expT,assets:assetT,liabs:liabT}};',
'}',
'',
'function dbFmt_(n){var abs=Math.abs(n);var s=APP_STATE.hideDecimals?Math.round(abs).toLocaleString("en-US"):abs.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});return n<0?"("+s+")":s;}',
'function dbDelta_(cur,prev,invert){if(prev===undefined||prev===null||prev===0)return"";var pct=(cur-prev)/Math.abs(prev)*100;var good=invert?pct<0:pct>=0;var arrow=pct>=0?"\\u25B2":"\\u25BC";return"<span class=\\"db-delta "+(good?"db-up":"db-down")+"\\">"+arrow+" "+Math.abs(pct).toFixed(1)+"% vs prior</span>";}',
'function dbBadge_(txt,cls){return"<span class=\\"db-badge "+cls+"\\">"+txt+"</span>";}',
'function dbDrillAttr_(accts,from,to){var a=escJS(accts.join("|"));return" onclick=\\"drillFromChatToGL(\'"+a+"\',\'"+from+"\',\'"+to+"\')\\" title=\\"Open in General Ledger\\"";}',
'',
'function renderDB(d){',
' var h=renderReportHeader(getReportMetadata("Financial Health Dashboard","period",{from:d.from,to:d.to}));',
' h+="<style>";',
' h+=".db-wrap{max-width:var(--report-width);margin:0 auto;}";',
' h+=".db-sec{margin:14px 0 6px;font-size:13px;font-weight:700;color:var(--navy);letter-spacing:.4px;text-transform:uppercase;border-bottom:2px solid var(--gold);padding-bottom:5px;}";',
' h+=".db-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:10px;}";',
' h+="@media(max-width:760px){.db-grid{grid-template-columns:1fr;}}";',
' h+=".db-card{background:#fff;border:1px solid #E5E7EB;border-radius:10px;padding:13px 15px;}";',
' h+=".db-k{font-size:10.5px;font-weight:700;color:#6B7280;letter-spacing:.5px;}";',
' h+=".db-v{font-size:22px;font-weight:700;margin:5px 0 3px;display:inline-block;}";',
' h+=".db-v.db-drill{cursor:pointer;border-bottom:1px dashed transparent;}";',
' h+=".db-v.db-drill:hover{color:var(--blue);border-bottom-color:var(--blue);}";',
' h+=".db-n{font-size:11px;color:#6B7280;}";',
' h+=".db-gold{color:var(--gold);}.db-green{color:#1E7E34;}.db-red{color:#B00020;}.db-navy{color:var(--navy);}";',
' h+=".db-badge{display:inline-block;font-size:10.5px;font-weight:700;padding:1px 8px;border-radius:10px;}";',
' h+=".db-bg{color:#1E7E34;border:1px solid #1E7E34;}.db-ba{color:#92600A;border:1px solid #C9A227;}.db-br{color:#B00020;border:1px solid #B00020;}";',
' h+=".db-delta{font-size:10.5px;font-weight:700;margin-left:6px;}.db-up{color:#1E7E34;}.db-down{color:#B00020;}";',
' h+=".db-bar{height:8px;background:#EEF1F5;border-radius:5px;overflow:hidden;margin-top:7px;}.db-bar i{display:block;height:100%;border-radius:5px;}";',
' h+=".db-tbl{width:100%;border-collapse:collapse;font-size:13px;background:#fff;}";',
' h+=".db-tbl th{text-align:left;font-size:10.5px;color:#6B7280;letter-spacing:.4px;padding:8px 10px;border-bottom:2px solid #E5E7EB;}";',
' h+=".db-tbl td{padding:8px 10px;border-bottom:1px solid #F0F1F3;}";',
' h+=".db-num{text-align:right;font-variant-numeric:tabular-nums;}";',
' h+=".db-rank{color:var(--gold);font-weight:700;}";',
' h+=".db-amt{font-weight:700;cursor:pointer;}.db-amt:hover{color:var(--blue);text-decoration:underline;}";',
' h+=".db-vis{height:9px;background:#EEF1F5;border-radius:5px;min-width:110px;}.db-vis i{display:block;height:100%;background:var(--blue);border-radius:5px;}";',
' h+=".db-tbl-wrap{border:1px solid #E5E7EB;border-radius:10px;overflow:hidden;margin-top:10px;background:#fff;}";',
' h+=".db-ins{display:grid;grid-template-columns:165px 1fr;border-bottom:1px solid #F0F1F3;background:#fff;}";',
' h+=".db-ins:last-child{border-bottom:none;}";',
' h+=".db-ins-l{padding:10px 12px;font-size:12px;font-weight:700;color:var(--navy);}.db-ins-r{padding:10px 12px;font-size:12.5px;color:#374151;}";',
' h+=".db-ins-wrap{border:1px solid #E5E7EB;border-radius:10px;overflow:hidden;margin-top:10px;}";',
' h+="</style>";',
' var c=d.cur,p=d.prior;',
' h+="<div class=\\"db-wrap\\">";',
' h+="<div class=\\"db-sec\\">Cash Position</div><div class=\\"db-grid\\">";',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">CASH BALANCE</div><span class=\\"db-v db-gold db-drill\\""+dbDrillAttr_(d.acc.cash,"1900-01-01",d.to)+">"+dbFmt_(c.cashBal)+"</span>"+dbDelta_(c.cashBal,p.cashBal,false)+"<div class=\\"db-n\\">as of "+fmtDateLabel(d.to)+"</div></div>";',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">CASH IN</div><span class=\\"db-v db-green db-drill\\""+dbDrillAttr_(d.acc.cash,d.from,d.to)+">"+dbFmt_(c.cashIn)+"</span>"+dbDelta_(c.cashIn,p.cashIn,false)+"<div class=\\"db-n\\">Total cash received</div></div>";',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">CASH OUT</div><span class=\\"db-v db-red db-drill\\""+dbDrillAttr_(d.acc.cash,d.from,d.to)+">"+dbFmt_(c.cashOut)+"</span>"+dbDelta_(c.cashOut,p.cashOut,true)+"<div class=\\"db-n\\">Total cash paid out</div></div>";',
' h+="</div>";',
' h+="<div class=\\"db-sec\\">Profitability</div><div class=\\"db-grid\\">";',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">REVENUE</div><span class=\\"db-v db-green db-drill\\""+dbDrillAttr_(d.acc.rev,d.from,d.to)+">"+dbFmt_(c.rev)+"</span>"+dbDelta_(c.rev,p.rev,false)+"<div class=\\"db-n\\">Sales + Other Income</div></div>";',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">TOTAL EXPENSES</div><span class=\\"db-v db-red db-drill\\""+dbDrillAttr_(d.acc.exp,d.from,d.to)+">"+dbFmt_(c.exp)+"</span>"+dbDelta_(c.exp,p.exp,true)+"<div class=\\"db-n\\">CoS + OpEx + Other + Income Tax</div></div>";',
' var niCls=c.ni>0?"db-green":c.ni<0?"db-red":"db-navy";var niB=c.ni>0?dbBadge_("Profitable","db-bg"):c.ni<0?dbBadge_("Loss","db-br"):dbBadge_("Break-even","db-ba");',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">NET INCOME</div><span class=\\"db-v "+niCls+"\\">"+dbFmt_(c.ni)+"</span>"+dbDelta_(c.ni,p.ni,false)+"<div class=\\"db-n\\">"+niB+"</div></div>";',
' var gB=d.gpm>=0.3?dbBadge_("Healthy","db-bg"):d.gpm>=0.1?dbBadge_("Monitor","db-ba"):dbBadge_("Low","db-br");',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">GROSS PROFIT MARGIN</div><span class=\\"db-v db-navy\\">"+(d.gpm*100).toFixed(1)+"%</span><div class=\\"db-n\\">"+gB+" Revenue minus CoS, over Revenue</div></div>";',
' var nB=d.npm>=0.2?dbBadge_("Healthy","db-bg"):d.npm>=0.05?dbBadge_("Monitor","db-ba"):dbBadge_("Low","db-br");',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">NET PROFIT MARGIN</div><span class=\\"db-v db-navy\\">"+(d.npm*100).toFixed(1)+"%</span><div class=\\"db-n\\">"+nB+" Net Income over Revenue</div></div>";',
' var rB=d.runway>=6?dbBadge_("Comfortable","db-bg"):d.runway>=3?dbBadge_("Monitor","db-ba"):dbBadge_("Tight","db-br");',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">CASH RUNWAY</div><span class=\\"db-v db-navy\\">"+(d.runway>=999?"No cash out":d.runway.toFixed(1))+"</span><div class=\\"db-n\\">"+(d.runway>=999?"":rB+" "+d.runway.toFixed(1)+" months of cash left")+"</div></div>";',
' h+="</div>";',
' h+="<div class=\\"db-sec\\">Financial Position (A - L = Net Worth)</div><div class=\\"db-grid\\">";',
' var lp=d.assets===0?0:d.liabs/d.assets*100;var np=d.assets===0?0:d.nw/d.assets*100;',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">TOTAL ASSETS</div><span class=\\"db-v db-navy db-drill\\""+dbDrillAttr_(d.acc.assets,"1900-01-01",d.to)+">"+dbFmt_(d.assets)+"</span><div class=\\"db-n\\">What you own as of "+fmtDateLabel(d.to)+"</div><div class=\\"db-bar\\"><i style=\\"width:100%;background:var(--blue)\\"></i></div><div class=\\"db-n\\" style=\\"margin-top:3px\\">100%</div></div>";',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">TOTAL LIABILITIES</div><span class=\\"db-v db-red db-drill\\""+dbDrillAttr_(d.acc.liabs,"1900-01-01",d.to)+">"+dbFmt_(d.liabs)+"</span><div class=\\"db-n\\">What you owe to creditors</div><div class=\\"db-bar\\"><i style=\\"width:"+lp.toFixed(1)+"%;background:#B00020\\"></i></div><div class=\\"db-n\\" style=\\"margin-top:3px\\">"+lp.toFixed(1)+"%</div></div>";',
' h+="<div class=\\"db-card\\"><div class=\\"db-k\\">NET WORTH</div><span class=\\"db-v db-green\\">"+dbFmt_(d.nw)+"</span><div class=\\"db-n\\">Assets minus Liabilities</div><div class=\\"db-bar\\"><i style=\\"width:"+np.toFixed(1)+"%;background:var(--gold)\\"></i></div><div class=\\"db-n\\" style=\\"margin-top:3px\\">"+np.toFixed(1)+"%</div></div>";',
' h+="</div>";',
' h+="<div class=\\"db-sec\\">Where Is The Money Going?</div>";',
' h+="<div class=\\"db-tbl-wrap\\"><table class=\\"db-tbl\\"><thead><tr><th style=\\"width:34px\\">#</th><th>Account</th><th>Visual</th><th class=\\"db-num\\" style=\\"width:80px\\">% of Total</th><th class=\\"db-num\\" style=\\"width:130px\\">Amount</th></tr></thead><tbody>";',
' if(d.top.length===0)h+="<tr><td colspan=\\"5\\" style=\\"color:#6B7280\\">No expenses recorded in this period.</td></tr>";',
' for(var i=0;i<d.top.length;i++){var tx=d.top[i];var pct=c.exp===0?0:tx.amt/c.exp*100;',
' h+="<tr><td class=\\"db-rank\\">"+(i+1)+"</td><td>"+esc(tx.title)+"</td><td><div class=\\"db-vis\\"><i style=\\"width:"+Math.min(100,pct*1.4).toFixed(0)+"%\\"></i></div></td><td class=\\"db-num\\">"+pct.toFixed(1)+"%</td><td class=\\"db-num\\"><span class=\\"db-amt\\""+dbDrillAttr_([tx.title],d.from,d.to)+">"+dbFmt_(tx.amt)+"</span></td></tr>";',
' }',
' h+="</tbody></table></div>";',
' var expRatio=c.rev===0?0:c.exp/c.rev;var topPct=(c.exp===0||d.top.length===0)?0:d.top[0].amt/c.exp;',
' var ins=[];',
' ins.push(["Business Status",c.ni>0?"Net income is "+dbFmt_(c.ni)+". Profitable this period.":c.ni<0?"Net loss of "+dbFmt_(Math.abs(c.ni))+". Review expenses and pricing.":"Break-even this period."]);',
' ins.push(["Cash vs Profit","Cash ("+dbFmt_(c.cashBal)+") differs from net income ("+dbFmt_(c.ni)+"). Normal - cash includes capital, loans, and non-income items."]);',
' ins.push(["Expense Ratio",c.rev===0?"No revenue yet.":"Expenses are "+(expRatio*100).toFixed(0)+"% of revenue. "+(expRatio>0.9?"High - review your costs.":expRatio>0.75?"Monitor.":"Healthy range.")]);',
' ins.push(["Daily Average","Revenue averages "+dbFmt_(c.rev/d.days)+" per day over "+d.days+" days."]);',
' if(d.top.length>0)ins.push(["Top Expense","#1 ("+esc(d.top[0].title)+") is "+(topPct*100).toFixed(0)+"% of total expenses. "+(topPct>=0.5?"High - review pricing or this cost.":"Manageable.")]);',
' ins.push(["Financial Health",d.assets===0?"No assets recorded.":"Net worth is "+np.toFixed(0)+"% of assets. "+(np>=50?"Strong - more owned than owed.":"Watch your debt level.")]);',
' ins.push(["Tip","Net Worth = Assets minus Liabilities. It is what your business truly owns after paying all debts. A growing net worth means your business is building value."]);',
' h+="<div class=\\"db-sec\\">Insights</div><div class=\\"db-ins-wrap\\">";',
' for(var n2=0;n2<ins.length;n2++)h+="<div class=\\"db-ins\\"><div class=\\"db-ins-l\\">"+ins[n2][0]+"</div><div class=\\"db-ins-r\\">"+ins[n2][1]+"</div></div>";',
' h+="</div>";',
' h+="</div>";',
' document.getElementById("report").innerHTML=h;',
'}',
'',
'/* ================================',
' BOOKKEEPING MADE EASY (bme)',
' ================================ */',
'',
'var BME_TSV="";',
'var BME_COA=null;',
'',
'function bmeCoaMap_(){',
' BME_COA={};',
' for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;var t=String(a.title).trim();if(!t)continue;',
' BME_COA[t]={type:String(a.type).trim(),normal:String(a.normal||"").trim(),fsGroup:String(a.fsGroup||"").trim(),code:String(a.code||""),isCash:classifyForCF(a)==="Cash"};}',
'}',
'','',
'var SS_PANEL=null,SS_INP=null,SS_ACTIVE=-1,SS_BOUND=false;',
'function ssSrc_(kind){var out=[];if(kind&&kind.indexOf("coa:")===0){var ck=kind.slice(4);var ca=(typeof coaUIOpts_==="function")?coaUIOpts_(ck):[];for(var ci=0;ci<ca.length;ci++)out.push({v:ca[ci],meta:"",grp:""});return out;}',
' if(kind&&kind.indexOf("set:")===0){var sk=kind.slice(4);var sa=(typeof settingsSrc_==="function")?settingsSrc_(sk):[];for(var si=0;si<sa.length;si++)out.push({v:sa[si],meta:"",grp:""});return out;}',
' if(kind==="yn"){return [{v:"Yes",meta:"",grp:""},{v:"No",meta:"",grp:""}];}',
' if(kind==="fyMonth"){var mo=["January","February","March","April","May","June","July","August","September","October","November","December"];return mo.map(function(m){return {v:m,meta:"",grp:""};});}',
' if(kind==="acctall"){var aa=ssSrc_("acct");aa.unshift({v:"All Accounts",meta:"",grp:""});return aa;}',
' if(kind==="cust"||kind==="supp"){var L=(typeof ntxnContactList_==="function")?ntxnContactList_(kind==="cust"?"customer":"supplier"):[];for(var i=0;i<L.length;i++)out.push({v:L[i].value,meta:L[i].label||"",grp:""});return out;}',
' var G={Revenue:[],Expense:[],Asset:[],Liability:[],Equity:[]};for(var j=0;j<DATA.coa.length;j++){var a=DATA.coa[j];if(a.active&&String(a.active).toUpperCase()==="NO")continue;var t=String(a.title).trim();if(!t)continue;var cf=classifyForCF(a);if(kind==="cash"&&cf!=="Cash")continue;if(kind==="acctnc"&&cf==="Cash")continue;var g=String(a.type).trim();if(!G[g])continue;G[g].push({v:t,meta:String(a.code||""),grp:(g==="Revenue"?"Income":g)});}',
' ["Revenue","Expense","Asset","Liability","Equity"].forEach(function(gg){G[gg].forEach(function(o){out.push(o);});});return out;}',
'function ssClose_(){if(SS_PANEL&&SS_PANEL.parentNode)SS_PANEL.parentNode.removeChild(SS_PANEL);SS_PANEL=null;SS_INP=null;SS_ACTIVE=-1;}',
'function ssPos_(){if(!SS_PANEL||!SS_INP)return;var r=SS_INP.getBoundingClientRect();SS_PANEL.style.left=r.left+"px";SS_PANEL.style.top=(r.bottom+2)+"px";SS_PANEL.style.minWidth=Math.max(r.width,210)+"px";}',
'function ssOpen_(inp){ssClose_();SS_INP=inp;SS_PANEL=document.createElement("div");SS_PANEL.className="ss-panel";document.body.appendChild(SS_PANEL);ssPos_();ssRender_("");try{inp.select();}catch(e){}}',
'function ssRender_(q){if(!SS_PANEL||!SS_INP)return;var kind=SS_INP.getAttribute("data-ss")||"acct";var src=ssSrc_(kind);var ql=String(q||"").toLowerCase();var cur=String(SS_INP.value||"");var html="",lastG="",n=0;SS_PANEL._opts=[];',
' for(var i=0;i<src.length;i++){var o=src[i];if(ql&&o.v.toLowerCase().indexOf(ql)<0&&String(o.meta).toLowerCase().indexOf(ql)<0)continue;if(o.grp&&o.grp!==lastG){html+="<div class=\\"ss-grp\\">"+esc(o.grp)+"</div>";lastG=o.grp;}var sel=(o.v===cur)?" ss-opt-sel":"";var idx=SS_PANEL._opts.length;SS_PANEL._opts.push(o.v);html+="<div class=\\"ss-opt"+sel+"\\" data-i=\\""+idx+"\\" onmousedown=\\"ssPickEv_(event)\\"><span>"+esc(o.v)+"</span>"+(o.meta?"<span class=\\"ss-opt-meta\\">"+esc(o.meta)+"</span>":"")+"</div>";n++;}',
' if(n===0)html="<div class=\\"ss-empty\\">No match</div>";SS_PANEL.innerHTML=html;SS_ACTIVE=-1;}',
'function ssPickEv_(ev){ev.preventDefault();var el=ev.currentTarget;var i=parseInt(el.getAttribute("data-i"),10);if(SS_PANEL&&SS_PANEL._opts&&SS_INP)ssSet_(SS_PANEL._opts[i]);}',
'function ssFire_(el,ty){var ev;try{ev=new Event(ty,{bubbles:true});}catch(e){ev=document.createEvent("Event");ev.initEvent(ty,true,true);}el.dispatchEvent(ev);}',
'function ssSet_(val){var inp=SS_INP;if(!inp)return;inp.value=val;ssClose_();ssFire_(inp,"input");ssFire_(inp,"change");try{inp.focus();}catch(e){}}',
'function ssInit_(){if(SS_BOUND)return;SS_BOUND=true;document.addEventListener("focusin",function(e){var t=e.target;if(t&&t.classList&&t.classList.contains("ss-input"))ssOpen_(t);});document.addEventListener("input",function(e){if(e.target&&e.target===SS_INP)ssRender_(e.target.value);});document.addEventListener("mousedown",function(e){if(SS_PANEL&&SS_INP&&e.target!==SS_INP&&!SS_PANEL.contains(e.target))ssClose_();});',
' document.addEventListener("keydown",function(e){if(!SS_PANEL||!SS_INP)return;var opts=SS_PANEL.querySelectorAll(".ss-opt");if(e.key==="Escape"){ssClose_();return;}if(e.key==="ArrowDown"||e.key==="ArrowUp"){e.preventDefault();if(!opts.length)return;SS_ACTIVE+=(e.key==="ArrowDown"?1:-1);if(SS_ACTIVE<0)SS_ACTIVE=opts.length-1;if(SS_ACTIVE>=opts.length)SS_ACTIVE=0;for(var k=0;k<opts.length;k++)opts[k].classList.toggle("ss-active",k===SS_ACTIVE);opts[SS_ACTIVE].scrollIntoView({block:"nearest"});}else if(e.key==="Enter"){if(SS_ACTIVE>=0&&opts[SS_ACTIVE]){e.preventDefault();ssSet_(SS_PANEL._opts[parseInt(opts[SS_ACTIVE].getAttribute("data-i"),10)]);}}});window.addEventListener("scroll",function(){if(SS_PANEL)ssPos_();},true);window.addEventListener("resize",function(){if(SS_PANEL)ssPos_();});document.addEventListener("keydown",function(e){var t=e.target;if(t&&t.classList&&t.classList.contains("amt-in")&&e.key==="Enter"){e.preventDefault();amtBlur_(t);if(typeof t.oninput==="function")t.oninput.call(t);}});}',
'function amtNum_(s){var v=parseFloat(String(s==null?"":s).replace(/,/g,"").replace(/[^0-9.-]/g,""));return isNaN(v)?0:v;}',
'function amtFmt_(n){return Number(n).toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});}',
'function amtDisp_(s){var x=String(s==null?"":s).trim();return x===""?"":amtFmt_(amtNum_(x));}',
'function amtFocus_(el){if(!el)return;var s=String(el.value||"");el.value=(s.trim()===""?"":String(amtNum_(s)));}',
'function amtBlur_(el){if(!el)return;var s=String(el.value||"").trim();if(s===""){el.value="";return;}var body=s.replace(/^-/,"");if(/[+*/]/.test(body)||/[0-9)]\\s*-\\s*[0-9(]/.test(s)){var safe=s.replace(/,/g,"").replace(/[^0-9.+\\-*/().]/g,"");try{var r=Function("return ("+safe+")")();if(typeof r==="number"&&isFinite(r)){el.value=amtFmt_(r);return;}}catch(e){}}el.value=amtFmt_(amtNum_(s));}',
'function btnLock_(b){if(b&&!b._ecLocked){b._ecLocked=1;b.style.minWidth=b.offsetWidth+"px";}}',
'function btnFlash_(b,word){if(!b||b._ecBusy)return;btnLock_(b);b._ecBusy=1;var orig=b.innerHTML;b.innerHTML="<span class=\\"ec-done\\">"+esc(word)+"</span>";setTimeout(function(){b.innerHTML=orig;b._ecBusy=0;},1400);}',
'function btnBusy_(b){if(!b)return;btnLock_(b);if(b._ecOrig==null)b._ecOrig=b.innerHTML;b._ecBusy=1;b.innerHTML="<span class=\\"ec-dots\\"><i></i><i></i><i></i></span>";b.disabled=true;}',
'function btnDone_(b,word){if(!b)return;b.disabled=false;var orig=b._ecOrig;b.innerHTML="<span class=\\"ec-done\\">"+esc(word)+"</span>";setTimeout(function(){if(orig!=null)b.innerHTML=orig;b._ecOrig=null;b._ecBusy=0;},1400);}',
'function btnReset_(b){if(!b)return;b.disabled=false;if(b._ecOrig!=null){b.innerHTML=b._ecOrig;b._ecOrig=null;}b._ecBusy=0;}',
'function bookAcctPick_(el){if(!el||typeof BOOK_STATE==="undefined")return;BOOK_STATE.glAccount=(el.value==="All Accounts"||!el.value)?"all":el.value;if(typeof rebuildBookContent==="function")rebuildBookContent();}',
'function buildBME(){',
' bmeCoaMap_();',
' document.getElementById("controls").innerHTML="";',
' var h="";',
' h+="<style>";',
' h+=".bme-wrap{max-width:var(--report-width);margin:0 auto;}";',
' h+=".bme-cols{display:grid;grid-template-columns:minmax(0,2.6fr) minmax(205px,1fr);gap:12px;align-items:start;}";',
' h+="@media(max-width:860px){.bme-cols{grid-template-columns:1fr;}}";',
' h+=".bme-card{background:#fff;border:1px solid #E5E7EB;border-radius:10px;padding:13px 15px;margin-bottom:12px;}";',
' h+=".bme-step{font-size:12px;font-weight:700;color:var(--navy);letter-spacing:.4px;text-transform:uppercase;border-bottom:2px solid var(--gold);padding-bottom:5px;margin-bottom:10px;}";',
' h+=".bme-lbl{font-size:10px;font-weight:700;color:#999;letter-spacing:.5px;text-transform:uppercase;display:block;margin-bottom:3px;}";',
' h+=".bme-in{padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);background:#fff;font-family:inherit;width:100%;box-sizing:border-box;}";',
' h+=".bme-in:hover{border-color:var(--gold);}";',
' h+=".bme-in:focus{border-color:var(--gold);outline:none;box-shadow:0 0 0 2px rgba(201,162,39,.22);}";',
' h+=".bme-tbl{width:100%;border-collapse:collapse;font-size:12.5px;}";',
' h+=".bme-tbl th{text-align:left;font-size:10px;color:#6B7280;letter-spacing:.4px;padding:5px 6px;border-bottom:2px solid #E5E7EB;}";',
' h+=".bme-tbl td{padding:4px 6px;border-bottom:1px solid #F0F1F3;vertical-align:middle;}";',
' h+=".bme-auto{font-size:11.5px;font-weight:700;color:var(--blue);white-space:nowrap;}";',
' h+=".bme-bal{margin-top:10px;font-size:13px;font-weight:700;padding:8px 12px;border-radius:8px;border:1px solid #E5E7EB;}";',
' h+=".bme-bal-ok{color:#1E7E34;border-color:#1E7E34;}.bme-bal-no{color:#92600A;border-color:#C9A227;}";',
' h+=".bme-rules td,.bme-rules th{font-size:11px;padding:3px 5px;}";',
' h+=".bme-prev-tbl td.bme-desc,.bme-prev-tbl th.bme-desc{white-space:normal;max-width:240px;min-width:150px;word-break:break-word;}";',
' h+=".bme-route{font-size:14px;font-weight:700;color:var(--navy);}";',
' h+=".bme-route-note{font-size:12px;color:#374151;margin-top:4px;}";',
' h+=".bme-prev-tbl{border-collapse:collapse;font-size:11.5px;white-space:nowrap;}";',
' h+=".bme-prev-tbl th{font-size:9.5px;color:#6B7280;border:1px solid #E5E7EB;padding:4px 7px;text-align:left;max-width:130px;overflow:hidden;text-overflow:ellipsis;}";',
' h+=".bme-prev-tbl td{border:1px solid #E5E7EB;padding:4px 7px;color:#9CA3AF;}";',
' h+=".bme-prev-tbl td.bme-fill{color:var(--navy);font-weight:700;background:#FFF8E6;}";',
' h+=".bme-btn{padding:6px 14px;border:1px solid #D0D0D0;border-radius:6px;background:#fff;color:var(--navy);font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit;}";',
' h+=".bme-btn-go{background:var(--navy);color:#fff;border-color:var(--navy);}";',
' h+=".bme-btn-go:hover{background:#152844;border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.45);}";',
' h+=".bme-btn:hover:not(:disabled){border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.28);}";',
' h+=".bme-warn{font-size:12px;color:#92600A;border:1px solid #C9A227;border-radius:8px;padding:8px 12px;margin-top:8px;}";',
' h+=".bme-tip{font-size:12px;color:var(--blue);margin-top:8px;}";',
' h+=".bme-quote{text-align:center;font-size:12px;font-style:italic;color:#6B7280;margin:16px 0 4px;}";',
' h+="</style>";',
' h+="<div class=\\"bme-wrap\\"><div class=\\"bme-cols\\"><div>";',
' h+="<div class=\\"bme-card\\"><div class=\\"bme-step\\">Step 1 - What happened?</div>";',
' h+="<div style=\\"display:grid;grid-template-columns:2fr 1fr;gap:10px\\">";',
' h+="<div><span class=\\"bme-lbl\\">What (description)</span><input type=\\"text\\" class=\\"bme-in\\" id=\\"bmeDesc\\" value=\\""+esc(BME_STATE.desc)+"\\" oninput=\\"bmeSync()\\" placeholder=\\"Example: Paid January rent\\"></div>";',
' h+="<div><span class=\\"bme-lbl\\">When (date)</span><input type=\\"date\\" class=\\"bme-in\\" id=\\"bmeDate\\" value=\\""+esc(BME_STATE.date)+"\\" onchange=\\"bmeSync()\\"></div>";',
' h+="</div></div>";',
' h+="<div class=\\"bme-card\\"><div class=\\"bme-step\\">Steps 2-4 - Which accounts? Increase or decrease? How much?</div>";',
' h+="<table class=\\"bme-tbl\\"><thead><tr><th style=\\"width:18px\\">#</th><th>Pick account</th><th style=\\"width:72px\\">Element</th><th style=\\"width:92px\\">Increase or Decrease?</th><th style=\\"width:56px\\">DR/CR</th><th style=\\"width:122px\\">Amount</th><th style=\\"width:30px\\"></th></tr></thead><tbody>";',
' for(var i=0;i<BME_STATE.legs.length;i++){var lg=BME_STATE.legs[i];',
' h+="<tr><td>"+(i+1)+"</td>";',
' h+="<td><input type=\\"text\\" class=\\"bme-in ss-input\\" data-ss=\\"acct\\" id=\\"bmeA"+i+"\\" value=\\""+esc(lg.a)+"\\" oninput=\\"bmeSync()\\" placeholder=\\"Type to search...\\" autocomplete=\\"off\\"></td>";',
' h+="<td><span class=\\"bme-auto\\" id=\\"bmeEl"+i+"\\"></span></td>";',
' h+="<td><select class=\\"bme-in\\" id=\\"bmeE"+i+"\\" onchange=\\"bmeSync()\\"><option value=\\"\\"></option><option"+(lg.e==="Increase"?" selected":"")+">Increase</option><option"+(lg.e==="Decrease"?" selected":"")+">Decrease</option></select></td>";',
' h+="<td><span class=\\"bme-auto\\" id=\\"bmeDC"+i+"\\"></span></td>";',
' h+="<td><input type=\\"text\\" inputmode=\\"decimal\\" class=\\"bme-in amt-in\\" id=\\"bmeM"+i+"\\" value=\\""+esc(amtDisp_(lg.m))+"\\" oninput=\\"bmeSync()\\" onfocus=\\"amtFocus_(this)\\" onblur=\\"amtBlur_(this);bmeSync()\\" placeholder=\\"0.00\\"></td>";',
' h+="<td><button class=\\"bme-rowclr\\" onclick=\\"bmeRowX_("+i+")\\" title=\\"Clear or remove this line\\">"+String.fromCharCode(215)+"</button></td></tr>";',
' }',
' h+="</tbody></table>";',
' h+="<div id=\\"bmeBal\\"></div>";',
' h+="<div style=\\"margin-top:10px;display:flex;gap:8px;flex-wrap:wrap\\"><button class=\\"bme-btn\\" onclick=\\"bmeAddRow_()\\">+ Add row</button><button class=\\"bme-btn\\" id=\\"bmeClearBtn\\" onclick=\\"bmeClear()\\">Clear entry</button></div>";',
' h+="</div>";',
' h+="<div class=\\"bme-card\\"><div class=\\"bme-step\\">Step 5 - Where to record + copy</div><div id=\\"bmeOut\\"></div></div>";',
' h+="</div><div>";',
' h+="<div class=\\"bme-card\\"><div class=\\"bme-step\\">Debit and Credit Rules</div>";',
' h+="<table class=\\"bme-tbl bme-rules\\"><thead><tr><th>Account Type</th><th>Increase</th><th>Decrease</th></tr></thead><tbody>";',
' h+="<tr><td>Asset</td><td><b>DEBIT</b></td><td>CREDIT</td></tr>";',
' h+="<tr><td>Expense</td><td><b>DEBIT</b></td><td>CREDIT</td></tr>";',
' h+="<tr><td>Liability</td><td><b>CREDIT</b></td><td>DEBIT</td></tr>";',
' h+="<tr><td>Equity</td><td><b>CREDIT</b></td><td>DEBIT</td></tr>";',
' h+="<tr><td>Income</td><td><b>CREDIT</b></td><td>DEBIT</td></tr>";',
' h+="</tbody></table>";',
' h+="<div style=\\"font-size:11px;color:#6B7280;margin-top:6px\\">Contra account? Check its Normal Balance in the Chart of Accounts.</div></div>";',
' h+="<div class=\\"bme-card\\"><div class=\\"bme-step\\">Where to Record?</div>";',
' h+="<table class=\\"bme-tbl bme-rules\\"><tbody>";',
' h+="<tr><td>Cash in</td><td><b>CRB</b></td></tr>";',
' h+="<tr><td>Cash out</td><td><b>CDB</b></td></tr>";',
' h+="<tr><td>Credit sale (no cash yet)</td><td><b>SJ</b></td></tr>";',
' h+="<tr><td>Credit purchase (no cash yet)</td><td><b>PJ</b></td></tr>";',
' h+="<tr><td>Everything else</td><td><b>GJ</b></td></tr>";',
' h+="</tbody></table></div>";',
' h+="</div></div>";',
' h+="<div class=\\"bme-quote\\">"+String.fromCharCode(8220)+"Hindi mo kailangang maging accountant. Kailangan mo lang ng tamang sistema."+String.fromCharCode(8221)+" - EC TAXPH</div>";',
' h+="<textarea id=\\"bmeTSVbox\\" style=\\"position:absolute;left:-9999px;top:0\\"></textarea>";',
' h+="</div>";',
' document.getElementById("report").innerHTML=h;',
' ssInit_();',
' updateBME();',
'}',
'',
'function bmeSync(){',
' BME_STATE.desc=document.getElementById("bmeDesc").value;',
' BME_STATE.date=document.getElementById("bmeDate").value;',
' for(var i=0;i<BME_STATE.legs.length;i++){var aE=document.getElementById("bmeA"+i),eE=document.getElementById("bmeE"+i),mE=document.getElementById("bmeM"+i);if(aE)BME_STATE.legs[i]={a:aE.value,e:eE?eE.value:"",m:mE?mE.value:""};}',
' for(var b=0;b<BME_STATE.legs.length;b++){var bAi=document.getElementById("bmeA"+b);if(bAi){bAi.style.borderColor=(bAi.value&&!BME_COA[bAi.value])?"#C9A227":"";}}',
' updateBME();',
'}',
'function bmeClear(){BME_STATE={desc:"",date:"",legs:[{a:"",e:"",m:""},{a:"",e:"",m:""},{a:"",e:"",m:""},{a:"",e:"",m:""},{a:"",e:"",m:""},{a:"",e:"",m:""}]};buildBME();var cb=document.getElementById("bmeClearBtn");if(cb)btnFlash_(cb,"Cleared");}',
'function bmeAddRow_(){bmeSync();BME_STATE.legs.push({a:"",e:"",m:""});buildBME();}',
'function bmeRowX_(i){bmeSync();if(BME_STATE.legs.length>6&&i>=6){BME_STATE.legs.splice(i,1);}else{BME_STATE.legs[i]={a:"",e:"",m:""};}buildBME();}',
'',
'function bmeLegs_(){',
' var out=[];',
' for(var i=0;i<BME_STATE.legs.length;i++){var lg=BME_STATE.legs[i];if(!lg.a)continue;var c=BME_COA[lg.a];if(!c)continue;',
' var side="";if(lg.e){side=(c.normal==="Debit")?(lg.e==="Increase"?"DEBIT":"CREDIT"):(lg.e==="Increase"?"CREDIT":"DEBIT");}',
' out.push({i:i,title:lg.a,type:c.type,normal:c.normal,isCash:c.isCash,side:side,amt:amtNum_(lg.m)});}',
' return out;',
'}',
'',
'function bmeRoute_(legs){',
' var cashDR=false,cashCR=false,arDR=false,apCR=false,revCR=false;',
' for(var i=0;i<legs.length;i++){var l=legs[i];if(!l.side||l.amt<=0)continue;',
' if(l.isCash&&l.side==="DEBIT")cashDR=true;',
' if(l.isCash&&l.side==="CREDIT")cashCR=true;',
' if(/^accounts receivable/i.test(l.title)&&l.side==="DEBIT")arDR=true;',
' if(/^accounts payable/i.test(l.title)&&l.side==="CREDIT")apCR=true;',
' if(l.type==="Revenue"&&l.side==="CREDIT")revCR=true;}',
' if(cashDR)return"crb";if(cashCR)return"cdb";',
' if(arDR&&revCR)return"sj";if(apCR)return"pj";',
' return"gj";',
'}',
'',
'function updateBME(){',
' var legs=bmeLegs_();',
' for(var i=0;i<BME_STATE.legs.length;i++){var el=document.getElementById("bmeEl"+i),dc=document.getElementById("bmeDC"+i);if(!el)continue;',
' var lg=BME_STATE.legs[i],c=lg.a?BME_COA[lg.a]:null;',
' el.textContent=c?(c.type==="Revenue"?"Income":c.type):"";',
' var found=null;for(var x=0;x<legs.length;x++)if(legs[x].i===i)found=legs[x];',
' dc.textContent=(found&&found.side)?found.side:"";}',
' var dr=0,cr=0,used=0;',
' for(var j=0;j<legs.length;j++){var l=legs[j];if(!l.side||l.amt<=0)continue;used++;if(l.side==="DEBIT")dr+=l.amt;else cr+=l.amt;}',
' dr=rnd(dr);cr=rnd(cr);',
' var balanced=used>=2&&dr>0&&Math.abs(dr-cr)<0.005;',
' var bal=document.getElementById("bmeBal");',
' if(used===0)bal.innerHTML="";',
' else if(balanced)bal.innerHTML="<div class=\\"bme-bal bme-bal-ok\\">BALANCED - DR "+dbFmt_(dr)+" = CR "+dbFmt_(cr)+"</div>";',
' else bal.innerHTML="<div class=\\"bme-bal bme-bal-no\\">Not balanced yet - DR "+dbFmt_(dr)+" vs CR "+dbFmt_(cr)+(used<2?" (choose at least 2 accounts)":"")+"</div>";',
' var out=document.getElementById("bmeOut");',
' if(!balanced){out.innerHTML="<div style=\\"font-size:12.5px;color:#6B7280\\">Complete a balanced entry above (DR = CR) to see where to record it and get the copy-paste row.</div>";BME_TSV="";return;}',
' var route=bmeRoute_(legs);',
' var names={crb:"Cash Receipts Book (CRB)",cdb:"Cash Disbursements Book (CDB)",sj:"Sales Journal (SJ)",pj:"Purchase Journal (PJ)",gj:"General Journal (GJ)"};',
' var why={crb:"Cash came IN - a cash account was debited.",cdb:"Cash went OUT - a cash account was credited.",sj:"Sale on account - no cash yet.",pj:"Purchase on account - no cash yet.",gj:"No cash movement and not a credit sale or purchase."};',
' var h="<div class=\\"bme-route\\">Record in: "+names[route]+"</div><div class=\\"bme-route-note\\">"+why[route]+"</div>";',
' h+=bmePreview_(route,legs);',
' out.innerHTML=h;',
'}',
'',
'function bmeMetaCols_(bm){',
' var std={status:2,entryId:3,date:4,ref:5,tin:6,branchCode:7,regName:8,lastName:9,firstName:10,middleName:11,addr1:12,addr2:13,desc:14};',
' var lbl={status:"Status",entryId:"Entry ID",date:"Date",ref:"Reference #",tin:"TIN",branchCode:"Branch Code",regName:"Registered Name",lastName:"Last Name",firstName:"First Name",middleName:"Middle Name",addr1:"Address 1",addr2:"Address 2",desc:"Description"};',
' var src=(bm&&bm.metaIdx&&bm.metaIdx.date)?bm.metaIdx:std;',
' var cols=[];for(var f in std){var col=src[f]||std[f];cols.push({field:f,label:lbl[f],col:col});}',
' return cols;',
'}',
'',
'function bmePreview_(route,legs){',
' var bms=DATA.bookMeta||null;',
' var h="";',
' if(route!=="gj"){',
' var bm=bms?bms[route]:null;',
' if(!bm){h+="<div class=\\"bme-warn\\">Book layout not available - click Generate Report again from the workbook to refresh, or use the General Journal version below.</div>";h+=bmeGJPrev_(legs,bms);return h;}',
' var matched=[],unmatched=[];',
' for(var i=0;i<legs.length;i++){var l=legs[i];if(!l.side||l.amt<=0)continue;',
' var hit=-1,fallback=-1,want=l.side==="DEBIT"?"DR":"CR";',
' for(var c=0;c<bm.headers.length;c++){if(String(bm.headers[c]).trim()!==l.title)continue;if(fallback<0)fallback=c;if(String(bm.drCr[c]).trim().toUpperCase()===want){hit=c;break;}}',
' if(hit>=0)matched.push({leg:l,colIdx:hit});',
' else if(fallback>=0)unmatched.push({leg:l,reason:"column exists but on the "+String(bm.drCr[fallback])+" side"});',
' else unmatched.push({leg:l,reason:"no column in this book"});}',
' var metaCols=bmeMetaCols_(bm);',
' var amtIdx=bm.amtIdx&&bm.amtIdx.length===bm.headers.length?bm.amtIdx:bm.headers.map(function(x,i2){return 15+i2;});',
' var maxCol=2;metaCols.forEach(function(m){if(m.col>maxCol)maxCol=m.col;});amtIdx.forEach(function(ci){if(ci>maxCol)maxCol=ci;});',
' var vals={};',
' metaCols.forEach(function(m){var v="";if(m.field==="status")v="Posted";else if(m.field==="entryId")v=bm.nextId;else if(m.field==="date")v=BME_STATE.date||"";else if(m.field==="desc")v=BME_STATE.desc||"";vals[m.col]={v:v,label:m.label};});',
' matched.forEach(function(mm){var col=amtIdx[mm.colIdx];vals[col]={v:String(mm.leg.amt),label:bm.headers[mm.colIdx]+" ("+bm.drCr[mm.colIdx]+")"};});',
' unmatched.forEach(function(um){maxCol++;vals[maxCol]={v:String(um.leg.amt),label:um.leg.title+" ("+(um.leg.side==="DEBIT"?"DR":"CR")+")",isNew:true};});',
' var hdrCells="",dataCells="",arr=[];',
' var colInfo={},descCol=-1;metaCols.forEach(function(m){colInfo[m.col]=m.label;if(m.field==="desc")descCol=m.col;});',
' for(var a2=0;a2<bm.headers.length;a2++)colInfo[amtIdx[a2]]=bm.headers[a2]+" ("+bm.drCr[a2]+")";',
' for(var col2=2;col2<=maxCol;col2++){var cell=vals[col2];var info=colInfo[col2]||(cell&&cell.label)||"";var dcc=(col2===descCol)?" bme-desc":"";var isNew=!!(cell&&cell.isNew);',
' if(cell&&cell.v!==""&&cell.v!=null){hdrCells+="<th"+(dcc?" class=\\"bme-desc\\"":"")+">"+esc(info)+(isNew?" (new column)":"")+"</th>";dataCells+="<td class=\\"bme-fill"+dcc+"\\""+(isNew?" style=\\"background:#FEF3C7\\"":"")+">"+esc(cell.v)+"</td>";}',
' arr.push(cell?cell.v:"");}',
' BME_TSV=arr.join(String.fromCharCode(9));',
' h+="<div style=\\"overflow-x:auto;margin-top:10px\\"><table class=\\"bme-prev-tbl\\"><thead><tr>"+hdrCells+"</tr></thead><tbody><tr>"+dataCells+"</tr></tbody></table></div>";',
' if(unmatched.length>0){var un="";for(var u=0;u<unmatched.length;u++)un+=(u?", ":"")+esc(unmatched[u].leg.title);h+="<div class=\\"bme-warn\\">No column yet in the "+esc(bm.label)+" for: <b>"+un+"</b>. Add it as a new last column in row 6 of the book - or click Record this in "+route.toUpperCase()+" below and it will be added automatically.</div>";}',
' else h+="<div style=\\"font-size:11px;color:#9CA3AF;margin-top:4px\\">Showing only the columns that get a value - all other columns in the book stay blank. Copy row still pastes in the correct positions.</div>";',
' var btMap={crb:"receive",cdb:"spend",sj:"invoice",pj:"bill"};var bt=btMap[route]||"journal";',
' h+="<div style=\\"margin-top:10px;display:flex;gap:10px;align-items:center;flex-wrap:wrap\\"><button class=\\"bme-btn\\" onclick=\\"bmeCopy(this)\\">Copy row</button>";',
' h+="<button class=\\"bme-btn bme-btn-go\\" onclick=\\"ntxnFromBME('"+bt+"')\\">Record this in "+route.toUpperCase()+" →</button></div>";',
' h+="<div class=\\"bme-tip\\" style=\\"margin-top:6px\\">Opens the New Transaction form, pre-filled in the "+route.toUpperCase()+" with these accounts and amounts - just complete what is missing (party, reference) and Save to Books.</div>";',
' h+="<div class=\\"bme-tip\\">Click Copy row, then in Google Sheets click cell <b>B"+bm.nextRow+"</b> of the "+esc(bm.label)+" and paste (Ctrl+V). Suggested Entry ID: <b>"+esc(bm.nextId)+"</b>.</div>";',
' h+=bmeVatTip_(route);',
' return h;}',
' h+=bmeGJPrev_(legs,bms);return h;',
'}',
'',
'function bmeGJPrev_(legs,bms){',
' var gm=(bms&&bms.gj)?bms.gj:{label:"General Journal",nextRow:7,nextId:"JE-0001"};',
' var rows=[],lines=[];',
' for(var i=0;i<legs.length;i++){var l=legs[i];if(!l.side||l.amt<=0)continue;',
' var dr=l.side==="DEBIT"?String(l.amt):"";var cr=l.side==="CREDIT"?String(l.amt):"";',
' var arr=[gm.nextId,BME_STATE.date||"","",BME_STATE.desc||"",l.title,dr,cr,"Posted"];',
' rows.push(arr);lines.push(arr.join(String.fromCharCode(9)));}',
' BME_TSV=lines.join(String.fromCharCode(10));',
' var h="<div style=\\"overflow-x:auto;margin-top:10px\\"><table class=\\"bme-prev-tbl\\"><thead><tr><th>Entry ID</th><th>Date</th><th>Reference No</th><th class=\\"bme-desc\\">Description</th><th>Account Title</th><th>Debit</th><th>Credit</th><th>Status</th></tr></thead><tbody>";',
' for(var r=0;r<rows.length;r++){h+="<tr>";for(var c=0;c<8;c++){var v=rows[r][c];var dcl=(c===3?" bme-desc":"")+(v?" bme-fill":"");h+="<td"+(dcl?" class=\\""+dcl.trim()+"\\"":"")+">"+esc(v)+"</td>";}h+="</tr>";}',
' h+="</tbody></table></div>";',
' h+="<div style=\\"margin-top:10px;display:flex;gap:10px;align-items:center;flex-wrap:wrap\\"><button class=\\"bme-btn\\" onclick=\\"bmeCopy(this)\\">Copy "+rows.length+" row(s)</button>";',
' h+="<button class=\\"bme-btn bme-btn-go\\" onclick=\\"ntxnFromBME('journal')\\">Record this in GJ →</button></div>";',
' h+="<div class=\\"bme-tip\\">Click Copy, then in Google Sheets click cell <b>B"+gm.nextRow+"</b> of the General Journal and paste (Ctrl+V). The SAME Entry ID groups all lines of one entry.</div>";',
' return h;',
'}',
'',
'function bmeVatTip_(route){',
' var rt=String((DATA.settings&&(DATA.settings.regType||DATA.settings.registrationType))||"").toUpperCase();',
' if(!rt||rt.indexOf("NON")>=0)return"";',
' var legs=bmeLegs_(),hasOut=false,hasIn=false;',
' for(var i=0;i<legs.length;i++){if(/output vat/i.test(legs[i].title))hasOut=true;if(/input vat/i.test(legs[i].title))hasIn=true;}',
' if((route==="crb"||route==="sj")&&!hasOut)return"<div class=\\"bme-warn\\">VAT Registered: if this is a VATable sale, split it - net amount in the sales column + 12% in the Output VAT column. See the VAT Setup Guide.</div>";',
' if((route==="cdb"||route==="pj")&&!hasIn)return"<div class=\\"bme-warn\\">VAT Registered: if this is a VATable purchase, split it - net amount in the purchase column + 12% in the Input VAT column. See the VAT Setup Guide.</div>";',
' return"";',
'}',
'',
'function bmeCopy(btn){',
' var done=function(){btnFlash_(btn,"Copied!");};',
' if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(BME_TSV).then(done,function(){bmeCopyFallback_();done();});}',
' else{bmeCopyFallback_();done();}',
'}',
'function bmeCopyFallback_(){var ta=document.getElementById("bmeTSVbox");ta.value=BME_TSV;ta.select();try{document.execCommand("copy");}catch(e){}}',
'',
'/* ================================',
' 1702Q - QUARTERLY INCOME TAX RETURN (CORP) v2018C',
' Sched 2 formulas verified vs eBIRForms saved XML. Sched 2 Tax1-7 = THIS quarter;',
' Tax8 = prior quarters NTI; Tax9 = cumulative. MCIT = 2% x cumulative gross income,',
' gated by Date of Incorporation (4th taxable year). Rate auto: NTI<=5M AND assets<=100M -> 20%.',
' Part II line labels 14-25 follow the verified flow; exact label map to re-check in B9.',
' ================================ */',
'',
'var Q1702C_STATE={viewMode:"Q1Q2Q3",ded:"itemized",rateMode:"auto",amended:false,taxYear:null,perQ:{1:{c1:0,c2:0,c3:0,c4:0,c5:0,c6:0,c7:0,p15:0,sur:0,intr:0,comp:0},2:{c1:0,c2:0,c3:0,c4:0,c5:0,c6:0,c7:0,p15:0,sur:0,intr:0,comp:0},3:{c1:0,c2:0,c3:0,c4:0,c5:0,c6:0,c7:0,p15:0,sur:0,intr:0,comp:0}}};',
'',
'function q1702Init_(){if(Q1702C_STATE.taxYear)return;var tm="";var je=DATA.jeRows||[];for(var i=0;i<je.length;i++){var d=je[i].date;if(d&&(!tm||d>tm))tm=d;}Q1702C_STATE.taxYear=(tm&&tm.length>=4)?parseInt(tm.substring(0,4)):new Date().getFullYear();}',
'function q1702Range_(q){var y=Q1702C_STATE.taxYear;var sM=(q-1)*3+1,eM=sM+2;return{from:y+"-"+pad(sM)+"-01",to:y+"-"+pad(eM)+"-"+pad(ld(eM,y))};}',
'',
'function compute1702Q_(q){',
' q1702Init_();',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var y=Q1702C_STATE.taxYear;',
' var rThis=q1702Range_(q);',
' function slice(rg){var isD=computeIS(DATA.jeRows,DATA.coa,rg.from,rg.to,fyM);var gross=isD.totalRevenue||0;var cos=isD.totalCOS||0;var oth=isD.totalOI||0;var gi=gross-cos;var tgi=gi+oth;',
' var ded=(Q1702C_STATE.ded==="osd")?rnd(tgi*0.40):rnd((isD.totalOpEx||0)+(isD.totalOExp||0));',
' return{gross:rnd(gross),cos:rnd(cos),gi:rnd(gi),oth:rnd(oth),tgi:rnd(tgi),ded:ded,nti:rnd(tgi-ded)};}',
' var cur=slice(rThis);',
// Sched 3 per-quarter gross income components + prior quarters NTI
' var giQ=[0,0,0];var prevNTI=0;',
' for(var pq=1;pq<=3;pq++){if(pq>q)continue;var sl=(pq===q)?cur:slice(q1702Range_(pq));giQ[pq-1]=sl.tgi;if(pq<q)prevNTI+=sl.nti;}',
' prevNTI=rnd(prevNTI);',
' var t9=rnd(cur.nti+prevNTI);',
' var cumTGI=rnd(giQ[0]+giQ[1]+giQ[2]);',
// Total assets per books as of quarter end (Asset type, net of contra), land checked only if over the cap
' var typeMap={};var assets=0,land=0;',
' for(var c1=0;c1<DATA.coa.length;c1++){var a=DATA.coa[c1];if(a.active&&String(a.active).toUpperCase()==="NO")continue;typeMap[String(a.title).trim()]={tp:String(a.type).trim(),isLand:/^land\\b/i.test(String(a.title).trim())};}',
' for(var j=0;j<DATA.jeRows.length;j++){var je=DATA.jeRows[j];var d=je.date||"";if(!d||d>rThis.to)continue;var tm2=typeMap[je.account];if(!tm2||tm2.tp!=="Asset")continue;var nv=(parseFloat(je.debit)||0)-(parseFloat(je.credit)||0);assets+=nv;if(tm2.isLand)land+=nv;}',
' assets=rnd(assets);land=rnd(land);',
' var taTest=assets,landNote=false;',
' if(assets>100000000&&land>0){taTest=rnd(assets-land);landNote=true;}',
' var ntiPass=t9<=5000000,taPass=taTest<=100000000;',
' var autoRate=(ntiPass&&taPass)?20:25;',
' var rate=Q1702C_STATE.rateMode==="auto"?autoRate:parseInt(Q1702C_STATE.rateMode);',
' var t11=rnd(t9*rate/100);',
// MCIT: 2% of cumulative total gross income, from the 4th taxable year after incorporation
' var incD=String(DATA.settings.dateOfIncorporation||"").trim();',
' var incYear=incD.length>=4?parseInt(incD.substring(0,4)):0;',
' var mcitApplies=incYear>0?(y>=incYear+4):true;',
' var mcitNoDate=incYear===0;',
' var m3=mcitApplies?rnd(cumTGI*0.02):0;',
// Sched 2 Item 13 = higher of Item 11 (RCIT) or Item 12 (MCIT) -> Part II Item 14
' var sch13=Math.max(t11,m3);var useM=m3>t11;',
' var st=Q1702C_STATE.perQ[q];',
' function q72n_(x){return parseFloat(x)||0;}',
' var amended=!!Q1702C_STATE.amended;',
// Sched 4 (form items 1-7): items 2/3/4 N/A in Q1; item 6 amended-only
' var e2=q===1?0:q72n_(st.c2),e3=q===1?0:q72n_(st.c3),e4=q===1?0:q72n_(st.c4);',
' var e6=amended?q72n_(st.c6):0;',
' var credits=rnd(q72n_(st.c1)+e2+e3+e4+q72n_(st.c5)+e6+q72n_(st.c7));',
// Part II chain (form items 14-25): 15 = unexpired excess of prior year MCIT, regular-rate basis only
' var p15=useM?0:q72n_(st.p15);',
' var p16=rnd(sch13-p15);',
' var p17=0;',
' var p18=rnd(p16+p17);',
' var p20=rnd(p18-credits);',
' var p24=rnd(q72n_(st.sur)+q72n_(st.intr)+q72n_(st.comp));',
' var p25=rnd(p20+p24);',
' return{q:q,y:y,from:rThis.from,to:rThis.to,cur:cur,prevNTI:prevNTI,t9:t9,giQ:giQ,cumTGI:cumTGI,assets:assets,taTest:taTest,landNote:landNote,ntiPass:ntiPass,taPass:taPass,autoRate:autoRate,rate:rate,t11:t11,mcitApplies:mcitApplies,mcitNoDate:mcitNoDate,incYear:incYear,m3:m3,useM:useM,sch13:sch13,credits:credits,p15:p15,p16:p16,p17:p17,p18:p18,p20:p20,p24:p24,p25:p25};',
'}',
'',
'function f72fmt(n){var a=Math.abs(n);var s=a.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});return n<0?"("+s+")":s;}',
'function q1702Accts_(kind){var out=[];for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;var fg=String(a.fsGroup||"").trim();',
' if(kind==="rev"&&fg==="Revenue")out.push(a.title);',
' else if(kind==="cos"&&fg==="Cost of Sales")out.push(a.title);',
' else if(kind==="oth"&&fg==="Other Income")out.push(a.title);',
' else if(kind==="ded"&&(fg==="Operating Expenses"||fg==="Other Expenses"))out.push(a.title);}',
' return out;}',
'',
'function build1702QC(){',
' q1702Init_();',
' var s=Q1702C_STATE;var yrNow=new Date().getFullYear();',
' var h="<div class=\\"ctrl-bar\\">";',
' h+="<div class=\\"ctrl-group\\"><label>2. Year Ended</label><input type=\\"number\\" value=\\""+s.taxYear+"\\" min=\\"2018\\" max=\\""+(yrNow+1)+"\\" onchange=\\"Q1702C_STATE.taxYear=parseInt(this.value)||"+yrNow+";render1702QC_()\\" style=\\"width:80px\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>3. Quarter</label>";',
' h+="<div style=\\"display:flex;gap:12px;padding:5px 8px;align-items:center;border:1px solid #D0D0D0;border-radius:4px;\\">";',
' ["Q1","Q2","Q3"].forEach(function(q){',
' var checked=s.viewMode.indexOf(q)>=0;',
' h+="<label style=\\"display:flex;align-items:center;gap:4px;font-size:13px;color:var(--navy);cursor:pointer;font-weight:500;\\"><input type=\\"checkbox\\" "+(checked?"checked":"")+" onchange=\\"toggle1702QQuarter(\'"+q+"\',this.checked)\\">"+q+"</label>";',
' });',
' h+="</div></div>";',
' h+="<div class=\\"ctrl-group\\"><label>4. Amended?</label><select onchange=\\"Q1702C_STATE.amended=this.value===\'yes\';render1702QC_()\\">";',
' h+="<option value=\\"no\\""+(!s.amended?" selected":"")+">No</option>";',
' h+="<option value=\\"yes\\""+(s.amended?" selected":"")+">Yes</option>";',
' h+="</select></div>";',
' h+="<div style=\\"flex-basis:100%;height:0;\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>12. Deductions</label><select onchange=\\"Q1702C_STATE.ded=this.value;render1702QC_()\\">";',
' h+="<option value=\\"itemized\\""+(s.ded==="itemized"?" selected":"")+">Itemized</option>";',
' h+="<option value=\\"osd\\""+(s.ded==="osd"?" selected":"")+">OSD (40%)</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Sch2-10. Rate</label><select onchange=\\"Q1702C_STATE.rateMode=this.value;render1702QC_()\\">";',
' h+="<option value=\\"auto\\""+(s.rateMode==="auto"?" selected":"")+">Auto (recommended)</option>";',
' h+="<option value=\\"25\\""+(s.rateMode==="25"?" selected":"")+">Force 25%</option>";',
' h+="<option value=\\"20\\""+(s.rateMode==="20"?" selected":"")+">Force 20%</option>";',
' h+="</select></div>";',
' h+="</div>";',
' document.getElementById("controls").innerHTML=h;',
' render1702QC_();',
'}',
'function toggle1702QQuarter(q,checked){var order=["Q1","Q2","Q3"];var cur=order.filter(function(x){return Q1702C_STATE.viewMode.indexOf(x)>=0;});var idx=cur.indexOf(q);if(checked&&idx<0)cur.push(q);if(!checked&&idx>=0)cur.splice(idx,1);if(cur.length===0)cur=[q];Q1702C_STATE.viewMode=order.filter(function(x){return cur.indexOf(x)>=0;}).join("");build1702QC();}',
'/* Tax Line-based account matching (same logic family as drill1701Q) with fsGroup fallback. */',
'function drill1702Q(qn,kind){',
' var r=q1702Range_(qn);var accts=[];',
' for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];if(!a||!a.title)continue;if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var tl=String(a.taxLine||"").trim();var fg=String(a.fsGroup||"").trim();var match=false;',
' if(kind==="sales")match=(tl==="Sales / Revenues / Receipts / Fees")||(fg==="Revenue");',
' else if(kind==="cogs")match=(tl==="Cost of Sales / Services")||(fg==="Cost of Sales");',
' else if(kind==="oth")match=(fg==="Other Income");',
' else if(kind==="itemized")match=!!(tl&&/^\\d+[a-d]? - /.test(tl))||(fg==="Operating Expenses")||(fg==="Other Expenses");',
' if(match)accts.push(String(a.title).trim());}',
' drillSCEFromAccts(accts.join("|"),r.from,r.to);',
'}',
'function q1702SetQ(qn,field,val){Q1702C_STATE.perQ[qn][field]=Math.round(parseFloat(String(val).replace(/,/g,""))||0);render1702QC_();}',
'',
'function ensureQ1701Style_(){',
' if(!document.getElementById(\"q1701-style\")){var st=document.createElement(\"style\");st.id=\"q1701-style\";st.textContent=\".q1701-report{padding:0;color:#111827;font-family:Roboto,-apple-system,Arial,sans-serif;font-size:13px;max-width:var(--report-width);margin:0 auto}.q1701-section{background:white;border:1px solid #E0E6ED;border-radius:10px;padding:14px 18px;margin:0 0 10px;box-shadow:0 1px 3px rgba(0,0,0,.03)}.q1701-titleblock{text-align:center;padding:20px 18px}.q1701-bn{font-size:15px;font-weight:700;color:#1F3864;letter-spacing:.3px}.q1701-ttl{font-size:13px;color:#111827;font-weight:600;margin-top:6px}.q1701-meta{font-size:11px;color:#6B7280;margin-top:6px;letter-spacing:.3px}.q1701-sh{font-size:11px;font-weight:700;color:#1F3864;letter-spacing:.6px;text-transform:uppercase;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid #E0E6ED}.q1701-bg,.q1701-tbl{width:100%;border-collapse:collapse;font-size:13px}.q1701-bg td{padding:7px 10px;border-bottom:1px solid #F3F4F6;vertical-align:top;font-size:13px}.q1701-bg td.lbl{color:#6B7280;font-weight:500;width:120px}.q1701-tbl thead th{padding:10px 10px;background:white;color:#6B7280;font-weight:600;font-size:12px;letter-spacing:.3px;text-align:left;border-bottom:1px solid #E0E6ED}.q1701-tbl thead th.num{text-align:right}.q1701-tbl thead th.item,.q1701-tbl tbody td.item{width:42px;text-align:center;color:#6B7280;font-weight:600;font-size:11px}.q1701-tbl tbody td{padding:6px 10px;border-bottom:1px solid #F3F4F6;font-size:13px;color:#111827}.q1701-tbl tbody td.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;min-width:120px}.q1701-tbl tbody td.q1701-auto{color:#6B7280;font-style:italic}.q1701-tbl tbody tr.sub td{font-weight:600;background:#F9FAFB}.q1701-tbl tbody tr.tot td{font-weight:700;color:#111827;border-top:2px solid #111827;background:white}.q1701-tbl tbody tr.hl td{background:#FEF3C7;color:#92400E;font-weight:600}.q1701-inp{width:110px;padding:4px 6px;border:1px solid #D0D0D0;border-radius:3px;font-size:12px;text-align:right;font-family:inherit;color:#111827;box-sizing:border-box;background:white}.q1701-inp:focus{border-color:var(--gold);outline:none;box-shadow:0 0 0 2px rgba(201,162,39,.22);transition:border-color .15s,box-shadow .15s}.q1701-inp:hover:not(:disabled){border-color:var(--gold);transition:border-color .15s}.q1701-inp:disabled{border-color:#E5E7EB;background:#F9FAFB;color:#9CA3AF;cursor:not-allowed;box-shadow:none}.q1701-drill{color:#2563EB;cursor:pointer}.q1701-drill:hover{text-decoration:underline}.q1701-cell-wrap{display:flex;align-items:center;gap:4px;justify-content:flex-end}.q1701-tog{padding:2px 6px;font-size:9px;background:#E5E7EB;color:#6B7280;border:none;border-radius:3px;cursor:pointer;letter-spacing:.4px;text-transform:uppercase;font-weight:600;font-family:inherit}.q1701-tog.on{background:#1F3864;color:white}.q1701-hint{font-size:10px;color:#9CA3AF;font-weight:400;font-style:italic;margin-left:4px}@media print{.ctrl-bar,.topbar-v3,.sidebar-v3,.chatbar-v3{display:none!important}.q1701-inp,.q1701-tog{border:none;background:transparent;color:#111}}\";document.head.appendChild(st);}',
'}',
'',
'function render1702QC_(){',
' var tc=String((DATA.settings&&DATA.settings.taxpayerClass)||"").toUpperCase();',
' if(tc.indexOf("NON")!==0){document.getElementById("report").innerHTML="<div style=\\"padding:20px;color:#6B7280\\">BIR Form 1702Q is for Non-Individual taxpayers (corporations and partnerships). Set Taxpayer Classification to Non-Individual in Settings.</div>";return;}',
' q1702Init_();',
' var s=Q1702C_STATE;var settings=DATA.settings||{};',
' var quarters=q1701QuartersForView(s.viewMode);',
' var results={};for(var qi=0;qi<quarters.length;qi++){results[quarters[qi]]=compute1702Q_(parseInt(quarters[qi].substring(1)));}',
' var regName=settings.registeredNameNonInd||settings.businessName||"";',
' function pickCol(fn){var x="";for(var i=0;i<quarters.length;i++)x+="<td class=\\"num\\">"+fn(results[quarters[i]])+"</td>";return x;}',
' function drillCol(kind,fn){var x="";for(var i=0;i<quarters.length;i++){var q=quarters[i],d=results[q];x+="<td class=\\"num gl-amt-clickable\\" onclick=\\"drill1702Q("+d.q+",\'"+kind+"\')\\" title=\\"Open General Ledger for "+q+"\\">"+fn(d)+"</td>";}return x;}',
' function inputCol(field,gated){var x="";for(var i=0;i<quarters.length;i++){var d=results[quarters[i]];var v=(s.perQ[d.q]||{})[field]||0;',
' var isGated=(typeof gated==="function")?!!gated(d):!!gated;var dis=isGated?" disabled":"";var dispVal=isGated?"":(v?fmt1701(v):"");',
' x+="<td class=\\"num\\"><input type=\\"text\\" value=\\""+dispVal+"\\" placeholder=\\"0.00\\""+dis+" onchange=\\"q1702SetQ("+d.q+",\'"+field+"\',this.value)\\" class=\\"q1701-inp\\"></td>";}return x;}',
' function hdr(){var x="<tr><th class=\\"item\\">#</th><th class=\\"desc\\">Particulars</th>";for(var i=0;i<quarters.length;i++)x+="<th class=\\"num\\">"+quarters[i]+"</th>";x+="</tr>";return x;}',
' function pf(b){return b?"<span style=\\"color:#1E7E34;font-weight:600\\">PASS</span>":"<span style=\\"color:#B00020;font-weight:600\\">FAIL</span>";}',
' ensureQ1701Style_();',
' var m0=results[quarters[0]];',
' var mcitHint=m0.mcitNoDate?"Date of Incorporation not set in Settings - MCIT assumed applicable":(m0.mcitApplies?"applicable - 4th taxable year onward (incorporated "+m0.incYear+")":"NOT yet applicable - within the first 3 taxable years (incorporated "+m0.incYear+")");',
' var anyM=false;for(var am=0;am<quarters.length;am++)if(results[quarters[am]].useM)anyM=true;',
' var h="<div class=\\"q1701-report\\">";',
' h+="<div class=\\"q1701-section q1701-titleblock\\">";',
' h+="<div class=\\"q1701-bn\\">"+esc(regName)+"</div>";',
' h+="<div class=\\"q1701-ttl\\">BIR Form 1702Q - Quarterly Income Tax Return (Corporations and Partnerships)</div>";',
' h+="<div class=\\"q1701-meta\\">1 Calendar · 2 Year Ended 12/"+s.taxYear+" · 3 Quarter "+quarters.join(", ")+(s.amended?" · 4 AMENDED":"")+" · 5 ATC "+(anyM?"IC 055 - Minimum Corporate Income Tax (MCIT)":"IC 010 - Corporation in General")+"</div>";',
' h+="</div>";',
' h+="<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">Part I - Background Information</div>";',
' h+="<table class=\\"q1701-bg\\">";',
' h+="<tr><td class=\\"lbl\\">6. TIN</td><td>"+esc(settings.tin||"")+"</td><td class=\\"lbl\\">7. RDO Code</td><td>"+esc(settings.rdo||"")+"</td></tr>";',
' h+="<tr><td class=\\"lbl\\">8. Registered Name</td><td colspan=\\"3\\">"+esc(regName)+"</td></tr>";',
' h+="<tr><td class=\\"lbl\\">9. Registered Address</td><td colspan=\\"2\\">"+esc(settings.address||"")+"</td><td><span class=\\"q1701-hint\\">9A ZIP</span> "+esc(settings.zipCode||"")+"</td></tr>";',
' h+="<tr><td class=\\"lbl\\">10. Contact Number</td><td>"+esc(settings.contactNumber||"")+"</td><td class=\\"lbl\\">11. Email Address</td><td>"+esc(settings.email||"")+"</td></tr>";',
' h+="<tr><td class=\\"lbl\\">12. Method of Deductions</td><td>"+(s.ded==="osd"?"Optional Standard Deduction (OSD) - 40% of Gross Income":"Itemized Deductions [Section 34 (A-J), NIRC]")+"</td><td class=\\"lbl\\">13. Tax relief under Special Law/Treaty?</td><td>No</td></tr>";',
' h+="<tr><td class=\\"lbl\\">Signatory</td><td colspan=\\"3\\">"+esc(settings.repName||"")+(settings.repTitle?" - "+esc(settings.repTitle):"")+" <span class=\\"q1701-hint\\">(calculation reference only - this is not the filed form)</span></td></tr>";',
' h+="</table></div>";',
' h+="<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">Rate Determination (auto from your books)</div>";',
' h+="<table class=\\"q1701-tbl\\"><thead>"+hdr()+"</thead><tbody>";',
' h+="<tr><td class=\\"item\\"></td><td>Net taxable income to date <span class=\\"q1701-hint\\">(test: 5,000,000 or less)</span></td>"+pickCol(function(d){return fmt1701(d.t9)+" "+pf(d.ntiPass);})+"</tr>";',
' h+="<tr><td class=\\"item\\"></td><td>Total assets per books <span class=\\"q1701-hint\\">(test: 100,000,000 or less)</span></td>"+pickCol(function(d){return fmt1701(d.taTest)+(d.landNote?" <span class=\\"q1701-hint\\">excl. Land</span>":"")+" "+pf(d.taPass);})+"</tr>";',
' h+="<tr class=\\"hl\\"><td class=\\"item\\"></td><td>APPLICABLE INCOME TAX RATE"+(s.rateMode!=="auto"?" <span class=\\"q1701-hint\\">(forced)</span>":"")+"</td>"+pickCol(function(d){return d.rate+"%"+(d.rate===20?" (MSME)":"")+(s.rateMode!=="auto"&&d.autoRate!==d.rate?" <span class=\\"q1701-hint\\">auto: "+d.autoRate+"%</span>":"");})+"</tr>";',
' h+="</tbody></table></div>";',
' h+="<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">Schedule 2 - Computation of Income Tax (RCIT)</div>";',
' h+="<table class=\\"q1701-tbl\\"><thead>"+hdr()+"</thead><tbody>";',
' h+="<tr><td class=\\"item\\">1</td><td>Gross Sales/Revenues/Receipts/Fees</td>"+drillCol("rev",function(d){return fmt1701(d.cur.gross);})+"</tr>";',
' h+="<tr><td class=\\"item\\">2</td><td>Less: Cost of Sales/Services</td>"+drillCol("cos",function(d){return "("+fmt1701(d.cur.cos)+")";})+"</tr>";',
' h+="<tr class=\\"sub\\"><td class=\\"item\\">3</td><td>Gross Income from Operation</td>"+pickCol(function(d){return fmt1701(d.cur.gi);})+"</tr>";',
' h+="<tr><td class=\\"item\\">4</td><td>Add: Other Taxable Income Not Subjected to Final Tax</td>"+drillCol("oth",function(d){return fmt1701(d.cur.oth);})+"</tr>";',
' h+="<tr class=\\"sub\\"><td class=\\"item\\">5</td><td>Total Gross Income</td>"+pickCol(function(d){return fmt1701(d.cur.tgi);})+"</tr>";',
' if(s.ded==="osd")h+="<tr><td class=\\"item\\">6</td><td>Less: Optional Standard Deduction (40% of Item 5)</td>"+pickCol(function(d){return "("+fmt1701(d.cur.ded)+")";})+"</tr>";',
' else h+="<tr><td class=\\"item\\">6</td><td>Less: Total Allowable Itemized Deductions</td>"+drillCol("ded",function(d){return "("+fmt1701(d.cur.ded)+")";})+"</tr>";',
' h+="<tr class=\\"sub\\"><td class=\\"item\\">7</td><td>Net Taxable Income This Quarter</td>"+pickCol(function(d){return fmt1701(d.cur.nti);})+"</tr>";',
' h+="<tr><td class=\\"item\\">8</td><td>Add: Taxable Income Previous Quarter/s <span class=\\"q1701-hint\\">(auto)</span></td>"+pickCol(function(d){return fmt1701(d.prevNTI);})+"</tr>";',
' h+="<tr class=\\"sub\\"><td class=\\"item\\">9</td><td>Total Taxable Income to Date <span class=\\"q1701-hint\\">(Sum of Items 7 and 8)</span></td>"+pickCol(function(d){return fmt1701(d.t9);})+"</tr>";',
' h+="<tr><td class=\\"item\\">10</td><td>Applicable Income Tax Rate <span class=\\"q1701-hint\\">(except MCIT rate)</span></td>"+pickCol(function(d){return d.rate+"%";})+"</tr>";',
' h+="<tr><td class=\\"item\\">11</td><td>Income Tax Due Other than MCIT <span class=\\"q1701-hint\\">(Item 9 multiply by Item 10)</span></td>"+pickCol(function(d){return fmt1701(d.t11);})+"</tr>";',
' h+="<tr><td class=\\"item\\">12</td><td>Minimum Corporate Income Tax (MCIT) <span class=\\"q1701-hint\\">(From Schedule 3 Item 6)</span></td>"+pickCol(function(d){return fmt1701(d.m3);})+"</tr>";',
' h+="<tr class=\\"hl\\"><td class=\\"item\\">13</td><td>INCOME TAX DUE <span class=\\"q1701-hint\\">(Item 11 or Item 12 whichever is higher) (To Part II Item 14)</span></td>"+pickCol(function(d){return fmt1701(d.sch13)+" <span class=\\"q1701-hint\\">"+(d.useM?"MCIT":"RCIT")+"</span>";})+"</tr>";',
' h+="</tbody></table></div>";',
' h+="<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">Schedule 3 - Computation of Minimum Corporate Income Tax (MCIT) for the Quarter/s</div>";',
' h+="<table class=\\"q1701-tbl\\"><thead>"+hdr()+"</thead><tbody>";',
' var s3ord=["1st","2nd","3rd"];',
' for(var s3=1;s3<=3;s3++){(function(k){',
' h+="<tr><td class=\\"item\\">"+k+"</td><td>Gross Income Regular/Normal Rate - "+s3ord[k-1]+" Quarter</td>"+pickCol(function(d){return d.q>=k?fmt1701(d.giQ[k-1]):"-";})+"</tr>";',
' })(s3);}',
' h+="<tr class=\\"sub\\"><td class=\\"item\\">4</td><td>Total Gross Income <span class=\\"q1701-hint\\">(Sum of Items 1 to 3)</span></td>"+pickCol(function(d){return fmt1701(d.cumTGI);})+"</tr>";',
' h+="<tr><td class=\\"item\\">5</td><td>MCIT Rate <span class=\\"q1701-hint\\">"+mcitHint+"</span></td>"+pickCol(function(d){return d.mcitApplies?"2%":"0%";})+"</tr>";',
' h+="<tr class=\\"hl\\"><td class=\\"item\\">6</td><td>Minimum Corporate Income Tax <span class=\\"q1701-hint\\">(To Schedule 2 Item 12)</span></td>"+pickCol(function(d){return fmt1701(d.m3);})+"</tr>";',
' h+="</tbody></table></div>";',
' h+="<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">Schedule 4 - Tax Credits/Payments</div>";',
' h+="<table class=\\"q1701-tbl\\"><thead>"+hdr()+"</thead><tbody>";',
' h+="<tr><td class=\\"item\\">1</td><td>Prior Year"+String.fromCharCode(8217)+"s Excess Credits</td>"+inputCol("c1",false)+"</tr>";',
' h+="<tr><td class=\\"item\\">2</td><td>Tax payment/s for the previous quarter/s of the same taxable year other than MCIT <span class=\\"q1701-hint\\">(N/A in Q1)</span></td>"+inputCol("c2",function(d){return d.q===1;})+"</tr>";',
' h+="<tr><td class=\\"item\\">3</td><td>MCIT payment/s for the previous quarter/s of the same taxable year <span class=\\"q1701-hint\\">(N/A in Q1)</span></td>"+inputCol("c3",function(d){return d.q===1;})+"</tr>";',
' h+="<tr><td class=\\"item\\">4</td><td>Creditable Tax Withheld for the previous quarter/s <span class=\\"q1701-hint\\">(N/A in Q1)</span></td>"+inputCol("c4",function(d){return d.q===1;})+"</tr>";',
' h+="<tr><td class=\\"item\\">5</td><td>Creditable Tax Withheld per BIR Form No. 2307 for this quarter</td>"+inputCol("c5",false)+"</tr>";',
' h+="<tr><td class=\\"item\\">6</td><td>Tax paid in return previously filed if this is an amended return"+(s.amended?"":" <span class=\\"q1701-hint\\">(amended only)</span>")+"</td>"+inputCol("c6",!s.amended)+"</tr>";',
' h+="<tr><td class=\\"item\\">6a</td><td>Other Tax Credits/Payments</td>"+inputCol("c7",false)+"</tr>";',
' h+="<tr class=\\"sub\\"><td class=\\"item\\">7</td><td>Total Tax Credits/Payments <span class=\\"q1701-hint\\">(Sum of Items 1 to 6a) (To Part II Item 19)</span></td>"+pickCol(function(d){return fmt1701(d.credits);})+"</tr>";',
' h+="</tbody></table></div>";',
' h+="<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">Part II - Total Tax Payable</div>";',
' h+="<table class=\\"q1701-tbl\\"><thead>"+hdr()+"</thead><tbody>";',
' h+="<tr><td class=\\"item\\">14</td><td>Income Tax Due <span class=\\"q1701-hint\\">(From Schedule 2 Item 13)</span></td>"+pickCol(function(d){return fmt1701(d.sch13);})+"</tr>";',
' h+="<tr><td class=\\"item\\">15</td><td>Less: Unexpired Excess of Prior Year"+String.fromCharCode(8217)+"s MCIT over Regular Rate <span class=\\"q1701-hint\\">(deductible only if the quarterly tax due is the regular/normal rate)</span></td>"+inputCol("p15",function(d){return d.useM;})+"</tr>";',
' h+="<tr class=\\"sub\\"><td class=\\"item\\">16</td><td>Balance/Income Tax Still Due <span class=\\"q1701-hint\\">(Item 14 Less Item 15)</span></td>"+pickCol(function(d){return fmt1701(d.p16);})+"</tr>";',
' h+="<tr><td class=\\"item\\">17</td><td>Add: Income Tax Due - Special Rate <span class=\\"q1701-hint\\">(Schedule 1 special/exempt registrations - not applicable in this build)</span></td>"+pickCol(function(d){return fmt1701(0);})+"</tr>";',
' h+="<tr class=\\"sub\\"><td class=\\"item\\">18</td><td>Aggregate Income Tax Due <span class=\\"q1701-hint\\">(Sum of Items 16 and 17)</span></td>"+pickCol(function(d){return fmt1701(d.p18);})+"</tr>";',
' h+="<tr><td class=\\"item\\">19</td><td>Less: Total Tax Credits/Payments <span class=\\"q1701-hint\\">(From Schedule 4 Item 7)</span></td>"+pickCol(function(d){return "("+fmt1701(d.credits)+")";})+"</tr>";',
' h+="<tr class=\\"sub\\"><td class=\\"item\\">20</td><td>Net Tax Payable/(Overpayment) <span class=\\"q1701-hint\\">(Item 18 Less Item 19)</span></td>"+pickCol(function(d){return fmt1701(d.p20);})+"</tr>";',
' h+="<tr><td class=\\"item\\">21</td><td>Add: Penalties - Surcharge</td>"+inputCol("sur",false)+"</tr>";',
' h+="<tr><td class=\\"item\\">22</td><td>Interest</td>"+inputCol("intr",false)+"</tr>";',
' h+="<tr><td class=\\"item\\">23</td><td>Compromise</td>"+inputCol("comp",false)+"</tr>";',
' h+="<tr class=\\"sub\\"><td class=\\"item\\">24</td><td>Total Penalties <span class=\\"q1701-hint\\">(Sum of Items 21 to 23)</span></td>"+pickCol(function(d){return fmt1701(d.p24);})+"</tr>";',
' h+="<tr class=\\"tot\\"><td class=\\"item\\">25</td><td>TOTAL AMOUNT PAYABLE/(Overpayment) <span class=\\"q1701-hint\\">(Sum of Items 20 and 24)</span></td>"+pickCol(function(d){return fmt1701(d.p25);})+"</tr>";',
' h+="</tbody></table></div>";',
' h+="</div>";',
' document.getElementById("report").innerHTML=h;',
'}',
'',
'/* ── CF Helpers ── */',
'function addDaysISO(iso,n){var d=new Date(iso+"T00:00:00");d.setDate(d.getDate()+n);var y=d.getFullYear(),m=d.getMonth()+1,dd=d.getDate();return y+"-"+(m<10?"0":"")+m+"-"+(dd<10?"0":"")+dd;}',
'function computeBalancesAt(jeRows,asOf){var bal={};for(var i=0;i<jeRows.length;i++){var je=jeRows[i];if(!je.date||je.date>asOf)continue;var a=je.account;if(!bal[a])bal[a]=0;bal[a]+=(parseFloat(je.debit)||0)-(parseFloat(je.credit)||0);}return bal;}',
'function classifyForCF(account){var t=String(account.type||"").trim();var title=String(account.title||"").toLowerCase();var fs=String(account.fsGroup||"").toLowerCase();var code=String(account.code||"");var ng=String(account.noteGroup||"").toLowerCase();var isAsset=/^asset/i.test(t);',
' if(/cash and cash equivalent/.test(ng)||(isAsset&&/cash|bank|bangko|gcash|g-cash|maya|paymaya|wallet/.test(title)))return "Cash";',
' if(t==="Expense"&&/depreciat|amortiz/.test(title))return "NonCashExp";',
' if(t==="Asset"&&/accumulated|accum/.test(title)&&/depr|amort/.test(title))return "NonCashContra";',
' if(t==="Asset"&&(/property|equipment|furniture|vehicle|building|land|ppe|fixed asset/.test(fs+" "+title)||code.indexOf("12")===0))return "Investing";',
' if(t==="Liability"&&(/long.term|loans? payable|notes? payable|mortgage/.test(fs)||/loan|mortgage/.test(title)))return "Financing";',
' if(t==="Equity")return "Financing";',
' if(t==="Revenue")return "OpRev";',
' if(t==="Expense"&&/income tax/.test(title))return "OpTaxExp";',
' if(t==="Expense"&&/interest/.test(title))return "OpIntExp";',
' if(t==="Expense")return "OpExp";',
' if(t==="Asset")return "OpAsset";',
' if(t==="Liability")return "OpLiab";',
' return "Other";}',
'function fmtCF(n){if(n===0||n===null||n===undefined||isNaN(n))return "-";var abs=Math.abs(n);var s=APP_STATE.hideDecimals?Math.round(abs).toLocaleString("en-US"):abs.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});return n<0?"("+s+")":s;}',
'',
'/* ── CF Direct Method (strict IAS 7) — Option X ── */',
'/* Categorizes actual cash flows from JE entries grouped by entryId. */',
'/* PPE acquisitions show NET of recoverable input VAT (IAS 7 / IFRIC Aug-2005; consistent with the indirect method) - input VAT routes to operating. Loans gross (proceeds vs principal separately). */',
'/* Books Start Date (Settings C87): entries dated on/before this are opening balances, not cash flows (Xero conversion-date pattern). */',
'function cfBsd_(){return (typeof DATA!=="undefined"&&DATA&&DATA.settings&&DATA.settings.booksStartDate)?String(DATA.settings.booksStartDate):"";}',
'function computeCFDirectStrict(jeRows,coa,from,to,cls){',
' var groups={};var bsd=cfBsd_();',
' for(var i=0;i<jeRows.length;i++){',
' var je=jeRows[i];if(!je.date||je.date<from||je.date>to)continue;',
' if(bsd&&je.date<=bsd)continue;',
' var key=(je.entryId||"")+"|"+(je.date||"")+"|"+(je.ref||"")+"|"+(je.source||"");',
' if(!groups[key])groups[key]=[];',
' groups[key].push(je);',
' }',
' function classifyCounter(account,isCRSide){',
' var c=cls[account];var t=String(account||"").toLowerCase();',
' if(c==="Cash")return null;',
' if(c==="OpRev")return "fromCustomers";',
' if(c==="OpAsset"){if(/receivable|cwt/.test(t))return "fromCustomers";return "toSuppliers";}',
' if(c==="OpIntExp")return "toInterest";',
' if(c==="OpTaxExp")return "toTax";',
' if(c==="OpExp")return "toSuppliers";',
' if(c==="OpLiab"){if(/dividend/.test(t))return "dividends";if(/income tax/.test(t))return "toTax";if(/interest/.test(t))return "toInterest";if(/output vat|vat payable/.test(t))return isCRSide?"fromCustomers":"toVAT";return "toSuppliers";}',
' if(c==="Investing")return "investing";',
' if(c==="Financing"){if(/retained earnings/.test(t))return "dividends";if(/loan|mortgage|note/.test(t))return isCRSide?"loanProceeds":"loanRepayments";if(/withdrawal|drawing/.test(t))return "ownerWithdraw";if(/capital/.test(t))return isCRSide?"ownerContrib":"ownerWithdraw";}',
' return null;',
' }',
' var ops={fromCustomers:0,toSuppliers:0,toInterest:0,toTax:0,toVAT:0};',
' var inv=0;',
' var fin={loanProceeds:0,loanRepayments:0,ownerContrib:0,ownerWithdraw:0,dividends:0};',
' for(var k in groups){',
' var legs=groups[k];var cashIn=0,cashOut=0;',
' for(var li=0;li<legs.length;li++){var leg=legs[li];if(cls[leg.account]==="Cash"){cashIn+=parseFloat(leg.debit)||0;cashOut+=parseFloat(leg.credit)||0;}}',
' var netCash=cashIn-cashOut;if(Math.abs(netCash)<0.005)continue;',
' var lookForCRs=netCash>0;var counterByCat={};var oppositeSideAmt=0;var hasInvesting=false;',
' for(var li2=0;li2<legs.length;li2++){',
' var leg2=legs[li2];if(cls[leg2.account]==="Cash")continue;',
' var amt=lookForCRs?(parseFloat(leg2.credit)||0):(parseFloat(leg2.debit)||0);',
' if(amt<0.005)continue;',
' var cat=classifyCounter(leg2.account,lookForCRs);if(!cat)continue;',
' if(cat==="investing")hasInvesting=true;',
' counterByCat[cat]=(counterByCat[cat]||0)+amt;',
' oppositeSideAmt+=amt;',
' }',
' if(oppositeSideAmt<0.005)continue;',
' var absNetCash=Math.abs(netCash);',
' /* CF-VAT FIX (IAS 7 / IFRIC Aug-2005 net-of-VAT, consistent w/ indirect): allocate cash PER counterpart.',
' Asset legs -> investing; recoverable input VAT (toSuppliers) -> operating. No longer dumps the whole',
' VAT-inclusive cash into investing when an entry contains a PPE account. */',
' for(var cat2 in counterByCat){',
' var alloc=absNetCash*(counterByCat[cat2]/oppositeSideAmt);',
' if(cat2==="fromCustomers")ops.fromCustomers+=alloc;',
' else if(cat2==="toSuppliers")ops.toSuppliers+=alloc;',
' else if(cat2==="toInterest")ops.toInterest+=alloc;',
' else if(cat2==="toTax")ops.toTax+=alloc;',
' else if(cat2==="toVAT")ops.toVAT+=alloc;',
' else if(cat2==="loanProceeds")fin.loanProceeds+=alloc;',
' else if(cat2==="loanRepayments")fin.loanRepayments+=alloc;',
' else if(cat2==="ownerContrib")fin.ownerContrib+=alloc;',
' else if(cat2==="ownerWithdraw")fin.ownerWithdraw+=alloc;',
' else if(cat2==="dividends")fin.dividends+=alloc;',
' else if(cat2==="investing"){if(netCash>0)inv+=alloc;else inv-=alloc;}',
' }',
' }',
' var netOp=ops.fromCustomers-ops.toSuppliers-ops.toInterest-ops.toTax-ops.toVAT;',
' var netFin=fin.loanProceeds-fin.loanRepayments+fin.ownerContrib-fin.ownerWithdraw-fin.dividends;',
' var netChange=netOp+inv+netFin;',
' var investingLines=[];',
' if(Math.abs(inv)>0.005)investingLines.push({label:inv<0?"Purchase of property, plant and equipment":"Proceeds from sale of property, plant and equipment",amount:inv,accounts:[]});',
' var financingLines=[];',
' if(Math.abs(fin.loanProceeds)>0.005)financingLines.push({label:"Proceeds from bank loans",amount:fin.loanProceeds,accounts:[]});',
' if(Math.abs(fin.loanRepayments)>0.005)financingLines.push({label:"Repayment of loan principal",amount:-fin.loanRepayments,accounts:[]});',
' var cfIsCorp=String((typeof DATA!=="undefined"&&DATA.settings&&DATA.settings.taxpayerClass)||"").toUpperCase().indexOf("NON")===0;',
' if(Math.abs(fin.ownerContrib)>0.005)financingLines.push({label:cfIsCorp?"Proceeds from share issuance":"Owner contributions",amount:fin.ownerContrib,accounts:[]});',
' if(Math.abs(fin.ownerWithdraw)>0.005)financingLines.push({label:"Owner withdrawals",amount:-fin.ownerWithdraw,accounts:[]});',
' if(Math.abs(fin.dividends)>0.005)financingLines.push({label:"Dividends paid",amount:-fin.dividends,accounts:[]});',
' return {',
' operating:[',
' {label:"Cash received from customers",amount:ops.fromCustomers,accounts:[]},',
' {label:"Cash paid to suppliers and employees",amount:-ops.toSuppliers,accounts:[]},',
' {label:"Cash paid for interest",amount:-ops.toInterest,accounts:[]},',
' {label:"Cash paid for income tax",amount:-ops.toTax,accounts:[]},',
' {label:"Cash paid for VAT",amount:-ops.toVAT,accounts:[]}',
' ],',
' operatingTotal:netOp,',
' investing:investingLines,',
' investingTotal:inv,',
' financing:financingLines,',
' financingTotal:netFin,',
' netChange:netChange',
' };',
'}',
'',
'/* ── CF Main Computation ── */',
'function computeCF(jeRows,coa,from,to){',
' var bsd=cfBsd_();',
' var preB=!!(bsd&&to<=bsd);',
' var begDate=addDaysISO(from,-1);',
' if(bsd&&begDate<bsd)begDate=bsd;',
' var begBal=computeBalancesAt(jeRows,begDate);',
' var endBal=computeBalancesAt(jeRows,to);',
// Period activity for all accounts (opening-balance entries excluded - they are positions, not flows)
' var period={};',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(!je.date||je.date<from||je.date>to)continue;if(bsd&&je.date<=bsd)continue;var a=je.account;if(!period[a])period[a]=0;period[a]+=(parseFloat(je.debit)||0)-(parseFloat(je.credit)||0);}',
// Classify each account
' var cls={};for(var k=0;k<coa.length;k++){cls[coa[k].title]=classifyForCF(coa[k]);}',
// Buckets
' var revByAcct={},expByAcct={},taxExpByAcct={},intExpByAcct={},nonCashExpByAcct={};',
' var opAssetsByAcct={},opLiabsByAcct={},investAssetsByAcct={},financingByAcct={};',
' var cashBeg=0,cashEnd=0;',
' for(var m=0;m<coa.length;m++){var acct=coa[m];var c=cls[acct.title];var b=begBal[acct.title]||0,e=endBal[acct.title]||0,p=period[acct.title]||0;var d=e-b;',
' if(c==="Cash"){cashBeg+=b;cashEnd+=e;}',
' else if(c==="OpRev"&&Math.abs(p)>0.005)revByAcct[acct.title]=-p;',
' else if(c==="OpExp"&&Math.abs(p)>0.005)expByAcct[acct.title]=p;',
' else if(c==="OpTaxExp"&&Math.abs(p)>0.005)taxExpByAcct[acct.title]=p;',
' else if(c==="OpIntExp"&&Math.abs(p)>0.005)intExpByAcct[acct.title]=p;',
' else if(c==="NonCashExp"&&Math.abs(p)>0.005)nonCashExpByAcct[acct.title]=p;',
' else if(c==="OpAsset"&&Math.abs(d)>0.005)opAssetsByAcct[acct.title]=d;',
' else if(c==="OpLiab"&&Math.abs(d)>0.005)opLiabsByAcct[acct.title]=d;',
' else if(c==="Investing"&&Math.abs(d)>0.005)investAssetsByAcct[acct.title]=d;',
' else if(c==="Financing"&&Math.abs(d)>0.005)financingByAcct[acct.title]=d;',
' }',
// PRE-BOOKS FIX: periods entirely on/before the Books Start Date have NO cash flows -
// opening (conversion) balances are positions, not flows (IAS 7; Xero conversion-date pattern).
// The delta-based buckets are spurious here (begDate clamps to BSD so begBal includes the OB
// while endBal at a pre-BSD "to" excludes it). Clear them so the Indirect method shows no flows,
// matching the strict Direct method which iterates journal rows and finds none.
' if(preB){opAssetsByAcct={};opLiabsByAcct={};investAssetsByAcct={};financingByAcct={};}',
// Totals
' var revTotal=0;for(var r in revByAcct)revTotal+=revByAcct[r];',
' var expTotal=0;for(var x in expByAcct)expTotal+=expByAcct[x];',
' var taxExpTotal=0;for(var tt in taxExpByAcct)taxExpTotal+=taxExpByAcct[tt];',
' var intExpTotal=0;for(var i2 in intExpByAcct)intExpTotal+=intExpByAcct[i2];',
' var nonCashExpTotal=0;for(var nc in nonCashExpByAcct)nonCashExpTotal+=nonCashExpByAcct[nc];',
' var netIncome=revTotal-(expTotal+taxExpTotal+intExpTotal+nonCashExpTotal);',
// Split OpAssets: receivables (revenue-related) vs prepaid (expense-related)
' var recvAssetDelta=0,prepaidAssetDelta=0;var recvAssets=[],prepaidAssets=[];',
' for(var aa in opAssetsByAcct){if(/receivable|cwt|unearned/i.test(aa)){recvAssetDelta+=opAssetsByAcct[aa];recvAssets.push(aa);}else{prepaidAssetDelta+=opAssetsByAcct[aa];prepaidAssets.push(aa);}}',
// Split OpLiabs: IT payable, interest payable, other
' var itPayableDelta=0,intPayableDelta=0,opOtherLiabDelta=0;var itPayables=[],intPayables=[],otherLiabs=[];',
' for(var ll in opLiabsByAcct){if(/income tax/i.test(ll)){itPayableDelta+=opLiabsByAcct[ll];itPayables.push(ll);}else if(/interest/i.test(ll)){intPayableDelta+=opLiabsByAcct[ll];intPayables.push(ll);}else{opOtherLiabDelta+=opLiabsByAcct[ll];otherLiabs.push(ll);}}',
// DIRECT METHOD
' var cashFromCust=revTotal-recvAssetDelta;',
' var cashPaidForOpExp=expTotal+prepaidAssetDelta+opOtherLiabDelta;',
' var cashPaidForTax=taxExpTotal+itPayableDelta;',
' var cashPaidForInt=intExpTotal+intPayableDelta;',
' var netOperatingDirect=cashFromCust-cashPaidForOpExp-cashPaidForTax-cashPaidForInt;',
' var directOperating=[',
' {label:"Cash received from customers",amount:cashFromCust,accounts:Object.keys(revByAcct).concat(recvAssets)},',
' {label:"Cash paid to suppliers and employees",amount:-cashPaidForOpExp,accounts:Object.keys(expByAcct).concat(prepaidAssets).concat(otherLiabs)},',
' {label:"Cash paid for income tax",amount:-cashPaidForTax,accounts:Object.keys(taxExpByAcct).concat(itPayables)},',
' {label:"Cash paid for interest",amount:-cashPaidForInt,accounts:Object.keys(intExpByAcct).concat(intPayables)}',
' ];',
// INVESTING (both methods)
' var investingLines=[];var investingTotal=0;',
' for(var ia in investAssetsByAcct){var iD=investAssetsByAcct[ia];var lbl=iD>0?"Purchase of "+ia:"Sale of "+ia;investingLines.push({label:lbl,amount:-iD,accounts:[ia]});investingTotal-=iD;}',
// FINANCING (both methods)
' var financingLines=[];var financingTotal=0;',
' for(var fa in financingByAcct){var fD=financingByAcct[fa];var cashImpact=-fD;var lbl2;',
' if(/withdrawal/i.test(fa))lbl2=fD>0?"Owner\\u2019s withdrawals":"Reversal of withdrawals";',
' else if(/capital/i.test(fa))lbl2=fD<0?"Owner\\u2019s contribution":"Withdrawal of capital";',
' else if(/loan|note/i.test(fa))lbl2=fD<0?"Proceeds from loans":"Repayment of loans";',
' else lbl2=fD<0?"Inflow from "+fa:"Outflow to "+fa;',
' financingLines.push({label:lbl2,amount:cashImpact,accounts:[fa]});financingTotal+=cashImpact;',
' }',
' var netChangeDirect=netOperatingDirect+investingTotal+financingTotal;',
// INDIRECT METHOD
' var indNonCash=[];for(var nn in nonCashExpByAcct)indNonCash.push({label:nn,amount:nonCashExpByAcct[nn],accounts:[nn]});',
' var indWC=[];',
' for(var op in opAssetsByAcct){var d2=opAssetsByAcct[op];var lblA=d2<0?"Decrease in "+op:"Increase in "+op;indWC.push({label:lblA,amount:-d2,accounts:[op]});}',
' for(var op2 in opLiabsByAcct){var d3=opLiabsByAcct[op2];var lblB=d3<0?"Increase in "+op2:"Decrease in "+op2;indWC.push({label:lblB,amount:-d3,accounts:[op2]});}',
' var indOpTotal=netIncome;',
' for(var nci=0;nci<indNonCash.length;nci++)indOpTotal+=indNonCash[nci].amount;',
' for(var wci=0;wci<indWC.length;wci++)indOpTotal+=indWC[wci].amount;',
' var netChangeIndirect=indOpTotal+investingTotal+financingTotal;',
' var netChangeActual=cashEnd-cashBeg;',
' // STRICT IAS 7 Direct Method (Option X): actual cash flows by entry, gross loans, PPE gross with VAT',
' var strictDirect=computeCFDirectStrict(jeRows,coa,from,to,cls);',
' var directMatch=Math.abs(strictDirect.netChange-netChangeActual)<1;',
' var indirectMatch=Math.abs(netChangeIndirect-netChangeActual)<1;',
' return {',
' direct:strictDirect,',
' indirect:{netIncome:netIncome,nonCash:indNonCash,workingCapital:indWC,operatingTotal:indOpTotal,investing:investingLines,investingTotal:investingTotal,financing:financingLines,financingTotal:financingTotal,netChange:netChangeIndirect},',
' beginCash:cashBeg,endCash:cashEnd,netChangeActual:netChangeActual,from:from,to:to,',
' directMatch:directMatch,indirectMatch:indirectMatch,valid:directMatch&&indirectMatch,',
' preBooks:(bsd&&to<=bsd)?true:false,booksStartDate:bsd',
' };',
'}',
'',
'/* ── CF Controls ── */',
'function buildCF(){buildCFControls();recomputeCF();}',
'function buildCFControls(){',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(function(d){return d&&d.length>=10}).sort();',
' var minD=dates[0]||"2025-01-01",maxD=dates[dates.length-1]||"2026-12-31";',
' var h="<div class=\\"ctrl-bar\\">";',
' h+="<div class=\\"ctrl-group\\"><label>Date Range</label><select id=\\"cfPreset\\" onchange=\\"applyCFPreset()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' h+="<option value=\\"tfy\\" selected>This Financial Year</option>";',
' h+="<option value=\\"ytd\\">Year-to-Date</option>";',
' h+="<option value=\\"lfy\\">Last Financial Year</option>";',
' h+="<option value=\\"tq\\">This Quarter</option>";',
' h+="<option value=\\"qtd\\">Quarter-to-Date</option>";',
' h+="<option value=\\"lq\\">Last Quarter</option>";',
' h+="<option value=\\"tm\\">This Month</option>";',
' h+="<option value=\\"mtd\\">Month-to-Date</option>";',
' h+="<option value=\\"lm\\">Last Month</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"q1\\">Quarter 1</option><option value=\\"q2\\">Quarter 2</option><option value=\\"q3\\">Quarter 3</option><option value=\\"q4\\">Quarter 4</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"custom\\">Custom</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\"><label>From</label><input type=\\"date\\" id=\\"cfFrom\\" value=\\""+minD+"\\" onchange=\\"document.getElementById(\'cfPreset\').value=\'custom\';recomputeCF()\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>To</label><input type=\\"date\\" id=\\"cfTo\\" value=\\""+maxD+"\\" onchange=\\"document.getElementById(\'cfPreset\').value=\'custom\';recomputeCF()\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Compare</label><select id=\\"cfCmp\\" onchange=\\"onCFCmpChange()\\">";',
' h+="<option value=\\"none\\" selected>None</option>";',
' h+="<option value=\\"sppy\\">Same Period Last Year</option>";',
' h+="<option value=\\"custom\\">Custom</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"cfCustomGrp\\" style=\\"display:none\\"><label>Compare From</label><input type=\\"date\\" id=\\"cfCustomDate\\" value=\\""+minD+"\\" onchange=\\"recomputeCF()\\"></div>";',
' h+="<div class=\\"ctrl-group\\" style=\\"position:relative\\"><label> </label><button id=\\"cfMoreBtn\\" class=\\"ctrl-dropdown-btn\\" onclick=\\"toggleCFMore()\\">More \\u25BE</button><div id=\\"cfMoreWrap\\"></div></div>";',
' h+="</div>";',
' document.getElementById("controls").innerHTML=h;',
' applyCFPreset();',
'}',
'function applyCFPreset(){var p=document.getElementById("cfPreset").value;if(p==="custom")return;var dates=DATA.jeRows.map(function(e){return e.date}).filter(function(d){return d&&d.length>=10}).sort();var maxD=dates[dates.length-1]||"2026-12-31";var now=new Date(maxD+"T00:00:00");var y=now.getFullYear(),m=now.getMonth()+1,d=now.getDate();var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var from,to;',
' function fyBounds(year){var sY=year,sM=fyM;var from=sY+"-"+(sM<10?"0":"")+sM+"-01";var eY=fyM===1?sY:sY+1;var eM=fyM===1?12:fyM-1;var eD=ld(eM,eY);var to=eY+"-"+(eM<10?"0":"")+eM+"-"+(eD<10?"0":"")+eD;return{from:from,to:to};}',
' function isCurFY(){return(fyM===1)?true:(m>=fyM);}',
' var curFYY=isCurFY()?y:y-1;',
' if(p==="tfy"){var b=fyBounds(curFYY);from=b.from;to=b.to;}',
' else if(p==="ytd"){var b2=fyBounds(curFYY);from=b2.from;to=maxD;}',
' else if(p==="lfy"){var b3=fyBounds(curFYY-1);from=b3.from;to=b3.to;}',
' else if(p==="tq"){var qStart=Math.floor((m-1)/3)*3+1;from=y+"-"+(qStart<10?"0":"")+qStart+"-01";var qE=qStart+2;var qED=ld(qE,y);to=y+"-"+(qE<10?"0":"")+qE+"-"+(qED<10?"0":"")+qED;}',
' else if(p==="qtd"){var qStart2=Math.floor((m-1)/3)*3+1;from=y+"-"+(qStart2<10?"0":"")+qStart2+"-01";to=maxD;}',
' else if(p==="lq"){var qStart3=Math.floor((m-1)/3)*3+1;var lqS=qStart3-3;var lqY=y;if(lqS<=0){lqS+=12;lqY-=1;}from=lqY+"-"+(lqS<10?"0":"")+lqS+"-01";var lqE=lqS+2;var lqED=ld(lqE,lqY);to=lqY+"-"+(lqE<10?"0":"")+lqE+"-"+(lqED<10?"0":"")+lqED;}',
' else if(p==="tm"){from=y+"-"+(m<10?"0":"")+m+"-01";var mED=ld(m,y);to=y+"-"+(m<10?"0":"")+m+"-"+(mED<10?"0":"")+mED;}',
' else if(p==="mtd"){from=y+"-"+(m<10?"0":"")+m+"-01";to=maxD;}',
' else if(p==="lm"){var lmM=m-1,lmY=y;if(lmM<1){lmM=12;lmY-=1;}from=lmY+"-"+(lmM<10?"0":"")+lmM+"-01";var lmED=ld(lmM,lmY);to=lmY+"-"+(lmM<10?"0":"")+lmM+"-"+(lmED<10?"0":"")+lmED;}',
' else if(p==="q1"||p==="q2"||p==="q3"||p==="q4"){var qNum=parseInt(p.substring(1));var qS=(qNum-1)*3+1,qE2=qS+2;from=y+"-"+(qS<10?"0":"")+qS+"-01";var qED2=ld(qE2,y);to=y+"-"+(qE2<10?"0":"")+qE2+"-"+(qED2<10?"0":"")+qED2;}',
' if(from&&to){document.getElementById("cfFrom").value=from;document.getElementById("cfTo").value=to;recomputeCF();}',
'}',
'function onCFCmpChange(){var m=document.getElementById("cfCmp").value;document.getElementById("cfCustomGrp").style.display=m==="custom"?"flex":"none";if(m==="none")CF_STATE.showVariance=false;refreshCFMorePanel();recomputeCF();}',
'',
'/* ── CF More Panel ── */',
'function toggleCFMore(){var wrap=document.getElementById("cfMoreWrap");if(wrap.innerHTML){wrap.innerHTML="";return;}wrap.innerHTML=renderCFMorePanel();}',
'function refreshCFMorePanel(){var wrap=document.getElementById("cfMoreWrap");if(wrap&&wrap.innerHTML)wrap.innerHTML=renderCFMorePanel();}',
'function renderCFMorePanel(){',
' var cmp=document.getElementById("cfCmp"),cmpVal=cmp?cmp.value:"none";',
' var h="<div class=\\"panel\\" style=\\"width:280px;right:0;left:auto\\">";',
' h+="<div class=\\"panel-hdr\\"><span class=\\"panel-title\\">More</span><button class=\\"panel-close\\" onclick=\\"toggleCFMore()\\">\\u2715</button></div>";',
// Method toggle (bilateral)
' var mInd=CF_STATE.method==="indirect";',
' var dClass=mInd?"bi-label":"bi-label active";',
' var iClass=mInd?"bi-label active":"bi-label";',
' h+="<div class=\\"more-row\\"><span class=\\"more-row-label\\">Method</span><div class=\\"toggle-bilateral\\"><span class=\\""+dClass+"\\">Direct</span><label class=\\"toggle-switch\\"><input type=\\"checkbox\\" "+(mInd?"checked":"")+" onchange=\\"toggleCFMethod()\\"><span class=\\"toggle-slider\\"></span></label><span class=\\""+iClass+"\\">Indirect</span></div></div>";',
// Variance (when comparing)
' if(cmpVal!=="none"){',
' h+="<div class=\\"more-row\\"><span class=\\"more-row-label\\">Variance</span><label class=\\"toggle-switch\\"><input type=\\"checkbox\\" "+(CF_STATE.showVariance?"checked":"")+" onchange=\\"toggleCFVar()\\"><span class=\\"toggle-slider\\"></span></label></div>";',
' }',
// Hide Decimals
' h+="<div class=\\"more-row\\"><span class=\\"more-row-label\\">Hide Decimals</span><label class=\\"toggle-switch\\"><input type=\\"checkbox\\" "+(APP_STATE.hideDecimals?"checked":"")+" onchange=\\"toggleCFHideDecimals(this.checked)\\"><span class=\\"toggle-slider\\"></span></label></div>";',
' h+="</div>";',
' return h;',
'}',
'function toggleCFMethod(){CF_STATE.method=CF_STATE.method==="direct"?"indirect":"direct";refreshCFMorePanel();recomputeCF();}',
'function toggleCFVar(){CF_STATE.showVariance=!CF_STATE.showVariance;refreshCFMorePanel();recomputeCF();}',
'function toggleCFHideDecimals(v){APP_STATE.hideDecimals=v;recomputeCF();}',
'',
'/* ── CF Compute Driver + Render ── */',
'function recomputeCF(){if(!document.getElementById("cfFrom"))return;',
' if(!SKEL_SUPPRESS){showSkeleton("cf");SKEL_SUPPRESS=true;setTimeout(function(){recomputeCF();SKEL_SUPPRESS=false;fadeInReport("cf");},200);return;}',
' var from=document.getElementById("cfFrom").value;var to=document.getElementById("cfTo").value;',
' CF_STATE.from=from;CF_STATE.to=to;',
' if(!from||!to){document.getElementById("report").innerHTML="<div style=\\"padding:20px;color:#6B7280\\">Please select From and To dates.</div>";return;}',
' var data=computeCF(jeView_(),DATA.coa,from,to);',
' var cmpEl=document.getElementById("cfCmp");var cmp=cmpEl?cmpEl.value:"none";var prior=null;',
' if(cmp!=="none"){var pf,pt;if(cmp==="sppy"){pf=shiftYear(from,-1);pt=shiftYear(to,-1);}else{var cdEl=document.getElementById("cfCustomDate");var cdv=cdEl?cdEl.value:"";if(cdv){pf=cdv;var dur=Math.round((new Date(to+"T00:00:00")-new Date(from+"T00:00:00"))/86400000);pt=addDaysISO(cdv,dur);}}if(pf&&pt)prior=computeCF(jeView_(),DATA.coa,pf,pt);}',
' document.getElementById("report").innerHTML=renderCF(data,prior);',
'}',
'function renderCF(data,prior){',
' var method=CF_STATE.method;',
' var methodNote=method==="direct"?"Direct Method":"Indirect Method";',
' var meta=getReportMetadata();',
' var fromLong=formatDateLong(data.from),toLong=formatDateLong(data.to);',
' var h="<div class=\\"report-block\\">";',
' h+="<div class=\\"report-card\\">";',
' h+="<div class=\\"report-hdr\\">";',
' h+="<div class=\\"report-biz\\">"+esc(meta.businessName)+"</div>";',
' h+="<div class=\\"report-title\\">Statement of Cash Flows</div>";',
' h+="<div class=\\"report-date\\">For the period "+fromLong+" to "+toLong+"</div>";',
' h+="<div class=\\"report-currency\\">"+methodNote+" \\u2022 (Amounts in Philippine Peso)</div>";',
' h+="</div>";',
// Warning if methods don't reconcile to actual cash change
' var activeMatch=method==="direct"?data.directMatch:data.indirectMatch;',
' if(!activeMatch){',
' h+="<div class=\\"cf-warning\\">\\u26A0 Calculated net change ("+fmtCF(method==="direct"?data.direct.netChange:data.indirect.netChange)+") does not match actual cash change ("+fmtCF(data.netChangeActual)+"). Possible classification issue. Difference: "+fmtCF((method==="direct"?data.direct.netChange:data.indirect.netChange)-data.netChangeActual)+"</div>";',
' }',
' if(prior){h+=renderCFCompare(data,prior,CF_STATE.showVariance===true);}',
' else if(method==="direct")h+=renderCFDirect(data);',
' else h+=renderCFIndirect(data);',
' if(data.preBooks||(prior&&prior.preBooks)){h+="<div style=\\"font-size:11px;color:#6B7280;margin-top:8px\\">Periods on or before the Books Start Date ("+esc(data.booksStartDate)+") are pre-books periods - opening balances are positions taken up, not cash flows, so no activity is shown.</div>";}',
' if(String((typeof DATA!=="undefined"&&DATA.settings&&(DATA.settings.regType||DATA.settings.registrationType))||"").toUpperCase().indexOf("NON")<0){h+="<div style=\\"font-size:11px;color:#6B7280;margin-top:4px\\">Cash flows are presented net of recoverable input VAT; such VAT is reported within operating activities (IAS 7).</div>";}',
' h+="</div></div>";',
' return h;',
'}',
'function formatDateLong(iso){if(!iso||iso.length<10)return iso||"";var d=new Date(iso+"T00:00:00");var months=["January","February","March","April","May","June","July","August","September","October","November","December"];return months[d.getMonth()]+" "+d.getDate()+", "+d.getFullYear();}',
'',
'function renderCFDirect(data){',
' var d=data.direct;var h="";',
' h+="<table class=\\"cf-table\\">";',
' h+="<thead><tr><th class=\\"cf-descr\\">Description</th><th class=\\"cf-amount-h\\">Amount</th></tr></thead>";',
' h+="<tbody>";',
// Operating
' h+="<tr class=\\"cf-section-hdr\\"><td colspan=\\"2\\">Cash Flow from Operating Activities</td></tr>";',
' for(var i=0;i<d.operating.length;i++){var line=d.operating[i];if(Math.abs(line.amount)<0.01)continue;h+="<tr><td class=\\"cf-line\\">"+esc(line.label)+"</td><td class=\\"cf-amount cf-drill\\" onclick=\\"drillFromCF(\'"+line.accounts.join("|")+"\',\'"+data.from+"\',\'"+data.to+"\')\\">"+fmtCF(line.amount)+"</td></tr>";}',
' h+="<tr class=\\"cf-subtotal\\"><td>Net cash from operating activities</td><td class=\\"cf-amount\\">"+fmtCF(d.operatingTotal)+"</td></tr>";',
// Investing
' h+="<tr class=\\"cf-section-hdr\\"><td colspan=\\"2\\">Cash Flow from Investing Activities</td></tr>";',
' if(d.investing.length===0){h+="<tr><td class=\\"cf-line cf-muted\\">Purchase of fixed assets</td><td class=\\"cf-amount cf-muted\\">\-</td></tr>";}',
' else{for(var j=0;j<d.investing.length;j++){var il=d.investing[j];h+="<tr><td class=\\"cf-line\\">"+esc(il.label)+"</td><td class=\\"cf-amount cf-drill\\" onclick=\\"drillFromCF(\'"+il.accounts.join("|")+"\',\'"+data.from+"\',\'"+data.to+"\')\\">"+fmtCF(il.amount)+"</td></tr>";}}',
' h+="<tr class=\\"cf-subtotal\\"><td>Net cash from investing activities</td><td class=\\"cf-amount\\">"+fmtCF(d.investingTotal)+"</td></tr>";',
// Financing
' h+="<tr class=\\"cf-section-hdr\\"><td colspan=\\"2\\">Cash Flow from Financing Activities</td></tr>";',
' if(d.financing.length===0){h+="<tr><td class=\\"cf-line cf-muted\\">No financing activities</td><td class=\\"cf-amount cf-muted\\">\-</td></tr>";}',
' else{for(var k=0;k<d.financing.length;k++){var fl=d.financing[k];h+="<tr><td class=\\"cf-line\\">"+esc(fl.label)+"</td><td class=\\"cf-amount cf-drill\\" onclick=\\"drillFromCF(\'"+fl.accounts.join("|")+"\',\'"+data.from+"\',\'"+data.to+"\')\\">"+fmtCF(fl.amount)+"</td></tr>";}}',
' h+="<tr class=\\"cf-subtotal\\"><td>Net cash from financing activities</td><td class=\\"cf-amount\\">"+fmtCF(d.financingTotal)+"</td></tr>";',
// Total
' h+="<tr class=\\"cf-total\\"><td>Net increase (decrease) in cash</td><td class=\\"cf-amount\\">"+fmtCF(d.netChange)+"</td></tr>";',
' h+="<tr><td class=\\"cf-line\\">Cash, beginning of period</td><td class=\\"cf-amount\\">"+fmtCF(data.beginCash)+"</td></tr>";',
' h+="<tr class=\\"cf-total cf-grand\\"><td>Cash, end of period</td><td class=\\"cf-amount\\">"+fmtCF(data.endCash)+"</td></tr>";',
' h+="</tbody></table>";',
' return h;',
'}',
'',
'function renderCFIndirect(data){',
' var ind=data.indirect;var h="";',
' h+="<table class=\\"cf-table\\">";',
' h+="<thead><tr><th class=\\"cf-descr\\">Description</th><th class=\\"cf-amount-h\\">Amount</th></tr></thead>";',
' h+="<tbody>";',
// Operating
' h+="<tr class=\\"cf-section-hdr\\"><td colspan=\\"2\\">Cash Flow from Operating Activities</td></tr>";',
' h+="<tr><td class=\\"cf-line\\">Net income</td><td class=\\"cf-amount cf-drill\\" onclick=\\"switchTab(\'is\')\\">"+fmtCF(ind.netIncome)+"</td></tr>";',
' if(ind.nonCash.length>0){',
' h+="<tr><td colspan=\\"2\\" class=\\"cf-grp-label\\">Adjustments for non-cash items:</td></tr>";',
' for(var n=0;n<ind.nonCash.length;n++){var nc=ind.nonCash[n];h+="<tr><td class=\\"cf-line cf-indent\\">"+esc(nc.label)+"</td><td class=\\"cf-amount cf-drill\\" onclick=\\"drillFromCF(\'"+nc.accounts.join("|")+"\',\'"+data.from+"\',\'"+data.to+"\')\\">"+fmtCF(nc.amount)+"</td></tr>";}',
' }',
' if(ind.workingCapital.length>0){',
' h+="<tr><td colspan=\\"2\\" class=\\"cf-grp-label\\">Changes in working capital:</td></tr>";',
' for(var w=0;w<ind.workingCapital.length;w++){var wc=ind.workingCapital[w];if(Math.abs(wc.amount)<0.01)continue;h+="<tr><td class=\\"cf-line cf-indent\\">"+esc(wc.label)+"</td><td class=\\"cf-amount cf-drill\\" onclick=\\"drillFromCF(\'"+wc.accounts.join("|")+"\',\'"+data.from+"\',\'"+data.to+"\')\\">"+fmtCF(wc.amount)+"</td></tr>";}',
' }',
' h+="<tr class=\\"cf-subtotal\\"><td>Net cash from operating activities</td><td class=\\"cf-amount\\">"+fmtCF(ind.operatingTotal)+"</td></tr>";',
// Investing
' h+="<tr class=\\"cf-section-hdr\\"><td colspan=\\"2\\">Cash Flow from Investing Activities</td></tr>";',
' if(ind.investing.length===0){h+="<tr><td class=\\"cf-line cf-muted\\">Purchase of fixed assets</td><td class=\\"cf-amount cf-muted\\">\-</td></tr>";}',
' else{for(var j=0;j<ind.investing.length;j++){var il2=ind.investing[j];h+="<tr><td class=\\"cf-line\\">"+esc(il2.label)+"</td><td class=\\"cf-amount cf-drill\\" onclick=\\"drillFromCF(\'"+il2.accounts.join("|")+"\',\'"+data.from+"\',\'"+data.to+"\')\\">"+fmtCF(il2.amount)+"</td></tr>";}}',
' h+="<tr class=\\"cf-subtotal\\"><td>Net cash from investing activities</td><td class=\\"cf-amount\\">"+fmtCF(ind.investingTotal)+"</td></tr>";',
// Financing
' h+="<tr class=\\"cf-section-hdr\\"><td colspan=\\"2\\">Cash Flow from Financing Activities</td></tr>";',
' if(ind.financing.length===0){h+="<tr><td class=\\"cf-line cf-muted\\">No financing activities</td><td class=\\"cf-amount cf-muted\\">\-</td></tr>";}',
' else{for(var k=0;k<ind.financing.length;k++){var fl2=ind.financing[k];h+="<tr><td class=\\"cf-line\\">"+esc(fl2.label)+"</td><td class=\\"cf-amount cf-drill\\" onclick=\\"drillFromCF(\'"+fl2.accounts.join("|")+"\',\'"+data.from+"\',\'"+data.to+"\')\\">"+fmtCF(fl2.amount)+"</td></tr>";}}',
' h+="<tr class=\\"cf-subtotal\\"><td>Net cash from financing activities</td><td class=\\"cf-amount\\">"+fmtCF(ind.financingTotal)+"</td></tr>";',
// Total
' h+="<tr class=\\"cf-total\\"><td>Net increase (decrease) in cash</td><td class=\\"cf-amount\\">"+fmtCF(ind.netChange)+"</td></tr>";',
' h+="<tr><td class=\\"cf-line\\">Cash, beginning of period</td><td class=\\"cf-amount\\">"+fmtCF(data.beginCash)+"</td></tr>";',
' h+="<tr class=\\"cf-total cf-grand\\"><td>Cash, end of period</td><td class=\\"cf-amount\\">"+fmtCF(data.endCash)+"</td></tr>";',
' h+="</tbody></table>";',
' return h;',
'}',
'',
'/* ── CF Drill-down ── */',
'function cfMergeLines(cur,pri){var map={},order=[];for(var i=0;i<cur.length;i++){var l=cur[i];if(!map[l.label]){map[l.label]={label:l.label,accounts:l.accounts||[],cur:0,pri:0};order.push(l.label);}map[l.label].cur+=l.amount;if(l.accounts)map[l.label].accounts=l.accounts;}for(var j=0;j<pri.length;j++){var p=pri[j];if(!map[p.label]){map[p.label]={label:p.label,accounts:p.accounts||[],cur:0,pri:0};order.push(p.label);}map[p.label].pri+=p.amount;}return order.map(function(k){return map[k];});}',
'function cfOpLines_(data){if(CF_STATE.method==="direct")return data.direct.operating.slice();var ind=data.indirect,arr=[{label:"Net income",amount:ind.netIncome,accounts:[]}];for(var n=0;n<ind.nonCash.length;n++)arr.push(ind.nonCash[n]);for(var w=0;w<ind.workingCapital.length;w++)arr.push(ind.workingCapital[w]);return arr;}',
'function cfOpTotal_(data){return CF_STATE.method==="direct"?data.direct.operatingTotal:data.indirect.operatingTotal;}',
'function cfInv_(data){return CF_STATE.method==="direct"?data.direct.investing:data.indirect.investing;}',
'function cfInvTotal_(data){return CF_STATE.method==="direct"?data.direct.investingTotal:data.indirect.investingTotal;}',
'function cfFin_(data){return CF_STATE.method==="direct"?data.direct.financing:data.indirect.financing;}',
'function cfFinTotal_(data){return CF_STATE.method==="direct"?data.direct.financingTotal:data.indirect.financingTotal;}',
'function cfNetChange_(data){return CF_STATE.method==="direct"?data.direct.netChange:data.indirect.netChange;}',
'function renderCFCompare(data,prior,showVar){var nCols=showVar?4:3;function vcell(c,p){if(!showVar)return"";return "<td class=\\"cf-amount\\">"+fmtCF(c-p)+"</td>";}function section(title,curLines,priLines,curTot,priTot,subLabel){var h="<tr class=\\"cf-section-hdr\\"><td colspan=\\""+nCols+"\\">"+title+"</td></tr>";var merged=cfMergeLines(curLines,priLines);var shown=0;for(var i=0;i<merged.length;i++){var m=merged[i];if(Math.abs(m.cur)<0.01&&Math.abs(m.pri)<0.01)continue;shown++;h+="<tr><td class=\\"cf-line\\">"+esc(m.label)+"</td><td class=\\"cf-amount\\">"+fmtCF(m.cur)+"</td><td class=\\"cf-amount\\">"+fmtCF(m.pri)+"</td>"+vcell(m.cur,m.pri)+"</tr>";}if(shown===0)h+="<tr><td class=\\"cf-line cf-muted\\">None</td><td class=\\"cf-amount cf-muted\\">-</td><td class=\\"cf-amount cf-muted\\">-</td>"+(showVar?"<td class=\\"cf-amount cf-muted\\">-</td>":"")+"</tr>";h+="<tr class=\\"cf-subtotal\\"><td>"+subLabel+"</td><td class=\\"cf-amount\\">"+fmtCF(curTot)+"</td><td class=\\"cf-amount\\">"+fmtCF(priTot)+"</td>"+vcell(curTot,priTot)+"</tr>";return h;}var h="<table class=\\"cf-table\\"><thead><tr><th class=\\"cf-descr\\">Description</th><th class=\\"cf-amount-h\\">Current</th><th class=\\"cf-amount-h\\">Prior</th>"+(showVar?"<th class=\\"cf-amount-h\\">Variance</th>":"")+"</tr></thead><tbody>";h+=section("Cash Flow from Operating Activities",cfOpLines_(data),cfOpLines_(prior),cfOpTotal_(data),cfOpTotal_(prior),"Net cash from operating activities");h+=section("Cash Flow from Investing Activities",cfInv_(data),cfInv_(prior),cfInvTotal_(data),cfInvTotal_(prior),"Net cash from investing activities");h+=section("Cash Flow from Financing Activities",cfFin_(data),cfFin_(prior),cfFinTotal_(data),cfFinTotal_(prior),"Net cash from financing activities");h+="<tr class=\\"cf-total\\"><td>Net increase (decrease) in cash</td><td class=\\"cf-amount\\">"+fmtCF(cfNetChange_(data))+"</td><td class=\\"cf-amount\\">"+fmtCF(cfNetChange_(prior))+"</td>"+vcell(cfNetChange_(data),cfNetChange_(prior))+"</tr>";h+="<tr><td class=\\"cf-line\\">Cash, beginning of period</td><td class=\\"cf-amount\\">"+fmtCF(data.beginCash)+"</td><td class=\\"cf-amount\\">"+fmtCF(prior.beginCash)+"</td>"+vcell(data.beginCash,prior.beginCash)+"</tr>";h+="<tr class=\\"cf-total cf-grand\\"><td>Cash, end of period</td><td class=\\"cf-amount\\">"+fmtCF(data.endCash)+"</td><td class=\\"cf-amount\\">"+fmtCF(prior.endCash)+"</td>"+vcell(data.endCash,prior.endCash)+"</tr>";h+="</tbody></table>";return h;}',
'function drillFromCF(accountsStr,fromDate,toDate){',
' var accts=accountsStr.split("|").filter(function(x){return x&&x.length>0});',
' DRILL_CONTEXT={from:"cf",prevState:null};',
' GL_STATE.selectedAccounts={};for(var i=0;i<accts.length;i++)GL_STATE.selectedAccounts[accts[i]]=true;',
' GL_STATE.page=1;GL_STATE.groupMode="account";',
' GL_STATE._pendingFrom=fromDate;GL_STATE._pendingTo=toDate;GL_STATE._pendingPreset="custom";',
' switchTab("gl");',
'}',
'',
'/* ══════════════════════════════════════',
' STATEMENT OF CHANGES IN EQUITY (SCE)',
' ══════════════════════════════════════ */',
'',
'/* ── SCE Helpers ── */',
'function isOwnersWithdrawal(title){return /withdrawal|drawing/i.test(String(title||""));}',
'function isOwnersCapital(title){var t=String(title||"");return /capital/i.test(t)&&!/withdrawal|drawing/i.test(t);}',
'',
'/* ── SCE Main Computation ── */',
'function computeSCEPeriod(jeRows,coa,from,to){',
' var begDate=addDaysISO(from,-1);',
' var begBal=computeBalancesAt(jeRows,begDate);',
// Build account type map
' var typeMap={};for(var i=0;i<coa.length;i++)typeMap[coa[i].title]=coa[i].type;',
// Beginning Capital = -(Equity + cumulative Rev/Exp before period start)
' var begEquity=0,begRevExp=0;',
' for(var t in begBal){var ty=typeMap[t]||"";if(ty==="Equity")begEquity+=begBal[t];else if(ty==="Revenue"||ty==="Expense")begRevExp+=begBal[t];}',
' var beginningCapital=-(begEquity+begRevExp);',
// Period activity
' var act={};',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(!je.date||je.date<from||je.date>to)continue;var a=je.account;if(!act[a])act[a]=0;act[a]+=(parseFloat(je.debit)||0)-(parseFloat(je.credit)||0);}',
// Net Income for period = -(sum of Rev/Exp deltas)
' var revExpDelta=0;',
' for(var a1 in act){var ty1=typeMap[a1]||"";if(ty1==="Revenue"||ty1==="Expense")revExpDelta+=act[a1];}',
' var netIncome=-revExpDelta;',
// Withdrawals (DR delta on Owner's Withdrawals accounts)
' var withdrawals=0,withdrawalAccts=[];',
' for(var a2 in act){if(typeMap[a2]==="Equity"&&isOwnersWithdrawal(a2)){withdrawals+=act[a2];withdrawalAccts.push(a2);}}',
// Additional Investments (CR delta on Owner's Capital, i.e., negative DR-CR delta)
' var contributions=0,contributionAccts=[];',
' for(var a3 in act){if(typeMap[a3]==="Equity"&&isOwnersCapital(a3)){contributions+=-act[a3];contributionAccts.push(a3);}}',
// Income/Expense accounts for drilldown
' var revExpAccts=[];for(var k=0;k<coa.length;k++){if(coa[k].type==="Revenue"||coa[k].type==="Expense")revExpAccts.push(coa[k].title);}',
// Ending
' var ending=beginningCapital+netIncome+contributions-withdrawals;',
' return {beginningCapital:beginningCapital,netIncome:netIncome,contributions:contributions,withdrawals:withdrawals,ending:ending,from:from,to:to,withdrawalAccts:withdrawalAccts,contributionAccts:contributionAccts,revExpAccts:revExpAccts};',
'}',
'',
'function computeSCE(jeRows,coa,from,to,cmpMode,customCmpFrom,customCmpTo){',
' var cy=computeSCEPeriod(jeRows,coa,from,to);',
' var py=null;',
' if(cmpMode==="py"){',
// Prior year same period (CY shifted back one year)
' var pyFrom=shiftYear(from,-1);var pyTo=shiftYear(to,-1);',
' py=computeSCEPeriod(jeRows,coa,pyFrom,pyTo);',
' }else if(cmpMode==="custom"&&customCmpFrom&&customCmpTo){',
' py=computeSCEPeriod(jeRows,coa,customCmpFrom,customCmpTo);',
' }',
' return {cy:cy,py:py};',
'}',
'function shiftYear(iso,n){var d=new Date(iso+"T00:00:00");d.setFullYear(d.getFullYear()+n);var y=d.getFullYear(),m=d.getMonth()+1,dd=d.getDate();return y+"-"+(m<10?"0":"")+m+"-"+(dd<10?"0":"")+dd;}',
'',
'/* ── SCE Controls ── */',
'function buildSCE(){buildSCEControls();recomputeSCE();}',
'function buildSCEControls(){',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(function(d){return d&&d.length>=10}).sort();',
' var minD=dates[0]||"2025-01-01",maxD=dates[dates.length-1]||"2026-12-31";',
' var h="<div class=\\"ctrl-bar\\">";',
' h+="<div class=\\"ctrl-group\\"><label>Date Range</label><select id=\\"scePreset\\" onchange=\\"applySCEPreset()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' h+="<option value=\\"tfy\\" selected>This Financial Year</option>";',
' h+="<option value=\\"ytd\\">Year-to-Date</option>";',
' h+="<option value=\\"lfy\\">Last Financial Year</option>";',
' h+="<option value=\\"tq\\">This Quarter</option>";',
' h+="<option value=\\"qtd\\">Quarter-to-Date</option>";',
' h+="<option value=\\"lq\\">Last Quarter</option>";',
' h+="<option value=\\"tm\\">This Month</option>";',
' h+="<option value=\\"mtd\\">Month-to-Date</option>";',
' h+="<option value=\\"lm\\">Last Month</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"q1\\">Quarter 1</option><option value=\\"q2\\">Quarter 2</option><option value=\\"q3\\">Quarter 3</option><option value=\\"q4\\">Quarter 4</option>";',
' h+="<option disabled>\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500</option>";',
' h+="<option value=\\"custom\\">Custom</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\"><label>From</label><input type=\\"date\\" id=\\"sceFrom\\" value=\\""+minD+"\\" onchange=\\"document.getElementById(\'scePreset\').value=\'custom\';recomputeSCE()\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>To</label><input type=\\"date\\" id=\\"sceTo\\" value=\\""+maxD+"\\" onchange=\\"document.getElementById(\'scePreset\').value=\'custom\';recomputeSCE()\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Compare</label><select id=\\"sceCmp\\" onchange=\\"onSCECmpChange()\\">";',
' h+="<option value=\\"py\\" selected>Prior Year</option>";',
' h+="<option value=\\"custom\\">Custom</option>";',
' h+="<option value=\\"none\\">None</option>";',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"sceCustomGrp\\" style=\\"display:none\\"><label>Compare From</label><input type=\\"date\\" id=\\"sceCmpFrom\\" value=\\""+minD+"\\" onchange=\\"recomputeSCE()\\"></div>";',
' h+="<div class=\\"ctrl-group\\" id=\\"sceCustomGrp2\\" style=\\"display:none\\"><label>Compare To</label><input type=\\"date\\" id=\\"sceCmpTo\\" value=\\""+maxD+"\\" onchange=\\"recomputeSCE()\\"></div>";',
' h+="<div class=\\"ctrl-group\\" style=\\"position:relative\\"><label> </label><button id=\\"sceMoreBtn\\" class=\\"ctrl-dropdown-btn\\" onclick=\\"toggleSCEMore()\\">More \\u25BE</button><div id=\\"sceMoreWrap\\"></div></div>";',
' h+="</div>";',
' document.getElementById("controls").innerHTML=h;',
' applySCEPreset();',
'}',
'function applySCEPreset(){var p=document.getElementById("scePreset").value;if(p==="custom")return;var dates=DATA.jeRows.map(function(e){return e.date}).filter(function(d){return d&&d.length>=10}).sort();var maxD=dates[dates.length-1]||"2026-12-31";var now=new Date(maxD+"T00:00:00");var y=now.getFullYear(),m=now.getMonth()+1;var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var from,to;',
' function fyBounds(year){var sY=year,sM=fyM;var f=sY+"-"+(sM<10?"0":"")+sM+"-01";var eY=fyM===1?sY:sY+1;var eM=fyM===1?12:fyM-1;var eD=ld(eM,eY);var t=eY+"-"+(eM<10?"0":"")+eM+"-"+(eD<10?"0":"")+eD;return{from:f,to:t};}',
' function isCurFY(){return(fyM===1)?true:(m>=fyM);}',
' var curFYY=isCurFY()?y:y-1;',
' if(p==="tfy"){var b=fyBounds(curFYY);from=b.from;to=b.to;}',
' else if(p==="ytd"){var b2=fyBounds(curFYY);from=b2.from;to=maxD;}',
' else if(p==="lfy"){var b3=fyBounds(curFYY-1);from=b3.from;to=b3.to;}',
' else if(p==="tq"){var qStart=Math.floor((m-1)/3)*3+1;from=y+"-"+(qStart<10?"0":"")+qStart+"-01";var qE=qStart+2;var qED=ld(qE,y);to=y+"-"+(qE<10?"0":"")+qE+"-"+(qED<10?"0":"")+qED;}',
' else if(p==="qtd"){var qStart2=Math.floor((m-1)/3)*3+1;from=y+"-"+(qStart2<10?"0":"")+qStart2+"-01";to=maxD;}',
' else if(p==="lq"){var qStart3=Math.floor((m-1)/3)*3+1;var lqS=qStart3-3;var lqY=y;if(lqS<=0){lqS+=12;lqY-=1;}from=lqY+"-"+(lqS<10?"0":"")+lqS+"-01";var lqE=lqS+2;var lqED=ld(lqE,lqY);to=lqY+"-"+(lqE<10?"0":"")+lqE+"-"+(lqED<10?"0":"")+lqED;}',
' else if(p==="tm"){from=y+"-"+(m<10?"0":"")+m+"-01";var mED=ld(m,y);to=y+"-"+(m<10?"0":"")+m+"-"+(mED<10?"0":"")+mED;}',
' else if(p==="mtd"){from=y+"-"+(m<10?"0":"")+m+"-01";to=maxD;}',
' else if(p==="lm"){var lmM=m-1,lmY=y;if(lmM<1){lmM=12;lmY-=1;}from=lmY+"-"+(lmM<10?"0":"")+lmM+"-01";var lmED=ld(lmM,lmY);to=lmY+"-"+(lmM<10?"0":"")+lmM+"-"+(lmED<10?"0":"")+lmED;}',
' else if(p==="q1"||p==="q2"||p==="q3"||p==="q4"){var qNum=parseInt(p.substring(1));var qS=(qNum-1)*3+1,qE2=qS+2;from=y+"-"+(qS<10?"0":"")+qS+"-01";var qED2=ld(qE2,y);to=y+"-"+(qE2<10?"0":"")+qE2+"-"+(qED2<10?"0":"")+qED2;}',
' if(from&&to){document.getElementById("sceFrom").value=from;document.getElementById("sceTo").value=to;recomputeSCE();}',
'}',
'function onSCECmpChange(){var m=document.getElementById("sceCmp").value;document.getElementById("sceCustomGrp").style.display=m==="custom"?"flex":"none";document.getElementById("sceCustomGrp2").style.display=m==="custom"?"flex":"none";if(m==="none")SCE_STATE.showVariance=false;refreshSCEMorePanel();recomputeSCE();}',
'',
'/* ── SCE More Panel ── */',
'function toggleSCEMore(){var wrap=document.getElementById("sceMoreWrap");if(wrap.innerHTML){wrap.innerHTML="";return;}wrap.innerHTML=renderSCEMorePanel();}',
'function refreshSCEMorePanel(){var wrap=document.getElementById("sceMoreWrap");if(wrap&&wrap.innerHTML)wrap.innerHTML=renderSCEMorePanel();}',
'function renderSCEMorePanel(){',
' var cmp=document.getElementById("sceCmp"),cmpVal=cmp?cmp.value:"py";',
' var h="<div class=\\"panel\\" style=\\"width:280px;right:0;left:auto\\">";',
' h+="<div class=\\"panel-hdr\\"><span class=\\"panel-title\\">More</span><button class=\\"panel-close\\" onclick=\\"toggleSCEMore()\\">\\u2715</button></div>";',
// Variance (only if comparing)
' if(cmpVal!=="none"){',
' h+="<div class=\\"more-row\\"><span class=\\"more-row-label\\">Variance</span><label class=\\"toggle-switch\\"><input type=\\"checkbox\\" "+(SCE_STATE.showVariance?"checked":"")+" onchange=\\"toggleSCEVar()\\"><span class=\\"toggle-slider\\"></span></label></div>";',
' }',
// Hide Decimals
' h+="<div class=\\"more-row\\"><span class=\\"more-row-label\\">Hide Decimals</span><label class=\\"toggle-switch\\"><input type=\\"checkbox\\" "+(APP_STATE.hideDecimals?"checked":"")+" onchange=\\"toggleSCEHideDecimals(this.checked)\\"><span class=\\"toggle-slider\\"></span></label></div>";',
' h+="</div>";',
' return h;',
'}',
'function toggleSCEVar(){SCE_STATE.showVariance=!SCE_STATE.showVariance;refreshSCEMorePanel();recomputeSCE();}',
'function toggleSCEHideDecimals(v){APP_STATE.hideDecimals=v;recomputeSCE();}',
'',
'/* ── SCE Compute Driver + Render ── */',
'function recomputeSCE(){if(!document.getElementById("sceFrom"))return;',
' if(!SKEL_SUPPRESS){showSkeleton("sce");SKEL_SUPPRESS=true;setTimeout(function(){recomputeSCE();SKEL_SUPPRESS=false;fadeInReport("sce");},200);return;}',
' var from=document.getElementById("sceFrom").value;var to=document.getElementById("sceTo").value;',
' SCE_STATE.from=from;SCE_STATE.to=to;',
' var cmp=document.getElementById("sceCmp").value;SCE_STATE.compareMode=cmp;',
' var cmpFrom="",cmpTo="";if(cmp==="custom"){cmpFrom=document.getElementById("sceCmpFrom").value;cmpTo=document.getElementById("sceCmpTo").value;}',
' if(!from||!to){document.getElementById("report").innerHTML="<div style=\\"padding:20px;color:#6B7280\\">Please select From and To dates.</div>";return;}',
' if(sceIsCorp_()){document.getElementById("report").innerHTML=renderSCECorp(computeSCECorp(jeView_(),DATA.coa,from,to));return;}',
' var data=computeSCE(jeView_(),DATA.coa,from,to,cmp,cmpFrom,cmpTo);',
' document.getElementById("report").innerHTML=renderSCE(data);',
'}',
'',
'/* ── SCE CORP (2026-06-05, D3/D4/D5): columnar Share Capital | Retained Earnings | Total. */',
'/* Direct-RE entries classified STRUCTURALLY by counterpart legs in the same Entry ID group: */',
'/* with Dividends Payable CR or Cash CR -> Dividends declared; anything else -> Prior period adjustment. */',
'function sceIsCorp_(){return String((DATA.settings&&DATA.settings.taxpayerClass)||"").toUpperCase().indexOf("NON")===0;}',
'function computeSCECorp(jeRows,coa,from,to){',
' var scAccts=[],reT="",cashSet={},divPaySet={},typeMap={};',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;var t=String(a.title).trim(),tp=String(a.type).trim();typeMap[t]=tp;',
' if(tp==="Equity"){if(t==="Retained Earnings")reT=t;else scAccts.push(t);}',
' if(tp==="Liability"&&/dividend/i.test(t))divPaySet[t]=1;',
' if(classifyForCF(a)==="Cash")cashSet[t]=1;}',
' var begDate=addDaysISO(from,-1);',
' var begBal=computeBalancesAt(jeRows,begDate);',
' var begSC=0,begREraw=0,begRevExp=0;',
' for(var t2 in begBal){var ty=typeMap[t2]||"";',
' if(ty==="Equity"){if(t2===reT)begREraw+=begBal[t2];else begSC+=begBal[t2];}',
' else if(ty==="Revenue"||ty==="Expense")begRevExp+=begBal[t2];}',
' begSC=-begSC;var begRE=-(begREraw+begRevExp);',
// Period activity: NI + share capital movements
' var revExpDelta=0,scDelta=0;',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(!je.date||je.date<from||je.date>to)continue;var ty2=typeMap[je.account]||"";var d2=(parseFloat(je.debit)||0)-(parseFloat(je.credit)||0);',
' if(ty2==="Revenue"||ty2==="Expense")revExpDelta+=d2;',
' else if(ty2==="Equity"&&je.account!==reT)scDelta+=d2;}',
' var netIncome=-revExpDelta;var issuances=-scDelta;',
// Direct-RE entries within period, grouped by Entry ID
' var grp={};',
' for(var g1=0;g1<jeRows.length;g1++){var jr=jeRows[g1];if(!jr.date||jr.date<from||jr.date>to)continue;var gk=(jr.entryId||"")+"|"+(jr.date||"")+"|"+(jr.ref||"")+"|"+(jr.source||"");if(!grp[gk])grp[gk]=[];grp[gk].push(jr);}',
' var dividends=0,ppa=0;',
' for(var gk2 in grp){var legs=grp[gk2];var reNet=0,hasRE=false,hasDivOrCashCR=false;',
' for(var l1=0;l1<legs.length;l1++){var lg=legs[l1];',
' if(lg.account===reT){hasRE=true;reNet+=(parseFloat(lg.debit)||0)-(parseFloat(lg.credit)||0);}',
' else if((divPaySet[lg.account]||cashSet[lg.account])&&(parseFloat(lg.credit)||0)>0.004)hasDivOrCashCR=true;}',
' if(!hasRE||Math.abs(reNet)<0.005)continue;',
' if(reNet>0&&hasDivOrCashCR)dividends+=reNet;',
' else ppa+=reNet;}',
' dividends=rnd(dividends);ppa=rnd(ppa);',
' var endSC=rnd(begSC+issuances);',
' var endRE=rnd(begRE+netIncome-dividends-ppa);',
' var revExpAccts=[];for(var k3=0;k3<coa.length;k3++){if(coa[k3].type==="Revenue"||coa[k3].type==="Expense")revExpAccts.push(coa[k3].title);}',
' return{from:from,to:to,begSC:rnd(begSC),begRE:rnd(begRE),netIncome:rnd(netIncome),issuances:rnd(issuances),dividends:dividends,ppa:ppa,endSC:endSC,endRE:endRE,scAccts:scAccts,reT:reT,revExpAccts:revExpAccts};',
'}',
'',
'function renderSCECorp(d){',
' var meta=getReportMetadata();',
' var q=String.fromCharCode(39);',
' var h="<div class=\\"report-block\\"><div class=\\"report-card\\">";',
' h+="<div class=\\"report-hdr\\">";',
' h+="<div class=\\"report-biz\\">"+esc(meta.businessName)+"</div>";',
' h+="<div class=\\"report-title\\">Statement of Changes in Equity</div>";',
' h+="<div class=\\"report-date\\">For the period ended "+formatDateLong(d.to)+"</div>";',
' h+="<div class=\\"report-currency\\">(Amounts in Philippine Peso)</div>";',
' h+="</div>";',
' h+="<table class=\\"cf-table\\">";',
' h+="<thead><tr><th class=\\"cf-descr\\">Particulars</th><th class=\\"cf-amount-h\\">Share Capital</th><th class=\\"cf-amount-h\\">Retained Earnings</th><th class=\\"cf-amount-h\\">Total</th></tr></thead><tbody>";',
' function row3(lbl,sc,re,cls,drill){var tot=(sc===null?0:sc)+(re===null?0:re);var r="<tr"+(cls?" class=\\""+cls+"\\"":"")+"><td class=\\""+(cls?"":"cf-line")+"\\">"+lbl+"</td>";',
' r+="<td class=\\"cf-amount\\">"+(sc===null?"-":fmtCF(sc))+"</td>";',
' if(drill)r+="<td class=\\"cf-amount cf-drill\\" onclick=\\""+drill+"\\">"+(re===null?"-":fmtCF(re))+"</td>";',
' else r+="<td class=\\"cf-amount\\">"+(re===null?"-":fmtCF(re))+"</td>";',
' r+="<td class=\\"cf-amount\\">"+fmtCF(tot)+"</td></tr>";return r;}',
' var drillNI="drillSCEFromAccts("+q+escJS(d.revExpAccts.join("|"))+q+","+q+d.from+q+","+q+d.to+q+")";',
' var drillRE="drillSCEFromAccts("+q+escJS(d.reT)+q+","+q+d.from+q+","+q+d.to+q+")";',
' h+=row3("Balance at beginning of period",d.begSC,d.begRE,"sce-bal",null);',
' if(Math.abs(d.issuances)>0.005)h+=row3("Share issuances",d.issuances,null,"",null);',
' h+=row3(d.netIncome>=0?"Net income for the period":"Net loss for the period",null,d.netIncome,"",drillNI);',
' if(Math.abs(d.dividends)>0.005)h+=row3("Dividends declared",null,-d.dividends,"",drillRE);',
' if(Math.abs(d.ppa)>0.005)h+=row3("Prior period adjustments",null,-d.ppa,"",drillRE);',
' h+=row3("Balance at end of period",d.endSC,d.endRE,"cf-total cf-grand",null);',
' h+="</tbody></table></div></div>";',
' return h;',
'}',
'function renderSCE(data){',
' var cy=data.cy,py=data.py;',
' var showCmp=py!==null;',
' var showVar=showCmp&&SCE_STATE.showVariance;',
' var meta=getReportMetadata();',
' var toLong=formatDateLong(cy.to);',
' var h="<div class=\\"report-block\\">";',
' h+="<div class=\\"report-card\\">";',
' h+="<div class=\\"report-hdr\\">";',
' h+="<div class=\\"report-biz\\">"+esc(meta.businessName)+"</div>";',
' h+="<div class=\\"report-title\\">Statement of Changes in Equity</div>";',
' h+="<div class=\\"report-date\\">For the period ended "+toLong+"</div>";',
' h+="<div class=\\"report-currency\\">(Amounts in Philippine Peso)</div>";',
' h+="</div>";',
' h+="<table class=\\"cf-table\\">";',
' h+="<thead><tr><th class=\\"cf-descr\\">Particulars</th>";',
' h+="<th class=\\"cf-amount-h\\">"+formatDateShort(cy.to)+"</th>";',
' if(showCmp)h+="<th class=\\"cf-amount-h\\">"+formatDateShort(py.to)+"</th>";',
' if(showVar)h+="<th class=\\"cf-amount-h\\">Variance</th>";',
' h+="</tr></thead><tbody>";',
// Beginning Capital row
' h+="<tr class=\\"sce-bal\\"><td class=\\"sce-bal-lbl\\">Beginning Capital</td>";',
' h+="<td class=\\"cf-amount\\">"+fmtCF(cy.beginningCapital)+"</td>";',
' if(showCmp)h+="<td class=\\"cf-amount\\">"+fmtCF(py.beginningCapital)+"</td>";',
' if(showVar)h+="<td class=\\"cf-amount\\">"+fmtCF(cy.beginningCapital-py.beginningCapital)+"</td>";',
' h+="</tr>";',
// Net Income / Net Loss row (auto-detect)
' var niLabel=cy.netIncome>=0?"Add: Net Income":"Less: Net Loss";',
' var niCls=cy.netIncome>=0?"":"sce-loss";',
' var niDisplay=cy.netIncome>=0?cy.netIncome:Math.abs(cy.netIncome);',
' var pyNiDisplay=py?(py.netIncome>=0?py.netIncome:Math.abs(py.netIncome)):0;',
' var pyNiLabel=py?(py.netIncome>=0?"":" sce-loss"):"";',
' h+="<tr><td class=\\"cf-line "+niCls+"\\">"+niLabel+"</td>";',
' h+="<td class=\\"cf-amount cf-drill "+niCls+"\\" onclick=\\"drillSCEFromAccts(\'"+cy.revExpAccts.join("|")+"\',\'"+cy.from+"\',\'"+cy.to+"\')\\">"+fmtCF(niDisplay)+"</td>";',
' if(showCmp){h+="<td class=\\"cf-amount cf-drill"+pyNiLabel+"\\" onclick=\\"drillSCEFromAccts(\'"+(py.revExpAccts.join("|"))+"\',\'"+py.from+"\',\'"+py.to+"\')\\">"+fmtCF(pyNiDisplay)+"</td>";}',
' if(showVar)h+="<td class=\\"cf-amount\\">"+fmtCF(cy.netIncome-py.netIncome)+"</td>";',
' h+="</tr>";',
// Additional Investments row (always shown, gray when zero)
' var hasContribCY=Math.abs(cy.contributions)>0.005;',
' var hasContribPY=py&&Math.abs(py.contributions)>0.005;',
' h+="<tr><td class=\\"cf-line"+(hasContribCY||hasContribPY?"":" cf-muted")+"\\">Add: Owner Investments</td>";',
' if(hasContribCY)h+="<td class=\\"cf-amount cf-drill\\" onclick=\\"drillSCEFromAccts(\'"+cy.contributionAccts.join("|")+"\',\'"+cy.from+"\',\'"+cy.to+"\')\\">"+fmtCF(cy.contributions)+"</td>";',
' else h+="<td class=\\"cf-amount cf-muted\\">\-</td>";',
' if(showCmp){if(hasContribPY)h+="<td class=\\"cf-amount cf-drill\\" onclick=\\"drillSCEFromAccts(\'"+py.contributionAccts.join("|")+"\',\'"+py.from+"\',\'"+py.to+"\')\\">"+fmtCF(py.contributions)+"</td>";else h+="<td class=\\"cf-amount cf-muted\\">\-</td>";}',
' if(showVar)h+="<td class=\\"cf-amount\\">"+fmtCF(cy.contributions-py.contributions)+"</td>";',
' h+="</tr>";',
// Withdrawals row (positive number with "Less:" prefix)
' var hasWithCY=Math.abs(cy.withdrawals)>0.005;',
' var hasWithPY=py&&Math.abs(py.withdrawals)>0.005;',
' h+="<tr><td class=\\"cf-line"+(hasWithCY||hasWithPY?"":" cf-muted")+"\\">Less: Withdrawals</td>";',
' if(hasWithCY)h+="<td class=\\"cf-amount cf-drill\\" onclick=\\"drillSCEFromAccts(\'"+cy.withdrawalAccts.join("|")+"\',\'"+cy.from+"\',\'"+cy.to+"\')\\">"+fmtCF(cy.withdrawals)+"</td>";',
' else h+="<td class=\\"cf-amount cf-muted\\">\-</td>";',
' if(showCmp){if(hasWithPY)h+="<td class=\\"cf-amount cf-drill\\" onclick=\\"drillSCEFromAccts(\'"+py.withdrawalAccts.join("|")+"\',\'"+py.from+"\',\'"+py.to+"\')\\">"+fmtCF(py.withdrawals)+"</td>";else h+="<td class=\\"cf-amount cf-muted\\">\-</td>";}',
' if(showVar)h+="<td class=\\"cf-amount\\">"+fmtCF(cy.withdrawals-py.withdrawals)+"</td>";',
' h+="</tr>";',
// Ending Capital row
' h+="<tr class=\\"cf-total cf-grand\\"><td>Ending Capital</td>";',
' h+="<td class=\\"cf-amount\\">"+fmtCF(cy.ending)+"</td>";',
' if(showCmp)h+="<td class=\\"cf-amount\\">"+fmtCF(py.ending)+"</td>";',
' if(showVar)h+="<td class=\\"cf-amount\\">"+fmtCF(cy.ending-py.ending)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table></div></div>";',
' return h;',
'}',
'function formatDateShort(iso){if(!iso||iso.length<10)return iso||"";var d=new Date(iso+"T00:00:00");var months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];return months[d.getMonth()]+" "+d.getDate()+", "+d.getFullYear();}',
'',
'/* ── SCE Drill-down ── */',
'function drillSCEFromAccts(accountsStr,fromDate,toDate){',
' var accts=accountsStr.split("|").filter(function(x){return x&&x.length>0});',
' DRILL_CONTEXT={from:"sce",prevState:null};',
' GL_STATE.selectedAccounts={};for(var i=0;i<accts.length;i++)GL_STATE.selectedAccounts[accts[i]]=true;',
' GL_STATE.page=1;GL_STATE.groupMode="account";',
' GL_STATE._pendingFrom=fromDate;GL_STATE._pendingTo=toDate;GL_STATE._pendingPreset="custom";',
' switchTab("gl");',
'}',
'',
'/* ══════════════════════════════════════',
' FINANCIAL STATEMENTS PACKAGE (Phase 1)',
' ══════════════════════════════════════ */',
'',
'/* ── FS Helpers ── */',
'function getAvailableFYs(){var dates=DATA.jeRows.map(function(e){return e.date}).filter(function(d){return d&&d.length>=10});var years={};var fyM=parseInt(DATA.settings.fyStartMonth)||1;for(var i=0;i<dates.length;i++){var y=parseInt(dates[i].substring(0,4));var m=parseInt(dates[i].substring(5,7));var fy=(fyM===1)?y:(m>=fyM?y:y-1);years[fy]=true;}var arr=[];for(var k in years)arr.push(parseInt(k));arr.sort(function(a,b){return b-a;});return arr;}',
'function fyBoundsForYear(year){var fyM=parseInt(DATA.settings.fyStartMonth)||1;var sM=fyM,sY=year;var from=sY+"-"+(sM<10?"0":"")+sM+"-01";var eY=fyM===1?sY:sY+1;var eM=fyM===1?12:fyM-1;var eD=ld(eM,eY);var to=eY+"-"+(eM<10?"0":"")+eM+"-"+(eD<10?"0":"")+eD;return{from:from,to:to};}',
'function fmtFS(n){if(n===null||n===undefined||isNaN(n))return "";if(n===0)return "-";var abs=Math.abs(n);var s=abs.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});return n<0?"("+s+")":s;}',
'function fyLongLabel(year){var fyM=parseInt(DATA.settings.fyStartMonth)||1;if(fyM===1)return "December 31, "+year;var months=["January","February","March","April","May","June","July","August","September","October","November","December"];var eM=fyM===1?12:fyM-1;var eY=fyM===1?year:year+1;var eD=ld(eM,eY);return months[eM-1]+" "+eD+", "+eY;}',
'',
'/* ── FS Controls ── */',
'var FS_ZOOM=1;',
'function fsApplyZoom_(){var d=document.querySelector(".fs-doc");if(d)d.style.zoom=FS_ZOOM;var p=document.getElementById("fsZoomPct");if(p)p.textContent=Math.round(FS_ZOOM*100)+"%";}',
'function fsZoom(delta){FS_ZOOM=Math.min(2,Math.max(0.5,Math.round((FS_ZOOM+delta)*10)/10));fsApplyZoom_();}',
'function fsZoomReset(){FS_ZOOM=1;fsApplyZoom_();}',
'var RPT_ZOOM=1;',
'function rptApplyZoom_(){var d=document.querySelector(".rpt-doc");if(d)d.style.zoom=RPT_ZOOM;var p=document.getElementById("rptZoomPct");if(p)p.textContent=Math.round(RPT_ZOOM*100)+"%";}',
'function rptZoom(delta){RPT_ZOOM=Math.min(2,Math.max(0.5,Math.round((RPT_ZOOM+delta)*10)/10));rptApplyZoom_();}',
'function rptZoomReset(){RPT_ZOOM=1;rptApplyZoom_();}',
'function buildFS(){buildFSControls();recomputeFS();}',
'function buildFSControls(){',
' var fys=getAvailableFYs();',
' var defaultY=fys[0]||(new Date()).getFullYear();',
' if(!FS_STATE.year)FS_STATE.year=defaultY;',
' var h="<div class=\\"ctrl-bar\\" style=\\"max-width:850px;margin:0 auto\\">";',
' h+="<div class=\\"ctrl-group\\"><label>Year Ended</label><select id=\\"fsYear\\" onchange=\\"recomputeFS()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' for(var i=0;i<fys.length;i++){var y=fys[i];h+="<option value=\\""+y+"\\""+(y===FS_STATE.year?" selected":"")+">"+fyLongLabel(y)+"</option>";}',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Comparative</label><select id=\\"fsCmp\\" onchange=\\"recomputeFS()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' for(var c=0;c<=4;c++){h+="<option value=\\""+c+"\\""+(c===FS_STATE.compareYears?" selected":"")+">"+(c===0?"None":(c+" prior year"+(c>1?"s":"")))+"</option>";}',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Cash Flow</label><select id=\\"fsCfMethod\\" onchange=\\"onFSCfMethodChange()\\" style=\\"padding:5px 8px;border:1px solid #D0D0D0;border-radius:4px;font-size:13px;color:var(--navy);font-family:inherit\\">";',
' h+="<option value=\\"indirect\\""+(FS_STATE.cfMethod==="indirect"?" selected":"")+">Indirect Method</option>";',
' h+="<option value=\\"direct\\""+(FS_STATE.cfMethod==="direct"?" selected":"")+">Direct Method</option>";',
' h+="</select></div>";',
'',
' h+="<div class=\\"ctrl-group\\"><label> </label><button onclick=\\"exportFSExcel()\\" class=\\"ctrl-dropdown-btn\\" style=\\"min-width:auto;padding:5px 12px\\">Export Excel</button></div>";',
' h+="<div class=\\"ctrl-group\\"><label>Zoom</label><div style=\\"display:flex;gap:4px;align-items:center\\"><button onclick=\\"fsZoom(-0.1)\\" class=\\"ctrl-btn\\" style=\\"padding:5px 10px;font-size:14px;line-height:1\\" title=\\"Zoom out\\">−</button><span id=\\"fsZoomPct\\" onclick=\\"fsZoomReset()\\" title=\\"Click to reset to 100% (PDF size)\\" style=\\"cursor:pointer;min-width:46px;text-align:center;font-size:12px;font-weight:600;color:var(--navy)\\">100%</span><button onclick=\\"fsZoom(0.1)\\" class=\\"ctrl-btn\\" style=\\"padding:5px 10px;font-size:14px;line-height:1\\" title=\\"Zoom in\\">+</button></div></div>";',
' h+="</div>";',
' document.getElementById("controls").innerHTML=h;',
'}',
'function onFSCfMethodChange(){var sel=document.getElementById("fsCfMethod");if(sel)FS_STATE.cfMethod=sel.value;recomputeFS();}',
'',
'/* ── FS Recompute Driver ── */',
'function recomputeFS(){',
' if(!SKEL_SUPPRESS){showSkeleton("fs");SKEL_SUPPRESS=true;setTimeout(function(){recomputeFS();SKEL_SUPPRESS=false;fadeInReport("fs");},200);return;}',
' var ySel=document.getElementById("fsYear");if(ySel)FS_STATE.year=parseInt(ySel.value);',
' var cSel=document.getElementById("fsCmp");if(cSel)FS_STATE.compareYears=parseInt(cSel.value);',
' var year=FS_STATE.year,nC=FS_STATE.compareYears;',
' var bounds=fyBoundsForYear(year);',
' var pkg={year:year,from:bounds.from,to:bounds.to,compareYears:nC,settings:DATA.settings,periods:[]};',
' for(var i=0;i<=nC;i++){var py=year-i;var pb=fyBoundsForYear(py);',
' pkg.periods.push({year:py,from:pb.from,to:pb.to,',
' tb:computeTB(DATA.jeRows,DATA.coa,pb.to,parseInt(DATA.settings.fyStartMonth)||1),',
' is:computeIS(DATA.jeRows,DATA.coa,pb.from,pb.to),',
' bs:computeBS(DATA.jeRows,DATA.coa,pb.to,parseInt(DATA.settings.fyStartMonth)||1),',
' sce:computeSCEPeriod(DATA.jeRows,DATA.coa,pb.from,pb.to),',
' cf:computeCF(DATA.jeRows,DATA.coa,pb.from,pb.to)',
' });',
' }',
' document.getElementById("report").innerHTML=renderFSPackage(pkg);fsApplyZoom_();',
'}',
'',
'/* ── FS Render Master ── */',
'function renderFSPackage(pkg){',
' var h="<div class=\\"fs-doc\\">";',
' h+=renderFSCover(pkg);',
' h+=renderFSSMR(pkg);',
' h+=renderFSBS(pkg);',
' h+=renderFSIS(pkg);',
' h+=renderFSSCE(pkg);',
' h+=renderFSCF(pkg);',
' h+=renderFSNotes(pkg);',
' h+="</div>";',
' return h;',
'}',
'',
'/* ── Cover Page ── */',
'function renderFSCover(pkg){',
' var s=pkg.settings;',
' var entityName=s.businessName||"[Business Name]";',
' var dateLabel=fyLongLabel(pkg.year);',
' var compareLabel="";',
' if(pkg.compareYears>0){var pyLabels=[];for(var i=1;i<=pkg.compareYears;i++)pyLabels.push(String(pkg.year-i));compareLabel="(With comparative figures for "+pyLabels.join(", ")+")";}',
' var h="<div class=\\"fs-page fs-cover\\">";',
' h+="<div class=\\"fs-cover-content\\">";',
' h+="<div class=\\"fs-entity-name\\">"+esc(entityName)+"</div>";',
' h+="<div class=\\"fs-doc-title\\">Financial Statements</div>";',
' h+="<div class=\\"fs-period\\">For the Year Ended "+dateLabel+"</div>";',
' if(compareLabel)h+="<div class=\\"fs-compare-note\\">"+esc(compareLabel)+"</div>";',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* ── SMR ── */',
'function renderFSSMR(pkg){',
' var s=pkg.settings;',
' var entityName=s.businessName||"[Business Name]";',
' var yearEnded=fyLongLabel(pkg.year);',
' var h="<div class=\\"fs-page\\">";',
' h+="<div class=\\"fs-section-title-block\\">";',
' h+="<div class=\\"fs-smr-title\\">Statement of Management\\u2019s Responsibility<br>for Annual Income Tax Return</div>";',
' h+="</div>";',
' h+="<div class=\\"fs-smr-body\\">";',
' h+="<p>The Management of <b>"+esc(entityName)+"</b> is responsible for all information and representations contained in the Annual Income Tax Return for the year ended <b>"+esc(yearEnded)+"</b>. Management is likewise responsible for all information and representations contained in the financial statements accompanying the Annual Income Tax Return covering the same reporting period. Furthermore, the Management is responsible for all information and representations contained in all the other tax returns filed for the reporting period, including, but not limited to, the value added tax and/or percentage tax returns, withholding tax returns, documentary stamp tax returns, and any and all other tax returns.</p>";',
' h+="<p>In this regard, the Management affirms that the attached audited financial statements for the year ended <b>"+esc(yearEnded)+"</b> and the accompanying Annual Income Tax Return are in accordance with the books and records of <b>"+esc(entityName)+"</b>, complete and correct in all material respects. Management likewise affirms that:</p>";',
' h+="<ol class=\\"fs-smr-list\\">";',
' h+="<li>the Annual Income Tax Return has been prepared in accordance with the provisions of the National Internal Revenue Code, as amended, and pertinent tax regulations and other issuances of the Department of Finance and the Bureau of Internal Revenue;</li>";',
' h+="<li>any disparity of figures in the submitted reports arising from the preparation of financial statements pursuant to financial accounting standards and the preparation of the income tax return pursuant to tax accounting rules has been reported as reconciling items and maintained in the company\\u2019s books and records in accordance with the requirements of Revenue Regulations No. 8-2007 and other relevant issuances;</li>";',
' h+="<li>the <b>"+esc(entityName)+"</b> has filed all applicable tax returns, reports and statements required to be filed under Philippine tax laws for the reporting period, and all taxes and other impositions shown thereon to be due and payable have been paid for the reporting period, except those contested in good faith.</li>";',
' h+="</ol>";',
' h+="</div>";',
// Signature lines
' h+="<div class=\\"fs-sig-block\\">";',
' h+="<div class=\\"fs-sig\\"><div class=\\"fs-sig-line\\"></div><div class=\\"fs-sig-label\\">Signature: <i>(Name of the Individual Taxpayer/President/Managing Partner)</i></div></div>";',
' h+="<div class=\\"fs-sig\\"><div class=\\"fs-sig-line\\"></div><div class=\\"fs-sig-label\\">Signature: <i>(Name of the Chief Executive Officer or its equivalent)</i></div></div>";',
' h+="<div class=\\"fs-sig\\"><div class=\\"fs-sig-line\\"></div><div class=\\"fs-sig-label\\">Signature: <i>(Name of the Chief Financial Officer or its equivalent)</i></div></div>";',
' h+="</div>";',
' h+="</div>";',
' return h;',
'}',
'',
'/* ── FS Header (used by BS/IS/SCE/CF) ── */',
'function fsReportHeader(title,periodLine,settings){',
' var entityName=settings.businessName||"[Business Name]";',
' var h="<div class=\\"fs-stmt-hdr\\">";',
' h+="<div class=\\"fs-stmt-entity\\">"+esc(entityName)+"</div>";',
' h+="<div class=\\"fs-stmt-title\\">"+esc(title)+"</div>";',
' h+="<div class=\\"fs-stmt-period\\">"+esc(periodLine)+"</div>";',
' h+="<div class=\\"fs-stmt-currency\\">(Amounts in Philippine Peso)</div>";',
' h+="</div>";',
' return h;',
'}',
'',
'/* ── BS B&W Render ── */',
'function renderFSBS(pkg){',
' var primary=pkg.periods[0],others=pkg.periods.slice(1);',
' var nCols=pkg.periods.length;',
' var h="<div class=\\"fs-page\\">";',
' h+=fsReportHeader("Statement of Financial Position","As of "+fyLongLabel(primary.year),pkg.settings);',
' h+="<table class=\\"fs-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th><th class=\\"fs-th-note\\">Note</th>";',
' for(var p=0;p<nCols;p++)h+="<th class=\\"fs-th-amt\\">"+pkg.periods[p].year+"</th>";',
' h+="</tr></thead><tbody>";',
// ASSETS section
' h+="<tr class=\\"fs-section\\"><td colspan=\\""+(2+nCols)+"\\" class=\\"fs-section-lbl\\">Assets</td></tr>";',
' var bsTypes=["Asset","Liability","Equity"];',
// Render Asset type with current/non-current sections
' h+=renderFSBSType(pkg,"Asset",["Current Assets","Non-Current Assets"]);',
// LIABILITIES AND EQUITY header
' h+="<tr class=\\"fs-section\\"><td colspan=\\""+(2+nCols)+"\\" class=\\"fs-section-lbl\\">Liabilities and Equity</td></tr>";',
' h+=renderFSBSType(pkg,"Liability",["Current Liabilities","Non-Current Liabilities"]);',
' h+=renderFSBSType(pkg,"Equity",[]);',
// Total Liab + Equity
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Total Liabilities and Equity</td><td></td>";',
' for(var p2=0;p2<nCols;p2++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[p2].bs.totalLE)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table></div>";',
' return h;',
'}',
'function renderFSBSType(pkg,typeName,subSections){',
' var nCols=pkg.periods.length,h="";',
' var primary=pkg.periods[0];',
' var typeData=primary.bs.types[typeName];if(!typeData)return"";',
// Iterate subsections (Current/Non-Current) or just sections
' var sectionKeys=Object.keys(typeData.sections).sort(function(a,b){return typeData.sections[a].order-typeData.sections[b].order;});',
' for(var sIdx=0;sIdx<sectionKeys.length;sIdx++){var sName=sectionKeys[sIdx];var sec=typeData.sections[sName];',
// Section sub-header (italic)
' if(typeName!=="Equity"){h+="<tr class=\\"fs-subsection\\"><td colspan=\\""+(2+nCols)+"\\" class=\\"fs-subsection-lbl\\">"+esc(sName)+"</td></tr>";}',
// NoteGroups within section
' var ngKeys=Object.keys(sec.noteGroups);',
' for(var ng=0;ng<ngKeys.length;ng++){var ngName=ngKeys[ng];var ngData=sec.noteGroups[ngName];',
' if(Math.abs(ngData.total)<0.005)continue;',
' h+="<tr><td class=\\"fs-line\\">"+esc(ngData.displayTitle.replace(/ \\(Note \\d+\\)/,""))+"</td><td class=\\"fs-note-num\\">"+esc(ngData.noteNum||"")+"</td>";',
' for(var p=0;p<nCols;p++){',
' var pBSData=pkg.periods[p].bs.types[typeName];',
' var amt=0;',
' if(pBSData&&pBSData.sections[sName]&&pBSData.sections[sName].noteGroups[ngName]){amt=pBSData.sections[sName].noteGroups[ngName].total;}',
' h+="<td class=\\"fs-amt\\">"+fmtFS(amt)+"</td>";',
' }',
' h+="</tr>";',
' }',
// Section subtotal (only if has multiple subsections in same type, e.g. Total Current Assets)
' if(typeName!=="Equity"&§ionKeys.length>1){',
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Total "+esc(sName)+"</td><td></td>";',
' for(var pp=0;pp<nCols;pp++){var pSec=pkg.periods[pp].bs.types[typeName].sections[sName];h+="<td class=\\"fs-amt\\">"+fmtFS(pSec?pSec.total:0)+"</td>";}',
' h+="</tr>";',
' }',
' }',
// Type total
' h+="<tr class=\\"fs-total\\"><td class=\\"fs-total-lbl\\">Total "+esc((typeName==="Asset"?"Assets":typeName==="Liability"?"Liabilities":"Equity"))+"</td><td></td>";',
' for(var pt=0;pt<nCols;pt++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pt].bs.types[typeName].total)+"</td>";',
' h+="</tr>";',
' return h;',
'}',
'',
'/* ── IS B&W Render - Title Case ── */',
'function renderFSIS(pkg){',
' var primary=pkg.periods[0];',
' var nCols=pkg.periods.length;',
' var h="<div class=\\"fs-page\\">";',
' h+=fsReportHeader("Statement of Income","For the Year Ended "+fyLongLabel(primary.year),pkg.settings);',
' h+="<table class=\\"fs-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th><th class=\\"fs-th-note\\">Note</th>";',
' for(var p=0;p<nCols;p++)h+="<th class=\\"fs-th-amt\\">"+pkg.periods[p].year+"</th>";',
' h+="</tr></thead><tbody>";',
// Revenue
' h+="<tr class=\\"fs-section-row\\"><td class=\\"fs-line-bold\\">Revenue</td><td class=\\"fs-note-num\\">13</td>";',
' for(var pr=0;pr<nCols;pr++)h+="<td class=\\"fs-amt fs-bold\\">"+fmtFS(pkg.periods[pr].is.totalRevenue)+"</td>";',
' h+="</tr>";',
// COS if present
' var hasCOS=false;for(var c=0;c<nCols;c++)if(Math.abs(pkg.periods[c].is.totalCOS)>0.005)hasCOS=true;',
' if(hasCOS){',
' h+="<tr><td class=\\"fs-line\\">Less: Cost of Sales</td><td class=\\"fs-note-num\\">14</td>";',
' for(var pc=0;pc<nCols;pc++){var v=pkg.periods[pc].is.totalCOS;h+="<td class=\\"fs-amt\\">"+(v>0?"("+fmtFS(v).replace(/[()]/g,"")+")":fmtFS(0))+"</td>";}',
' h+="</tr>";',
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Gross Profit</td><td></td>";',
' for(var pgp=0;pgp<nCols;pgp++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pgp].is.grossProfit)+"</td>";',
' h+="</tr>";',
' }',
// Other Income
' var hasOI=false;for(var c2=0;c2<nCols;c2++)if(Math.abs(pkg.periods[c2].is.totalOI)>0.005)hasOI=true;',
' if(hasOI){',
' h+="<tr><td class=\\"fs-line\\">Add: Other Income</td><td class=\\"fs-note-num\\">16</td>";',
' for(var po=0;po<nCols;po++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[po].is.totalOI)+"</td>";',
' h+="</tr>";',
' }',
// OpEx
' h+="<tr><td class=\\"fs-line\\">Less: Operating Expenses</td><td class=\\"fs-note-num\\">15</td>";',
' for(var pe=0;pe<nCols;pe++){var ve=pkg.periods[pe].is.totalOpEx;h+="<td class=\\"fs-amt\\">"+(ve>0?"("+fmtFS(ve).replace(/[()]/g,"")+")":fmtFS(0))+"</td>";}',
' h+="</tr>";',
// Other Expenses
' var hasOE=false;for(var c3=0;c3<nCols;c3++)if(Math.abs(pkg.periods[c3].is.totalOExp)>0.005)hasOE=true;',
' if(hasOE){',
' h+="<tr><td class=\\"fs-line\\">Less: Other Expenses</td><td class=\\"fs-note-num\\">16</td>";',
' for(var poe=0;poe<nCols;poe++){var voe=pkg.periods[poe].is.totalOExp;h+="<td class=\\"fs-amt\\">"+(voe>0?"("+fmtFS(voe).replace(/[()]/g,"")+")":fmtFS(0))+"</td>";}',
' h+="</tr>";',
' }',
// NIBT
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Net Income Before Income Tax</td><td></td>";',
' for(var pn=0;pn<nCols;pn++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pn].is.nibt)+"</td>";',
' h+="</tr>";',
// Income Tax
' var hasIT=false;for(var c4=0;c4<nCols;c4++)if(Math.abs(pkg.periods[c4].is.totalIT)>0.005)hasIT=true;',
' if(hasIT){',
' h+="<tr><td class=\\"fs-line\\">Less: Income Tax Expense</td><td class=\\"fs-note-num\\">17</td>";',
' for(var pit=0;pit<nCols;pit++){var vit=pkg.periods[pit].is.totalIT;h+="<td class=\\"fs-amt\\">"+(vit>0?"("+fmtFS(vit).replace(/[()]/g,"")+")":fmtFS(0))+"</td>";}',
' h+="</tr>";',
' }',
// Net Income (grand total)
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Net Income</td><td></td>";',
' for(var pni=0;pni<nCols;pni++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pni].is.netIncome)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table></div>";',
' return h;',
'}',
'',
'/* ── SCE B&W Render ── */',
'function renderFSSCE(pkg){',
' if(sceIsCorp_())return renderFSSCECorp(pkg);',
' var primary=pkg.periods[0];',
' var nCols=pkg.periods.length;',
' var h="<div class=\\"fs-page\\">";',
' h+=fsReportHeader("Statement of Changes in Equity","For the Year Ended "+fyLongLabel(primary.year),pkg.settings);',
' h+="<table class=\\"fs-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th>";',
' for(var p=0;p<nCols;p++)h+="<th class=\\"fs-th-amt\\">"+pkg.periods[p].year+"</th>";',
' h+="</tr></thead><tbody>";',
// Beginning Capital
' h+="<tr><td class=\\"fs-line\\">Beginning Capital</td>";',
' for(var pb=0;pb<nCols;pb++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pb].sce.beginningCapital)+"</td>";',
' h+="</tr>";',
// Net Income / Loss (auto-detect per period)
' h+="<tr>";',
' var anyLoss=false;for(var pl=0;pl<nCols;pl++)if(pkg.periods[pl].sce.netIncome<0)anyLoss=true;',
' h+="<td class=\\"fs-line\\">"+(anyLoss&&primary.sce.netIncome<0?"Less: Net Loss":"Add: Net Income")+"</td>";',
' for(var pni=0;pni<nCols;pni++){var ni=pkg.periods[pni].sce.netIncome;h+="<td class=\\"fs-amt\\">"+fmtFS(ni>=0?ni:Math.abs(ni))+"</td>";}',
' h+="</tr>";',
// Additional Investments
' var hasC=false;for(var pc=0;pc<nCols;pc++)if(Math.abs(pkg.periods[pc].sce.contributions)>0.005)hasC=true;',
' h+="<tr><td class=\\"fs-line\\">Add: Owner Investments</td>";',
' for(var pcc=0;pcc<nCols;pcc++)h+="<td class=\\"fs-amt\\">"+(hasC?fmtFS(pkg.periods[pcc].sce.contributions):"\-")+"</td>";',
' h+="</tr>";',
// Withdrawals
' h+="<tr><td class=\\"fs-line\\">Less: Withdrawals</td>";',
' for(var pw=0;pw<nCols;pw++){var wv=pkg.periods[pw].sce.withdrawals;h+="<td class=\\"fs-amt\\">"+(wv>0?"("+fmtFS(wv).replace(/[()]/g,"")+")":"\-")+"</td>";}',
' h+="</tr>";',
// Ending Capital
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Ending Capital</td>";',
' for(var pe=0;pe<nCols;pe++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pe].sce.ending)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table></div>";',
' return h;',
'}',
'',
'/* ── FS SCE CORP (2026-06-05, D3): columnar Share Capital | Retained Earnings | Total, per year ── */',
'function renderFSSCECorp(pkg){',
' var primary=pkg.periods[0];',
' var h="<div class=\\"fs-page\\">";',
' h+=fsReportHeader("Statement of Changes in Equity","For the Year Ended "+fyLongLabel(primary.year),pkg.settings);',
' for(var p=0;p<pkg.periods.length;p++){var per=pkg.periods[p];',
' var d=computeSCECorp(DATA.jeRows,DATA.coa,per.from,per.to);',
' h+="<div style=\\"font-weight:700;margin:10px 0 4px\\">"+per.year+"</div>";',
' h+="<table class=\\"fs-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th><th class=\\"fs-th-amt\\">Share Capital</th><th class=\\"fs-th-amt\\">Retained Earnings</th><th class=\\"fs-th-amt\\">Total</th></tr></thead><tbody>";',
' function fsRow3(lbl,sc,re,grand){var tot=(sc===null?0:sc)+(re===null?0:re);var r="<tr"+(grand?" class=\\"fs-grand\\"":"")+"><td class=\\""+(grand?"fs-grand-lbl":"fs-line")+"\\">"+lbl+"</td>";r+="<td class=\\"fs-amt\\">"+(sc===null?"-":fmtFS(sc))+"</td><td class=\\"fs-amt\\">"+(re===null?"-":fmtFS(re))+"</td><td class=\\"fs-amt\\">"+fmtFS(tot)+"</td></tr>";return r;}',
' h+=fsRow3("Balance at beginning of year",d.begSC,d.begRE,false);',
' if(Math.abs(d.issuances)>0.005)h+=fsRow3("Share issuances",d.issuances,null,false);',
' h+=fsRow3(d.netIncome>=0?"Net income for the year":"Net loss for the year",null,d.netIncome,false);',
' if(Math.abs(d.dividends)>0.005)h+=fsRow3("Dividends declared",null,-d.dividends,false);',
' if(Math.abs(d.ppa)>0.005)h+=fsRow3("Prior period adjustments",null,-d.ppa,false);',
' h+=fsRow3("Balance at end of year",d.endSC,d.endRE,true);',
' h+="</tbody></table>";',
' }',
' h+="</div>";',
' return h;',
'}',
'',
'/* ── CF B&W Render (Indirect default for FS Package) ── */',
'function renderFSCF(pkg){',
' if(FS_STATE.cfMethod==="direct")return renderFSCFDirect(pkg);',
' return renderFSCFIndirect(pkg);',
'}',
'',
'function renderFSCFIndirect(pkg){',
' var primary=pkg.periods[0];',
' var nCols=pkg.periods.length;',
' var h="<div class=\\"fs-page\\">";',
' h+=fsReportHeader("Statement of Cash Flows","For the Year Ended "+fyLongLabel(primary.year),pkg.settings);',
' h+="<div class=\\"fs-stmt-method\\">Indirect Method</div>";',
' h+="<table class=\\"fs-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th>";',
' for(var p=0;p<nCols;p++)h+="<th class=\\"fs-th-amt\\">"+pkg.periods[p].year+"</th>";',
' h+="</tr></thead><tbody>";',
// Operating
' h+="<tr class=\\"fs-section-row\\"><td colspan=\\""+(1+nCols)+"\\" class=\\"fs-line-bold\\">Cash Flow from Operating Activities</td></tr>";',
' h+="<tr><td class=\\"fs-line\\">Net Income</td>";',
' for(var pn=0;pn<nCols;pn++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pn].cf.indirect.netIncome)+"</td>";',
' h+="</tr>";',
// Non-cash adjustments
' h+="<tr><td class=\\"fs-line\\" style=\\"font-style:italic\\">Adjustments for non-cash items:</td>";',
' for(var px=0;px<nCols;px++)h+="<td></td>";',
' h+="</tr>";',
// Collect all non-cash by account (account-keyed, label = account title)
' var ncByAcct={};for(var p1=0;p1<nCols;p1++){var ncs=pkg.periods[p1].cf.indirect.nonCash;for(var i=0;i<ncs.length;i++){var k=(ncs[i].accounts&&ncs[i].accounts.length)?ncs[i].accounts[0]:ncs[i].label;if(!ncByAcct[k])ncByAcct[k]={accounts:ncs[i].accounts||[k]};}}',
' var ncOrder=Object.keys(ncByAcct);',
' for(var n=0;n<ncOrder.length;n++){var ncKey=ncOrder[n];h+="<tr><td class=\\"fs-line fs-indent\\">"+esc(ncKey)+"</td>";',
' for(var p2=0;p2<nCols;p2++){var pncs=pkg.periods[p2].cf.indirect.nonCash;var amt=0;for(var ix=0;ix<pncs.length;ix++){var pk=(pncs[ix].accounts&&pncs[ix].accounts.length)?pncs[ix].accounts[0]:pncs[ix].label;if(pk===ncKey){amt=pncs[ix].amount;break;}}h+="<td class=\\"fs-amt\\">"+fmtFS(amt)+"</td>";}',
' h+="</tr>";',
' }',
// Working capital - consolidate by account, label = "Change in <Account>"
' var wcByAcct={};for(var p3=0;p3<nCols;p3++){var wcs=pkg.periods[p3].cf.indirect.workingCapital;for(var iw=0;iw<wcs.length;iw++){var wk=(wcs[iw].accounts&&wcs[iw].accounts.length)?wcs[iw].accounts[0]:wcs[iw].label;if(!wcByAcct[wk])wcByAcct[wk]={accounts:wcs[iw].accounts||[wk]};}}',
' var wcOrder=Object.keys(wcByAcct);',
' if(wcOrder.length>0){',
' h+="<tr><td class=\\"fs-line\\" style=\\"font-style:italic\\">Changes in working capital:</td>";',
' for(var pwh=0;pwh<nCols;pwh++)h+="<td></td>";',
' h+="</tr>";',
' for(var w=0;w<wcOrder.length;w++){var wKey=wcOrder[w];h+="<tr><td class=\\"fs-line fs-indent\\">"+esc("Change in "+wKey)+"</td>";',
' for(var p4=0;p4<nCols;p4++){var pwcs=pkg.periods[p4].cf.indirect.workingCapital;var amt2=0;for(var iw2=0;iw2<pwcs.length;iw2++){var pwk=(pwcs[iw2].accounts&&pwcs[iw2].accounts.length)?pwcs[iw2].accounts[0]:pwcs[iw2].label;if(pwk===wKey){amt2=pwcs[iw2].amount;break;}}h+="<td class=\\"fs-amt\\">"+fmtFS(amt2)+"</td>";}',
' h+="</tr>";',
' }',
' }',
// Net operating
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Net cash from operating activities</td>";',
' for(var po=0;po<nCols;po++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[po].cf.indirect.operatingTotal)+"</td>";',
' h+="</tr>";',
// Investing - group by account, label = account title (no "Purchase of/Sale of" prefix variation)
' h+="<tr class=\\"fs-section-row\\"><td colspan=\\""+(1+nCols)+"\\" class=\\"fs-line-bold\\">Cash Flow from Investing Activities</td></tr>";',
' var invByAcct={};for(var p5=0;p5<nCols;p5++){var invs=pkg.periods[p5].cf.indirect.investing;for(var ii=0;ii<invs.length;ii++){var ik=(invs[ii].accounts&&invs[ii].accounts.length)?invs[ii].accounts[0]:invs[ii].label;if(!invByAcct[ik])invByAcct[ik]={accounts:invs[ii].accounts||[ik],label:invs[ii].label};}}',
' var invOrder=Object.keys(invByAcct);',
' if(invOrder.length===0){h+="<tr><td class=\\"fs-line\\">No investing activities</td>";for(var pi=0;pi<nCols;pi++)h+="<td class=\\"fs-amt\\">-</td>";h+="</tr>";}',
' else{for(var iv=0;iv<invOrder.length;iv++){var iKey=invOrder[iv];h+="<tr><td class=\\"fs-line\\">"+esc(invByAcct[iKey].label||iKey)+"</td>";',
' for(var p6=0;p6<nCols;p6++){var pinvs=pkg.periods[p6].cf.indirect.investing;var amt3=0;for(var iv2=0;iv2<pinvs.length;iv2++){var pik=(pinvs[iv2].accounts&&pinvs[iv2].accounts.length)?pinvs[iv2].accounts[0]:pinvs[iv2].label;if(pik===iKey){amt3=pinvs[iv2].amount;break;}}h+="<td class=\\"fs-amt\\">"+fmtFS(amt3)+"</td>";}',
' h+="</tr>";',
' }}',
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Net cash from investing activities</td>";',
' for(var pii=0;pii<nCols;pii++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pii].cf.indirect.investingTotal)+"</td>";',
' h+="</tr>";',
// Financing - group by account
' h+="<tr class=\\"fs-section-row\\"><td colspan=\\""+(1+nCols)+"\\" class=\\"fs-line-bold\\">Cash Flow from Financing Activities</td></tr>";',
' var finByAcct={};for(var p7=0;p7<nCols;p7++){var fins=pkg.periods[p7].cf.indirect.financing;for(var f=0;f<fins.length;f++){var fk=(fins[f].accounts&&fins[f].accounts.length)?fins[f].accounts[0]:fins[f].label;if(!finByAcct[fk])finByAcct[fk]={accounts:fins[f].accounts||[fk],label:fins[f].label};}}',
' var finOrder=Object.keys(finByAcct);',
' if(finOrder.length===0){h+="<tr><td class=\\"fs-line\\">No financing activities</td>";for(var pf=0;pf<nCols;pf++)h+="<td class=\\"fs-amt\\">-</td>";h+="</tr>";}',
' else{for(var fi=0;fi<finOrder.length;fi++){var fKey=finOrder[fi];h+="<tr><td class=\\"fs-line\\">"+esc(finByAcct[fKey].label||fKey)+"</td>";',
' for(var p8=0;p8<nCols;p8++){var pfins=pkg.periods[p8].cf.indirect.financing;var amt4=0;for(var fi2=0;fi2<pfins.length;fi2++){var pfk=(pfins[fi2].accounts&&pfins[fi2].accounts.length)?pfins[fi2].accounts[0]:pfins[fi2].label;if(pfk===fKey){amt4=pfins[fi2].amount;break;}}h+="<td class=\\"fs-amt\\">"+fmtFS(amt4)+"</td>";}',
' h+="</tr>";',
' }}',
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Net cash from financing activities</td>";',
' for(var pfi=0;pfi<nCols;pfi++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pfi].cf.indirect.financingTotal)+"</td>";',
' h+="</tr>";',
// Net change + reconciliation
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Net increase (decrease) in cash</td>";',
' for(var pnc=0;pnc<nCols;pnc++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pnc].cf.indirect.netChange)+"</td>";',
' h+="</tr>";',
' h+="<tr><td class=\\"fs-line\\">Cash, beginning of period</td>";',
' for(var pcb=0;pcb<nCols;pcb++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pcb].cf.beginCash)+"</td>";',
' h+="</tr>";',
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Cash, end of period</td>";',
' for(var pce=0;pce<nCols;pce++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pce].cf.endCash)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table>";',
' h+=fsCFPreBooksNote_(pkg);',
' h+="</div>";',
' return h;',
'}',
'',
'/* Pre-books note shared by FS CF renders */',
'function fsCFPreBooksNote_(pkg){var pb=false,bsd="";for(var i=0;i<pkg.periods.length;i++){var c=pkg.periods[i].cf;if(c&&c.preBooks){pb=true;bsd=c.booksStartDate||"";}}if(!pb)return"";return "<div style=\\"font-size:10px;color:#444;margin-top:6px;font-style:italic\\">Periods on or before the Books Start Date ("+esc(bsd)+") are pre-books periods - opening balances are positions taken up, not cash flows, so no activity is shown.</div>";}',
'',
'/* ── CF Direct Method (B&W FS render) ── */',
'function renderFSCFDirect(pkg){',
' var primary=pkg.periods[0];',
' var nCols=pkg.periods.length;',
' var h="<div class=\\"fs-page\\">";',
' h+=fsReportHeader("Statement of Cash Flows","For the Year Ended "+fyLongLabel(primary.year),pkg.settings);',
' h+="<div class=\\"fs-stmt-method\\">Direct Method</div>";',
' h+="<table class=\\"fs-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th>";',
' for(var p=0;p<nCols;p++)h+="<th class=\\"fs-th-amt\\">"+pkg.periods[p].year+"</th>";',
' h+="</tr></thead><tbody>";',
// Operating - Direct method has 4 fixed line items
' h+="<tr class=\\"fs-section-row\\"><td colspan=\\""+(1+nCols)+"\\" class=\\"fs-line-bold\\">Cash Flow from Operating Activities</td></tr>";',
// Operating line items (use direct.operating array)
' var opLabels={};for(var p1=0;p1<nCols;p1++){var ops=pkg.periods[p1].cf.direct.operating;for(var i=0;i<ops.length;i++)opLabels[ops[i].label]=true;}',
' var opOrder=Object.keys(opLabels);',
' for(var n=0;n<opOrder.length;n++){var olbl=opOrder[n];h+="<tr><td class=\\"fs-line\\">"+esc(olbl)+"</td>";',
' for(var p2=0;p2<nCols;p2++){var pops=pkg.periods[p2].cf.direct.operating;var amt=0;for(var ix=0;ix<pops.length;ix++)if(pops[ix].label===olbl){amt=pops[ix].amount;break;}h+="<td class=\\"fs-amt\\">"+fmtFS(amt)+"</td>";}',
' h+="</tr>";',
' }',
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Net cash from operating activities</td>";',
' for(var po=0;po<nCols;po++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[po].cf.direct.operatingTotal)+"</td>";',
' h+="</tr>";',
// Investing (same as Indirect - group by account)
' h+="<tr class=\\"fs-section-row\\"><td colspan=\\""+(1+nCols)+"\\" class=\\"fs-line-bold\\">Cash Flow from Investing Activities</td></tr>";',
' var dInvByAcct={};for(var p3=0;p3<nCols;p3++){var dInvs=pkg.periods[p3].cf.direct.investing;for(var ii=0;ii<dInvs.length;ii++){var ik=(dInvs[ii].accounts&&dInvs[ii].accounts.length)?dInvs[ii].accounts[0]:dInvs[ii].label;if(!dInvByAcct[ik])dInvByAcct[ik]={accounts:dInvs[ii].accounts||[ik],label:dInvs[ii].label};}}',
' var dInvOrder=Object.keys(dInvByAcct);',
' if(dInvOrder.length===0){h+="<tr><td class=\\"fs-line\\">No investing activities</td>";for(var pi=0;pi<nCols;pi++)h+="<td class=\\"fs-amt\\">-</td>";h+="</tr>";}',
' else{for(var iv=0;iv<dInvOrder.length;iv++){var iKey=dInvOrder[iv];h+="<tr><td class=\\"fs-line\\">"+esc(dInvByAcct[iKey].label||iKey)+"</td>";',
' for(var p4=0;p4<nCols;p4++){var pdInvs=pkg.periods[p4].cf.direct.investing;var amt3=0;for(var iv2=0;iv2<pdInvs.length;iv2++){var pik=(pdInvs[iv2].accounts&&pdInvs[iv2].accounts.length)?pdInvs[iv2].accounts[0]:pdInvs[iv2].label;if(pik===iKey){amt3=pdInvs[iv2].amount;break;}}h+="<td class=\\"fs-amt\\">"+fmtFS(amt3)+"</td>";}',
' h+="</tr>";',
' }}',
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Net cash from investing activities</td>";',
' for(var pii=0;pii<nCols;pii++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pii].cf.direct.investingTotal)+"</td>";',
' h+="</tr>";',
// Financing (same as Indirect - group by account)
' h+="<tr class=\\"fs-section-row\\"><td colspan=\\""+(1+nCols)+"\\" class=\\"fs-line-bold\\">Cash Flow from Financing Activities</td></tr>";',
' var dFinByAcct={};for(var p5=0;p5<nCols;p5++){var dFins=pkg.periods[p5].cf.direct.financing;for(var f=0;f<dFins.length;f++){var fk=(dFins[f].accounts&&dFins[f].accounts.length)?dFins[f].accounts[0]:dFins[f].label;if(!dFinByAcct[fk])dFinByAcct[fk]={accounts:dFins[f].accounts||[fk],label:dFins[f].label};}}',
' var dFinOrder=Object.keys(dFinByAcct);',
' if(dFinOrder.length===0){h+="<tr><td class=\\"fs-line\\">No financing activities</td>";for(var pf=0;pf<nCols;pf++)h+="<td class=\\"fs-amt\\">-</td>";h+="</tr>";}',
' else{for(var fi=0;fi<dFinOrder.length;fi++){var fKey=dFinOrder[fi];h+="<tr><td class=\\"fs-line\\">"+esc(dFinByAcct[fKey].label||fKey)+"</td>";',
' for(var p6=0;p6<nCols;p6++){var pdFins=pkg.periods[p6].cf.direct.financing;var amt4=0;for(var fi2=0;fi2<pdFins.length;fi2++){var pfk=(pdFins[fi2].accounts&&pdFins[fi2].accounts.length)?pdFins[fi2].accounts[0]:pdFins[fi2].label;if(pfk===fKey){amt4=pdFins[fi2].amount;break;}}h+="<td class=\\"fs-amt\\">"+fmtFS(amt4)+"</td>";}',
' h+="</tr>";',
' }}',
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Net cash from financing activities</td>";',
' for(var pfi=0;pfi<nCols;pfi++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pfi].cf.direct.financingTotal)+"</td>";',
' h+="</tr>";',
// Net change + reconciliation
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Net increase (decrease) in cash</td>";',
' for(var pnc=0;pnc<nCols;pnc++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pnc].cf.direct.netChange)+"</td>";',
' h+="</tr>";',
' h+="<tr><td class=\\"fs-line\\">Cash, beginning of period</td>";',
' for(var pcb=0;pcb<nCols;pcb++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pcb].cf.beginCash)+"</td>";',
' h+="</tr>";',
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Cash, end of period</td>";',
' for(var pce=0;pce<nCols;pce++)h+="<td class=\\"fs-amt\\">"+fmtFS(pkg.periods[pce].cf.endCash)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table>";',
' h+=fsCFPreBooksNote_(pkg);',
' h+="</div>";',
' return h;',
'}',
'',
'',
'/* ══════════════════════════════════════',
' PHASE 2 - NOTES TO FINANCIAL STATEMENTS',
' ══════════════════════════════════════ */',
'',
'/* ── Compute: Note Schedule by NoteGroup (auto from COA) ── */',
'function computeNoteSchedule(jeRows,coa,noteGroupNum,asOfCY,asOfPY){',
' var accts={};',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var ng=String(a.noteGroup||"").trim();if(!ng)continue;',
' var m=ng.match(/^(\\d+)/);if(!m)continue;',
' if(parseInt(m[1])!==noteGroupNum)continue;',
' var t=String(a.title).trim();',
' accts[t]={code:String(a.code||""),title:t,type:String(a.type).trim(),normal:String(a.normal||"").trim(),cy:0,py:0};',
' }',
' var titles=Object.keys(accts);if(titles.length===0)return null;',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(!accts[je.account])continue;var d=je.date||"";',
' if(d<=asOfCY)accts[je.account].cy+=(je.debit||0)-(je.credit||0);',
' if(d<=asOfPY)accts[je.account].py+=(je.debit||0)-(je.credit||0);',
' }',
' var rows=[],totalCY=0,totalPY=0;',
' for(var k=0;k<titles.length;k++){var ac=accts[titles[k]];',
' var cyDisplay=ac.normal==="Credit"?-ac.cy:ac.cy;',
' var pyDisplay=ac.normal==="Credit"?-ac.py:ac.py;',
' cyDisplay=rnd(cyDisplay);pyDisplay=rnd(pyDisplay);',
' if(cyDisplay===0&&pyDisplay===0)continue;',
' rows.push({code:ac.code,title:ac.title,cy:cyDisplay,py:pyDisplay});',
' totalCY+=cyDisplay;totalPY+=pyDisplay;',
' }',
' rows.sort(function(x,y){return x.code<y.code?-1:x.code>y.code?1:0;});',
' return{rows:rows,totalCY:rnd(totalCY),totalPY:rnd(totalPY)};',
'}',
'',
'/* ── Compute: PPE Rollforward (detailed: cost / additions / disposals / accum dep / depreciation / NBV) ── */',
'function computePPERollforward(jeRows,coa,fromCY,toCY){',
// Identify PPE asset accounts (Asset type, with depreciable subgroup) and their paired Accum Dep accounts
' var assetAccts={},depAccts={};',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var t=String(a.title).trim(),tp=String(a.type).trim(),fg=String(a.fsGroup||"").trim();',
' if(tp!=="Asset")continue;',
// PPE detection (aligned with the Note-8 group): Notes-to-FS group 8, OR FSGroup/title keyword,
// OR code 12xx. Using noteGroup 8 is the reliable signal (matches the note schedule grouping) so a
// PPE account tagged "8 - Property, Plant and Equipment" but with FSGroup "Non-Current Assets" and a
// 10xx code is still picked up - previously it was missed and the whole PPE note disappeared.
' var ngNum=(String(a.noteGroup||"").match(/^\\s*(\\d+)/)||["",""])[1];',
' var isPPE=(ngNum==="8")||/property|plant|equipment|fixed asset|furniture|fixtures|vehicle|transportation|machinery|building|leasehold|PPE/i.test(fg+" "+t)||/^12/.test(String(a.code||""));',
' if(!isPPE)continue;',
' var isAccumDep=/accum|depreciation/i.test(t);',
' if(isAccumDep){depAccts[t]={title:t,code:String(a.code||""),normal:a.normal||"Credit"};}',
' else{assetAccts[t]={title:t,code:String(a.code||""),normal:a.normal||"Debit"};}',
' }',
// Pair each asset with its accum dep (by name match: "X" → "Accum. Depr. - X" or "Accumulated Depreciation - X")
' var classes=[];var assetKeys=Object.keys(assetAccts);',
' for(var k=0;k<assetKeys.length;k++){var assetTitle=assetKeys[k];',
' var matchedDep=null;var depKeys=Object.keys(depAccts);',
' for(var d=0;d<depKeys.length;d++){var dt=depKeys[d];',
' if(dt.indexOf(assetTitle)>=0||(dt.toLowerCase().indexOf(assetTitle.split(" ").slice(-1)[0].toLowerCase())>=0)){matchedDep=dt;break;}',
' }',
' classes.push({title:assetTitle,code:assetAccts[assetTitle].code,depAcct:matchedDep});',
' }',
// For each class, compute rollforward
' var dayBefore=addDaysISO(fromCY,-1);',
' var totals={costBegin:0,additions:0,disposals:0,costEnd:0,depBegin:0,depExpense:0,depDisposals:0,depEnd:0,nbv:0};',
' for(var c=0;c<classes.length;c++){var cls=classes[c];',
' cls.costBegin=0;cls.additions=0;cls.disposals=0;cls.costEnd=0;',
' cls.depBegin=0;cls.depExpense=0;cls.depDisposals=0;cls.depEnd=0;',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];var dt=je.date||"";',
' if(je.account===cls.title){',
' if(dt<=dayBefore){cls.costBegin+=(je.debit||0)-(je.credit||0);}',
' else if(dt>=fromCY&&dt<=toCY){cls.additions+=(je.debit||0);cls.disposals+=(je.credit||0);}',
' }',
' if(cls.depAcct&&je.account===cls.depAcct){',
' if(dt<=dayBefore){cls.depBegin+=(je.credit||0)-(je.debit||0);}',
' else if(dt>=fromCY&&dt<=toCY){cls.depExpense+=(je.credit||0);cls.depDisposals+=(je.debit||0);}',
' }',
' }',
' cls.costBegin=rnd(cls.costBegin);cls.additions=rnd(cls.additions);cls.disposals=rnd(cls.disposals);',
' cls.costEnd=rnd(cls.costBegin+cls.additions-cls.disposals);',
' cls.depBegin=rnd(cls.depBegin);cls.depExpense=rnd(cls.depExpense);cls.depDisposals=rnd(cls.depDisposals);',
' cls.depEnd=rnd(cls.depBegin+cls.depExpense-cls.depDisposals);',
' cls.nbv=rnd(cls.costEnd-cls.depEnd);',
' totals.costBegin+=cls.costBegin;totals.additions+=cls.additions;totals.disposals+=cls.disposals;totals.costEnd+=cls.costEnd;',
' totals.depBegin+=cls.depBegin;totals.depExpense+=cls.depExpense;totals.depDisposals+=cls.depDisposals;totals.depEnd+=cls.depEnd;',
' totals.nbv+=cls.nbv;',
' }',
' Object.keys(totals).forEach(function(kk){totals[kk]=rnd(totals[kk]);});',
' return{classes:classes,totals:totals};',
'}',
'',
'/* ── Compute: Income Tax breakdown ── */',
'function computeITBreakdown(jeRows,coa,fromCY,toCY,fromPY,toPY){',
// IT Expense + IT Payable accounts
' var itExpCY=0,itExpPY=0,itPayCY=0,itPayPY=0,cwtCY=0,cwtPY=0;',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var t=String(a.title).trim(),tp=String(a.type).trim();',
' var isITExp=tp==="Expense"&&/income tax/i.test(t);',
' var isITPay=tp==="Liability"&&/income tax payable/i.test(t);',
' var isCWT=tp==="Asset"&&/CWT|creditable.*withhold/i.test(t);',
' if(!isITExp&&!isITPay&&!isCWT)continue;',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(je.account!==t)continue;var d=je.date||"";',
' var net=(je.debit||0)-(je.credit||0);',
' if(isITExp){if(d>=fromCY&&d<=toCY)itExpCY+=net;if(d>=fromPY&&d<=toPY)itExpPY+=net;}',
' if(isITPay){if(d<=toCY)itPayCY+=-net;if(d<=toPY)itPayPY+=-net;}',
' if(isCWT){if(d<=toCY)cwtCY+=net;if(d<=toPY)cwtPY+=net;}',
' }',
' }',
' return{itExpCY:rnd(itExpCY),itExpPY:rnd(itExpPY),itPayCY:rnd(itPayCY),itPayPY:rnd(itPayPY),cwtCY:rnd(cwtCY),cwtPY:rnd(cwtPY)};',
'}',
'',
'/* ── Compute: BIR Taxes and Licenses (17.1) ── */',
'function computeBIRTaxesLicenses(jeRows,coa,fromCY,toCY,fromPY,toPY){',
// Find expense accounts where title or taxLine matches "Tax" / "License" / "Permit" patterns
' var tlAccts={};',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var t=String(a.title).trim(),tp=String(a.type).trim();',
' if(tp!=="Expense")continue;',
' var tl=String(a.taxLine||"");',
// Match "Taxes & Licenses" or similar tax line, or title contains tax/license/permit/registration
' var isTL=/tax(es)?\\s*(&|and)\\s*licens/i.test(tl)||/tax(es)?\\s*(&|and)\\s*licens|business tax|mayor.{0,3}permit|barangay|registration|stamp|amilyar|community tax|license/i.test(t);',
' if(!isTL)continue;',
' tlAccts[t]={code:String(a.code||""),title:t,cy:0,py:0};',
' }',
' var titles=Object.keys(tlAccts);if(titles.length===0)return{rows:[],totalCY:0,totalPY:0};',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(!tlAccts[je.account])continue;var d=je.date||"";',
' var net=(je.debit||0)-(je.credit||0);',
' if(d>=fromCY&&d<=toCY)tlAccts[je.account].cy+=net;',
' if(d>=fromPY&&d<=toPY)tlAccts[je.account].py+=net;',
' }',
' var rows=[],totalCY=0,totalPY=0;',
' for(var k=0;k<titles.length;k++){var ac=tlAccts[titles[k]];ac.cy=rnd(ac.cy);ac.py=rnd(ac.py);',
' if(ac.cy===0&&ac.py===0)continue;',
' rows.push({code:ac.code,title:ac.title,cy:ac.cy,py:ac.py});',
' totalCY+=ac.cy;totalPY+=ac.py;',
' }',
' rows.sort(function(x,y){return x.title<y.title?-1:x.title>y.title?1:0;});',
' return{rows:rows,totalCY:rnd(totalCY),totalPY:rnd(totalPY)};',
'}',
'',
'/* ── Compute: BIR Withholding Taxes (17.3) ── */',
'function computeBIRWithholdingTaxes(jeRows,coa,fromCY,toCY,fromPY,toPY){',
// Find WHT Payable accounts (Liability + title matches WHT/EWT/Compensation)
' var whtAccts={};',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var t=String(a.title).trim(),tp=String(a.type).trim();',
' if(tp!=="Liability")continue;',
' var isWHT=/withholding|EWT|WTC|FWT|compensation.*payable|payable.*compensat|expanded|final wh/i.test(t);',
' if(!isWHT)continue;',
// Categorize: EWT vs Compensation vs Final
' var category="Other";',
' if(/compensation/i.test(t))category="Compensation";',
' else if(/EWT|expanded/i.test(t))category="Expanded (EWT)";',
' else if(/FWT|final/i.test(t))category="Final (FWT)";',
' whtAccts[t]={code:String(a.code||""),title:t,category:category,cyTotal:0,pyTotal:0};',
' }',
' var titles=Object.keys(whtAccts);if(titles.length===0)return{rows:[],totalCY:0,totalPY:0};',
// Sum credit-side activity (taxes withheld) during each period; opening-balance entries are balances, not withholdings
' var whtBsd=cfBsd_();',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(!whtAccts[je.account])continue;var d=je.date||"";',
' if(whtBsd&&d<=whtBsd)continue;',
' var credit=je.credit||0;',
' if(d>=fromCY&&d<=toCY)whtAccts[je.account].cyTotal+=credit;',
' if(d>=fromPY&&d<=toPY)whtAccts[je.account].pyTotal+=credit;',
' }',
' var rows=[],totalCY=0,totalPY=0;',
' for(var k=0;k<titles.length;k++){var ac=whtAccts[titles[k]];ac.cyTotal=rnd(ac.cyTotal);ac.pyTotal=rnd(ac.pyTotal);',
' if(ac.cyTotal===0&&ac.pyTotal===0)continue;',
' rows.push({title:ac.title,category:ac.category,cy:ac.cyTotal,py:ac.pyTotal});',
' totalCY+=ac.cyTotal;totalPY+=ac.pyTotal;',
' }',
' rows.sort(function(x,y){return x.title<y.title?-1:x.title>y.title?1:0;});',
' return{rows:rows,totalCY:rnd(totalCY),totalPY:rnd(totalPY)};',
'}',
'',
'/* ── Compute: Revenue and Expense breakdown (Notes 13/14) ── */',
'function computeRevExpBreakdown(jeRows,coa,fromCY,toCY,fromPY,toPY,acctType){',
' var accts={};',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var tp=String(a.type).trim();if(tp!==acctType)continue;',
' var fg=String(a.fsGroup||"").trim();',
' if(acctType==="Revenue"&&fg!=="Revenue"&&fg!=="Other Income")continue;',
' if(acctType==="Expense"&&fg==="Income Tax")continue;',
' var t=String(a.title).trim();',
' accts[t]={code:String(a.code||""),title:t,fsGroup:fg,normal:String(a.normal||"").trim(),cy:0,py:0};',
' }',
' var titles=Object.keys(accts);if(titles.length===0)return{rows:[],totalCY:0,totalPY:0};',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(!accts[je.account])continue;var d=je.date||"";',
' var net=(je.debit||0)-(je.credit||0);',
' if(d>=fromCY&&d<=toCY)accts[je.account].cy+=net;',
' if(d>=fromPY&&d<=toPY)accts[je.account].py+=net;',
' }',
' var rows=[],totalCY=0,totalPY=0;',
' for(var k=0;k<titles.length;k++){var ac=accts[titles[k]];',
// FIX (Apr 25, 2026): FSGroup-based sign normalization (handles contra-revenue correctly)
' var sectionIsCreditSide=(ac.fsGroup==="Revenue"||ac.fsGroup==="Other Income");',
' var cyDisp=sectionIsCreditSide?-ac.cy:ac.cy;',
' var pyDisp=sectionIsCreditSide?-ac.py:ac.py;',
' cyDisp=rnd(cyDisp);pyDisp=rnd(pyDisp);',
' if(cyDisp===0&&pyDisp===0)continue;',
' rows.push({code:ac.code,title:ac.title,fsGroup:ac.fsGroup,cy:cyDisp,py:pyDisp});',
' totalCY+=cyDisp;totalPY+=pyDisp;',
' }',
' rows.sort(function(x,y){return x.code<y.code?-1:x.code>y.code?1:0;});',
' return{rows:rows,totalCY:rnd(totalCY),totalPY:rnd(totalPY)};',
'}',
'',
'/* ── Render: Notes Master Dispatcher ── */',
'function renderFSNotes(pkg){',
' var h="";',
' h+="<div class=\\"fs-page\\">";',
' h+=renderNoteHeader(pkg);',
' h+=renderNote1_Entity(pkg);',
' h+=renderNote2_BasisOfPrep(pkg);',
' h+=renderNote3_AcctPolicies(pkg);',
' h+=renderNotes4to16_Schedules(pkg);',
' h+=renderNote17_IncomeTax(pkg);',
' h+=renderNote18_BIRCompliance(pkg);',
' h+=renderNote19_SubsequentEvents(pkg);',
' h+=renderNote20_Approval(pkg);',
' h+="</div>";',
' return h;',
'}',
'',
'/* ── Note Header (page-level) ── */',
'function renderNoteHeader(pkg){',
' var h="<div class=\\"fs-section-title-block\\">";',
' h+="<div class=\\"fs-stmt-entity\\">"+esc(pkg.settings.businessName||"[Business Name]")+"</div>";',
' h+="<div class=\\"fs-stmt-title\\">Notes to Financial Statements</div>";',
' h+="<div class=\\"fs-stmt-period\\">For the Year Ended "+fyLongLabel(pkg.year)+"</div>";',
' h+="<div class=\\"fs-stmt-currency\\">(Amounts in Philippine Peso)</div>";',
' h+="</div>";',
' return h;',
'}',
'',
'/* ── Note 1: Reporting Entity ── */',
'function renderNote1_Entity(pkg){',
' var s=pkg.settings;',
' var name=s.businessName||"[Business Name]";',
' var tin=s.tin||"[TIN]";',
' var address=s.address||"[Business Address]";',
' var activity=s.principalActivity||"[Principal Business Activity]";',
' var regType=s.registrationType||"Non-VAT Registered";',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">1. Reporting Entity</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p><b>"+esc(name)+"</b> (the \\u201CEntity\\u201D) is a sole proprietorship duly registered with the Bureau of Internal Revenue (BIR) under Tax Identification Number (TIN) <b>"+esc(tin)+"</b>. The Entity is classified as <b>"+esc(regType)+"</b> for tax purposes.</p>";',
' h+="<p>The Entity is engaged in <b>"+esc(activity)+"</b>. Its principal place of business is located at <b>"+esc(address)+"</b>.</p>";',
' h+="<p>The financial statements were authorized for issue by the Owner on the date stated in Note 20.</p>";',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* ── Note 2: Basis of Preparation ── */',
'function renderNote2_BasisOfPrep(pkg){',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">2. Basis of Preparation</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p><i>Statement of Compliance.</i> These financial statements have been prepared in accordance with the Philippine Financial Reporting Standards for Small Entities (PFRS for Small Entities) issued by the Financial Reporting Standards Council (FRSC) and adopted by the Securities and Exchange Commission (SEC).</p>";',
' h+="<p><i>Basis of Measurement.</i> The financial statements have been prepared on the historical cost basis, except where otherwise stated. The accounting policies have been applied consistently throughout the periods presented.</p>";',
' h+="<p><i>Functional and Presentation Currency.</i> The financial statements are presented in Philippine Peso (PHP), which is the Entity\\u2019s functional currency. All amounts are rounded to the nearest peso, except when otherwise indicated.</p>";',
' h+="<p><i>Use of Estimates and Judgments.</i> The preparation of the financial statements requires management to make judgments, estimates and assumptions that affect the application of accounting policies and the reported amounts of assets, liabilities, income and expenses. Actual results may differ from these estimates.</p>";',
' h+="<p><i>Going Concern.</i> "+esc(pkg.settings.goingConcernStatement||"Management has assessed the Entity\\u2019s ability to continue as a going concern. As of the reporting date, management is not aware of any material uncertainties that may cast significant doubt on the Entity\\u2019s ability to continue as a going concern. Accordingly, the financial statements have been prepared on a going concern basis.")+"</p>";',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* ── Note 3: Summary of Significant Accounting Policies ── */',
'function renderNote3_AcctPolicies(pkg){',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">3. Summary of Significant Accounting Policies</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p><b>3.1 Cash and Cash Equivalents.</b> Cash includes cash on hand and demand deposits. Cash equivalents are short-term, highly liquid investments that are readily convertible to known amounts of cash with original maturities of three months or less and which are subject to an insignificant risk of change in value.</p>";',
' h+="<p><b>3.2 Trade and Other Receivables.</b> Trade and other receivables are recognized initially at the transaction price and subsequently measured at amortized cost less any provision for impairment. A provision for impairment is established when there is objective evidence that the Entity will not be able to collect all amounts due according to the original terms of the receivable.</p>";',
' h+="<p><b>3.3 Property, Plant and Equipment.</b> Property, plant and equipment are stated at cost less accumulated depreciation and any accumulated impairment losses. Depreciation is computed using the straight-line method over the estimated useful lives of the assets. Useful lives applied are: Office Equipment - 5 years; Furniture and Fixtures - 5 years; Transportation Equipment - 5 years; Building - 20-30 years. The residual values, useful lives and depreciation methods are reviewed at each financial year-end and adjusted prospectively if appropriate.</p>";',
' h+="<p><b>3.4 Trade and Other Payables.</b> Trade and other payables are recognized initially at the transaction price and subsequently measured at amortized cost. They represent liabilities for goods and services provided to the Entity prior to the reporting period that are unpaid.</p>";',
' h+="<p><b>3.5 Revenue Recognition.</b> Revenue is recognized when it is probable that the economic benefits associated with the transaction will flow to the Entity and the amount of revenue can be reliably measured. Revenue is measured at the fair value of the consideration received or receivable, net of returns, trade discounts and volume rebates. Revenue from services is recognized when the services are rendered.</p>";',
' h+="<p><b>3.6 Expense Recognition.</b> Expenses are recognized when incurred and are reported in the statement of income in the period to which they relate, applying the matching principle.</p>";',
' var npIsCorp=String((DATA.settings&&DATA.settings.taxpayerClass)||"").toUpperCase().indexOf("NON")===0;',
' if(npIsCorp)h+="<p><b>3.7 Income Tax.</b> The Entity, being a domestic corporation, is subject to the regular corporate income tax under the National Internal Revenue Code, as amended. The applicable rate is 25%, or 20% when net taxable income does not exceed PHP 5,000,000 and total assets do not exceed PHP 100,000,000 (excluding the land on which the office, plant and equipment are situated). The minimum corporate income tax of 2% of gross income applies beginning on the fourth taxable year following the commencement of operations, whenever it is higher than the regular corporate income tax.</p>";',
' else h+="<p><b>3.7 Income Tax.</b> The Entity, being a sole proprietorship, is subject to income tax under the graduated income tax rates or the 8% optional flat rate, as elected, pursuant to the National Internal Revenue Code, as amended. Income tax expense represents the current tax payable on the taxable income for the reporting period.</p>";',
' h+="<p><b>3.8 Events After the Reporting Period.</b> Post-year-end events that provide additional information about the Entity\\u2019s position at the reporting date (adjusting events) are reflected in the financial statements. Post-year-end events that are not adjusting events are disclosed in the notes when material.</p>";',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* ── Notes 4-16: Auto schedules (BS + IS) from COA noteGroup ── */',
'function renderNotes4to16_Schedules(pkg){',
' var h="";',
' var schedules=[',
' {num:4,title:"Cash and Cash Equivalents"},',
' {num:5,title:"Trade and Other Receivables"},',
' {num:6,title:"Inventories"},',
' {num:7,title:"Prepaid Expenses and Other Current Assets"},',
' {num:8,title:"Property, Plant and Equipment"},',
' {num:9,title:"Trade and Other Payables"},',
' {num:10,title:"Accrued Expenses"},',
' {num:11,title:"Long-Term Obligations"},',
' {num:12,title:"Owner\\u2019s Equity"},',
' {num:13,title:"Revenue",isPL:true,isRev:true},',
' {num:14,title:"Cost of Sales",isPL:true},',
' {num:15,title:"Operating Expenses",isPL:true},',
' {num:16,title:"Other Income / (Expenses)",isPL:true}',
' ];',
' for(var i=0;i<schedules.length;i++){var s=schedules[i];',
' if(s.num===8){h+=renderNote8_PPE(pkg);continue;}',
' if(s.num===12){h+=renderNote12_Equity(pkg);continue;}',
' if(s.isPL){h+=renderNoteSchedulePL(pkg,s.num,s.title);continue;}',
' h+=renderNoteSchedule(pkg,s.num,s.title);',
' }',
' return h;',
'}',
'',
'/* ── Generic P&L note schedule (period-based, by COA noteGroup) ── */',
'function renderNoteSchedulePL(pkg,noteNum,title){',
' var primary=pkg.periods[0];var py=pkg.periods[1];',
' var fromPY=py?py.from:"1900-01-01",toPY=py?py.to:"1900-01-01";',
' var hasComp=!!py;',
' var sched=computeNoteSchedulePL(DATA.jeRows,DATA.coa,noteNum,primary.from,primary.to,fromPY,toPY);',
' if(!sched||sched.rows.length===0)return"";',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">"+noteNum+". "+esc(title)+"</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p>This account consists of the following:</p>";',
' h+="<table class=\\"fs-note-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th><th class=\\"fs-th-amt\\">"+primary.year+"</th>";',
' if(hasComp)h+="<th class=\\"fs-th-amt\\">"+py.year+"</th>";',
' h+="</tr></thead><tbody>";',
' for(var r=0;r<sched.rows.length;r++){var row=sched.rows[r];',
' h+="<tr><td class=\\"fs-line\\">"+esc(row.title)+"</td><td class=\\"fs-amt\\">"+fmtFS(row.cy)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(row.py)+"</td>";',
' h+="</tr>";',
' }',
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Total</td><td class=\\"fs-amt\\">"+fmtFS(sched.totalCY)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(sched.totalPY)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table></div></div>";',
' return h;',
'}',
'',
'/* ── Compute: Note Schedule for P&L (period activity, by COA noteGroup) ── */',
'function computeNoteSchedulePL(jeRows,coa,noteGroupNum,fromCY,toCY,fromPY,toPY){',
' var accts={};',
' for(var i=0;i<coa.length;i++){var a=coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;',
' var ng=String(a.noteGroup||"").trim();if(!ng)continue;',
' var m=ng.match(/^(\\d+)/);if(!m)continue;',
' if(parseInt(m[1])!==noteGroupNum)continue;',
' var t=String(a.title).trim();',
' accts[t]={code:String(a.code||""),title:t,fsGroup:String(a.fsGroup||"").trim(),normal:String(a.normal||"").trim(),cy:0,py:0};',
' }',
' var titles=Object.keys(accts);if(titles.length===0)return null;',
' for(var j=0;j<jeRows.length;j++){var je=jeRows[j];if(!accts[je.account])continue;var d=je.date||"";',
' var net=(je.debit||0)-(je.credit||0);',
' if(d>=fromCY&&d<=toCY)accts[je.account].cy+=net;',
' if(d>=fromPY&&d<=toPY)accts[je.account].py+=net;',
' }',
' var rows=[],totalCY=0,totalPY=0;',
' for(var k=0;k<titles.length;k++){var ac=accts[titles[k]];',
// FIX (Apr 25, 2026): FSGroup-based sign normalization (handles contra-revenue correctly)
' var sectionIsCreditSide=(ac.fsGroup==="Revenue"||ac.fsGroup==="Other Income");',
' var cy=sectionIsCreditSide?-ac.cy:ac.cy;',
' var py=sectionIsCreditSide?-ac.py:ac.py;',
' cy=rnd(cy);py=rnd(py);',
' if(cy===0&&py===0)continue;',
' rows.push({code:ac.code,title:ac.title,cy:cy,py:py});',
' totalCY+=cy;totalPY+=py;',
' }',
' rows.sort(function(x,y){return x.code<y.code?-1:x.code>y.code?1:0;});',
' return{rows:rows,totalCY:rnd(totalCY),totalPY:rnd(totalPY)};',
'}',
'',
'/* ── Generic note schedule renderer (for Notes 4-7, 9-11) ── */',
'function renderNoteSchedule(pkg,noteNum,title){',
' var primary=pkg.periods[0];var py=pkg.periods[1];',
' var asOfPY=py?py.to:"1900-01-01";',
' var sched=computeNoteSchedule(DATA.jeRows,DATA.coa,noteNum,primary.to,asOfPY);',
' if(!sched||sched.rows.length===0)return""; // auto-skip if no accounts or all zero',
' var hasComp=!!py;',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">"+noteNum+". "+esc(title)+"</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p>This account consists of the following:</p>";',
' h+="<table class=\\"fs-note-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th><th class=\\"fs-th-amt\\">"+primary.year+"</th>";',
' if(hasComp)h+="<th class=\\"fs-th-amt\\">"+py.year+"</th>";',
' h+="</tr></thead><tbody>";',
' for(var r=0;r<sched.rows.length;r++){var row=sched.rows[r];',
' h+="<tr><td class=\\"fs-line\\">"+esc(row.title)+"</td><td class=\\"fs-amt\\">"+fmtFS(row.cy)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(row.py)+"</td>";',
' h+="</tr>";',
' }',
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Total</td><td class=\\"fs-amt\\">"+fmtFS(sched.totalCY)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(sched.totalPY)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table></div></div>";',
' return h;',
'}',
'',
'/* ── Note 8: PPE Detailed Rollforward ── */',
'function renderNote8_PPE(pkg){',
' var primary=pkg.periods[0];',
' var roll=computePPERollforward(DATA.jeRows,DATA.coa,primary.from,primary.to);',
' if(!roll||roll.classes.length===0)return"";',
' if(roll.totals.costEnd===0&&roll.totals.costBegin===0)return"";',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">8. Property, Plant and Equipment</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p>The movements in property, plant and equipment for the year ended "+fyLongLabel(primary.year)+" are as follows:</p>";',
// Build a wide table: rows = [Cost lines + Dep lines + NBV], cols = each asset class + Total
' var nClasses=roll.classes.length;',
' h+="<table class=\\"fs-note-table fs-ppe-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th>";',
' for(var c=0;c<nClasses;c++)h+="<th class=\\"fs-th-amt\\">"+esc(roll.classes[c].title)+"</th>";',
' h+="<th class=\\"fs-th-amt\\">Total</th></tr></thead><tbody>";',
// Cost section
' h+="<tr class=\\"fs-section-row\\"><td colspan=\\""+(nClasses+2)+"\\" class=\\"fs-line-bold\\">Cost</td></tr>";',
' h+="<tr><td class=\\"fs-line\\">Beginning balance</td>";',
' for(var c2=0;c2<nClasses;c2++)h+="<td class=\\"fs-amt\\">"+fmtFS(roll.classes[c2].costBegin)+"</td>";',
' h+="<td class=\\"fs-amt\\">"+fmtFS(roll.totals.costBegin)+"</td></tr>";',
' h+="<tr><td class=\\"fs-line\\">Additions</td>";',
' for(var c3=0;c3<nClasses;c3++)h+="<td class=\\"fs-amt\\">"+fmtFS(roll.classes[c3].additions)+"</td>";',
' h+="<td class=\\"fs-amt\\">"+fmtFS(roll.totals.additions)+"</td></tr>";',
' h+="<tr><td class=\\"fs-line\\">Disposals</td>";',
' for(var c4=0;c4<nClasses;c4++){var d=roll.classes[c4].disposals;h+="<td class=\\"fs-amt\\">"+(d>0?"("+fmtFS(d).replace(/[()]/g,"")+")":fmtFS(0))+"</td>";}',
' h+="<td class=\\"fs-amt\\">"+(roll.totals.disposals>0?"("+fmtFS(roll.totals.disposals).replace(/[()]/g,"")+")":fmtFS(0))+"</td></tr>";',
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Ending balance</td>";',
' for(var c5=0;c5<nClasses;c5++)h+="<td class=\\"fs-amt\\">"+fmtFS(roll.classes[c5].costEnd)+"</td>";',
' h+="<td class=\\"fs-amt\\">"+fmtFS(roll.totals.costEnd)+"</td></tr>";',
// Accumulated Depreciation section
' h+="<tr class=\\"fs-section-row\\"><td colspan=\\""+(nClasses+2)+"\\" class=\\"fs-line-bold\\">Accumulated Depreciation</td></tr>";',
' h+="<tr><td class=\\"fs-line\\">Beginning balance</td>";',
' for(var c6=0;c6<nClasses;c6++)h+="<td class=\\"fs-amt\\">"+fmtFS(roll.classes[c6].depBegin)+"</td>";',
' h+="<td class=\\"fs-amt\\">"+fmtFS(roll.totals.depBegin)+"</td></tr>";',
' h+="<tr><td class=\\"fs-line\\">Depreciation expense</td>";',
' for(var c7=0;c7<nClasses;c7++)h+="<td class=\\"fs-amt\\">"+fmtFS(roll.classes[c7].depExpense)+"</td>";',
' h+="<td class=\\"fs-amt\\">"+fmtFS(roll.totals.depExpense)+"</td></tr>";',
' h+="<tr><td class=\\"fs-line\\">Disposals</td>";',
' for(var c8=0;c8<nClasses;c8++){var dd=roll.classes[c8].depDisposals;h+="<td class=\\"fs-amt\\">"+(dd>0?"("+fmtFS(dd).replace(/[()]/g,"")+")":fmtFS(0))+"</td>";}',
' h+="<td class=\\"fs-amt\\">"+(roll.totals.depDisposals>0?"("+fmtFS(roll.totals.depDisposals).replace(/[()]/g,"")+")":fmtFS(0))+"</td></tr>";',
' h+="<tr class=\\"fs-subtotal\\"><td class=\\"fs-subtotal-lbl\\">Ending balance</td>";',
' for(var c9=0;c9<nClasses;c9++)h+="<td class=\\"fs-amt\\">"+fmtFS(roll.classes[c9].depEnd)+"</td>";',
' h+="<td class=\\"fs-amt\\">"+fmtFS(roll.totals.depEnd)+"</td></tr>";',
// Net Book Value
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Net Book Value</td>";',
' for(var c10=0;c10<nClasses;c10++)h+="<td class=\\"fs-amt\\">"+fmtFS(roll.classes[c10].nbv)+"</td>";',
' h+="<td class=\\"fs-amt\\">"+fmtFS(roll.totals.nbv)+"</td></tr>";',
' h+="</tbody></table></div></div>";',
' return h;',
'}',
'',
'/* ── Note 12: Owner\\u2019s Equity ── */',
'function renderNote12_Equity(pkg){',
' if(sceIsCorp_())return renderNote12_EquityCorp(pkg);',
' var primary=pkg.periods[0];var py=pkg.periods[1];',
' if(primary.bs.totalEquity===0&&(!py||py.bs.totalEquity===0))return"";',
' var hasComp=!!py;',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">12. Owner\\u2019s Equity</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p>The movements in Owner\\u2019s Equity are summarized in the Statement of Changes in Equity. The composition of equity at year-end is as follows:</p>";',
' h+="<table class=\\"fs-note-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th><th class=\\"fs-th-amt\\">"+primary.year+"</th>";',
' if(hasComp)h+="<th class=\\"fs-th-amt\\">"+py.year+"</th>";',
' h+="</tr></thead><tbody>";',
' h+="<tr><td class=\\"fs-line\\">Owner\\u2019s Capital, beginning</td>";',
' h+="<td class=\\"fs-amt\\">"+fmtFS(primary.sce.beginningCapital)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(py.sce.beginningCapital)+"</td>";',
' h+="</tr>";',
' h+="<tr><td class=\\"fs-line\\">Add: Net income</td><td class=\\"fs-amt\\">"+fmtFS(primary.sce.netIncome)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(py.sce.netIncome)+"</td>";',
' h+="</tr>";',
' h+="<tr><td class=\\"fs-line\\">Add: Additional investments</td><td class=\\"fs-amt\\">"+fmtFS(primary.sce.contributions)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(py.sce.contributions)+"</td>";',
' h+="</tr>";',
' h+="<tr><td class=\\"fs-line\\">Less: Withdrawals</td>";',
' h+="<td class=\\"fs-amt\\">"+(primary.sce.withdrawals>0?"("+fmtFS(primary.sce.withdrawals).replace(/[()]/g,"")+")":fmtFS(0))+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+(py.sce.withdrawals>0?"("+fmtFS(py.sce.withdrawals).replace(/[()]/g,"")+")":fmtFS(0))+"</td>";',
' h+="</tr>";',
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Owner\\u2019s Equity, ending</td><td class=\\"fs-amt\\">"+fmtFS(primary.sce.ending)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(py.sce.ending)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table></div></div>";',
' return h;',
'}',
'',
'/* ── Note 12 CORP (2026-06-05, D6 minimum viable): Shareholders Equity + PFRS for SE par.36 share capital disclosure ── */',
'function renderNote12_EquityCorp(pkg){',
' var primary=pkg.periods[0];var py=pkg.periods[1];',
' var hasComp=!!py;',
' var cy=computeSCECorp(DATA.jeRows,DATA.coa,primary.from,primary.to);',
' var pyD=hasComp?computeSCECorp(DATA.jeRows,DATA.coa,py.from,py.to):null;',
' if(cy.endSC===0&&cy.endRE===0&&(!pyD||(pyD.endSC===0&&pyD.endRE===0)))return"";',
' var q12=String.fromCharCode(8217);',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">12. Shareholders"+q12+" Equity</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p>The movements in equity are summarized in the Statement of Changes in Equity. The composition of equity at year-end is as follows:</p>";',
' h+="<table class=\\"fs-note-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th><th class=\\"fs-th-amt\\">"+primary.year+"</th>";',
' if(hasComp)h+="<th class=\\"fs-th-amt\\">"+py.year+"</th>";',
' h+="</tr></thead><tbody>";',
' function n12row(lbl,cv,pv,grand){var r="<tr"+(grand?" class=\\"fs-grand\\"":"")+"><td class=\\""+(grand?"fs-grand-lbl":"fs-line")+"\\">"+lbl+"</td><td class=\\"fs-amt\\">"+fmtFS(cv)+"</td>";if(hasComp)r+="<td class=\\"fs-amt\\">"+fmtFS(pv)+"</td>";r+="</tr>";return r;}',
' h+=n12row("Share capital",cy.endSC,pyD?pyD.endSC:0,false);',
' h+=n12row("Retained earnings, beginning",cy.begRE,pyD?pyD.begRE:0,false);',
' h+=n12row("Net income for the year",cy.netIncome,pyD?pyD.netIncome:0,false);',
' if(Math.abs(cy.dividends)>0.005||(pyD&&Math.abs(pyD.dividends)>0.005))h+=n12row("Dividends declared",-cy.dividends,pyD?-pyD.dividends:0,false);',
' if(Math.abs(cy.ppa)>0.005||(pyD&&Math.abs(pyD.ppa)>0.005))h+=n12row("Prior period adjustments",-cy.ppa,pyD?-pyD.ppa:0,false);',
' h+=n12row("Shareholders"+q12+" equity, ending",cy.endSC+cy.endRE,pyD?(pyD.endSC+pyD.endRE):0,true);',
' h+="</tbody></table>";',
' var st=DATA.settings||{};',
' var n12as=String(st.authorizedShares||"").trim(),n12pv=String(st.parValue||"").trim(),n12is=String(st.issuedShares||"").trim();',
' function n12num(x){var n=parseFloat(String(x).replace(/,/g,""));return isNaN(n)?esc(x):n.toLocaleString("en-US");}',
' if(n12as||n12pv||n12is){',
' var sc="<p>";',
' if(n12as)sc+="The Entity is authorized to issue "+n12num(n12as)+" shares"+(n12pv?" at a par value of "+n12num(n12pv)+" per share":"")+". ";',
' else if(n12pv)sc+="The shares have a par value of "+n12num(n12pv)+" per share. ";',
' if(n12is)sc+="As of the reporting date, "+n12num(n12is)+" shares are issued and outstanding. ";',
' sc+="All shares belong to a single class of ordinary shares with equal rights to dividends and to the repayment of capital.</p>";',
' h+=sc;',
' }',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* ── Note 15: Income Tax ── */',
'function renderNote17_IncomeTax(pkg){',
' var primary=pkg.periods[0];var py=pkg.periods[1];',
' var fromPY=py?py.from:"1900-01-01",toPY=py?py.to:"1900-01-01";',
' var hasComp=!!py;',
' var it=computeITBreakdown(DATA.jeRows,DATA.coa,primary.from,primary.to,fromPY,toPY);',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">17. Income Tax</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p>The Entity, being a sole proprietorship, is subject to income tax on its taxable income under the National Internal Revenue Code (NIRC), as amended by Republic Act No. 10963 (TRAIN Law) and Republic Act No. 11534 (CREATE Law). The Owner may elect to be taxed under the graduated income tax rates or the 8% optional flat rate on gross sales/receipts (subject to qualification), as applicable.</p>";',
' h+="<p>Income tax expense for the period is composed of:</p>";',
' h+="<table class=\\"fs-note-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th><th class=\\"fs-th-amt\\">"+primary.year+"</th>";',
' if(hasComp)h+="<th class=\\"fs-th-amt\\">"+py.year+"</th>";',
' h+="</tr></thead><tbody>";',
' h+="<tr><td class=\\"fs-line\\">Net income before income tax</td><td class=\\"fs-amt\\">"+fmtFS(primary.is.nibt)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(py.is.nibt)+"</td>";',
' h+="</tr>";',
' h+="<tr><td class=\\"fs-line\\">Income tax expense</td><td class=\\"fs-amt\\">"+fmtFS(it.itExpCY)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(it.itExpPY)+"</td>";',
' h+="</tr>";',
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Income tax payable, end</td><td class=\\"fs-amt\\">"+fmtFS(it.itPayCY)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(it.itPayPY)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table></div></div>";',
' return h;',
'}',
'',
'/* ── Note 17: BIR Compliance Disclosures (RR 15-2010) ── */',
'function renderNote18_BIRCompliance(pkg){',
' var primary=pkg.periods[0];var py=pkg.periods[1];',
' var fromPY=py?py.from:"1900-01-01",toPY=py?py.to:"1900-01-01";',
' var hasComp=!!py;',
' var tl=computeBIRTaxesLicenses(DATA.jeRows,DATA.coa,primary.from,primary.to,fromPY,toPY);',
' var wht=computeBIRWithholdingTaxes(DATA.jeRows,DATA.coa,primary.from,primary.to,fromPY,toPY);',
' var s=pkg.settings;',
' var defText=s.deficiencyTaxAssessments||"As of the reporting date, the Entity has no deficiency tax assessment received from the Bureau of Internal Revenue.";',
' var caseText=s.taxCases||"As of the reporting date, the Entity has no pending tax cases before the Bureau of Internal Revenue, the Court of Tax Appeals, or other judicial bodies.";',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">18. Supplementary Information Required under Revenue Regulations No. 15-2010</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p>The following supplementary information is presented for purposes of filing with the Bureau of Internal Revenue (BIR) pursuant to Revenue Regulations No. 15-2010, and is not a required part of the basic financial statements.</p>";',
// 18.1 Taxes and Licenses
' h+="<p><b>18.1 Taxes and Licenses</b></p>";',
' if(tl.rows.length===0){h+="<p style=\\"font-style:italic\\">No taxes and licenses recorded for the period.</p>";}',
' else{',
' h+="<table class=\\"fs-note-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th><th class=\\"fs-th-amt\\">"+primary.year+"</th>";',
' if(hasComp)h+="<th class=\\"fs-th-amt\\">"+py.year+"</th>";',
' h+="</tr></thead><tbody>";',
' for(var r=0;r<tl.rows.length;r++){var trow=tl.rows[r];',
' h+="<tr><td class=\\"fs-line\\">"+esc(trow.title)+"</td><td class=\\"fs-amt\\">"+fmtFS(trow.cy)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(trow.py)+"</td>";',
' h+="</tr>";',
' }',
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Total</td><td class=\\"fs-amt\\">"+fmtFS(tl.totalCY)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(tl.totalPY)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table>";',
' }',
// 18.2 Documentary Stamp Tax
' h+="<p style=\\"margin-top:14px\\"><b>18.2 Documentary Stamp Tax (DST)</b></p>";',
' h+="<p>"+esc(s.dstNarrative||"There were no documentary stamp tax payments made during the year that are required to be disclosed separately.")+"</p>";',
// 18.3 Withholding Taxes
' h+="<p style=\\"margin-top:14px\\"><b>18.3 Withholding Taxes</b> (taxes withheld during the year)</p>";',
' if(wht.rows.length===0){h+="<p style=\\"font-style:italic\\">No withholding taxes recorded for the period.</p>";}',
' else{',
' h+="<table class=\\"fs-note-table\\">";',
' h+="<thead><tr><th class=\\"fs-th-left\\">Particulars</th><th class=\\"fs-th-amt\\">"+primary.year+"</th>";',
' if(hasComp)h+="<th class=\\"fs-th-amt\\">"+py.year+"</th>";',
' h+="</tr></thead><tbody>";',
' for(var w=0;w<wht.rows.length;w++){var wrow=wht.rows[w];',
' var whtLbl=String(wrow.title).replace(/\\s*payable\\s*/gi," ").replace(/\\s{2,}/g," ").trim();',
' h+="<tr><td class=\\"fs-line\\">"+esc(whtLbl)+"</td><td class=\\"fs-amt\\">"+fmtFS(wrow.cy)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(wrow.py)+"</td>";',
' h+="</tr>";',
' }',
' h+="<tr class=\\"fs-grand\\"><td class=\\"fs-grand-lbl\\">Total</td><td class=\\"fs-amt\\">"+fmtFS(wht.totalCY)+"</td>";',
' if(hasComp)h+="<td class=\\"fs-amt\\">"+fmtFS(wht.totalPY)+"</td>";',
' h+="</tr>";',
' h+="</tbody></table>";',
' }',
// 17.4 Tax Cases / Deficiency
' h+="<p style=\\"margin-top:14px\\"><b>18.4 Deficiency Tax Assessments and Tax Cases</b></p>";',
' h+="<p>"+esc(defText)+"</p>";',
' h+="<p>"+esc(caseText)+"</p>";',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* ── Note 18: Subsequent Events ── */',
'function renderNote19_SubsequentEvents(pkg){',
' var s=pkg.settings;',
' var text=s.subsequentEvents||"There have been no events occurring subsequent to "+fyLongLabel(pkg.year)+" up to the date these financial statements were authorized for issue that would require adjustment to or disclosure in the financial statements.";',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">19. Events after the Reporting Period</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p>"+esc(text)+"</p>";',
' h+="</div></div>";',
' return h;',
'}',
'',
'/* ── Note 19: Approval of Financial Statements ── */',
'function renderNote20_Approval(pkg){',
' var s=pkg.settings;',
' var approvalText=s.approvalText||"These financial statements were approved and authorized for issue by the Owner on the date of submission with the Annual Income Tax Return.";',
' var h="<div class=\\"fs-note\\">";',
' h+="<div class=\\"fs-note-title\\">20. Approval of the Financial Statements</div>";',
' h+="<div class=\\"fs-note-body\\">";',
' h+="<p>"+esc(approvalText)+"</p>";',
' h+="</div></div>";',
' return h;',
'}',
'function renderFSNotesPlaceholder(pkg){',
' var h="<div class=\\"fs-page\\">";',
' h+="<div class=\\"fs-section-title-block\\">";',
' h+="<div class=\\"fs-stmt-title\\">Notes to Financial Statements</div>";',
' h+="<div class=\\"fs-stmt-period\\">For the Year Ended "+fyLongLabel(pkg.year)+"</div>";',
' h+="</div>";',
' h+="<div class=\\"fs-notes-placeholder\\"><p style=\\"font-style:italic;text-align:center\\">Detailed notes will be included in Phase 2 of this build.</p>";',
' h+="<p>The notes will include:</p>";',
' h+="<ul>";',
' h+="<li>Note 1 \\u2014 Reporting Entity</li>";',
' h+="<li>Note 2 \\u2014 Basis of Preparation</li>";',
' h+="<li>Note 3 \\u2014 Summary of Significant Accounting Policies</li>";',
' h+="<li>Notes 4\\u201312 \\u2014 Statement of Financial Position Schedules (Cash, Receivables, PPE, etc.)</li>";',
' h+="<li>Notes 13\\u201314 \\u2014 Statement of Income Schedules (Revenue, Expenses)</li>";',
' h+="<li>Note 15 \\u2014 Income Tax</li>";',
' h+="<li>Note 17 \\u2014 BIR Compliance Disclosures</li>";',
' h+="<li>Notes 18\\u201319 \\u2014 Subsequent Events, Approval of Financial Statements</li>";',
' h+="</ul></div></div>";',
' return h;',
'}',
'',
'/* ── Export ── */',
'function exportFSExcel(){if(typeof XLSX==="undefined"){var s=document.createElement("script");s.src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js";s.onload=function(){doExportFS();};s.onerror=function(){refreshToast_("Cannot load Excel library.",true);};document.head.appendChild(s);return;}doExportFS();}',
'function doExportFS(){',
' var wb=XLSX.utils.book_new();',
' var pkg={year:FS_STATE.year,from:fyBoundsForYear(FS_STATE.year).from,to:fyBoundsForYear(FS_STATE.year).to,compareYears:FS_STATE.compareYears,settings:DATA.settings,periods:[]};',
' var nC=FS_STATE.compareYears,year=FS_STATE.year,fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' for(var i=0;i<=nC;i++){var py=year-i;var pb=fyBoundsForYear(py);',
' pkg.periods.push({year:py,from:pb.from,to:pb.to,',
' tb:computeTB(DATA.jeRows,DATA.coa,pb.to,fyM),',
' is:computeIS(DATA.jeRows,DATA.coa,pb.from,pb.to,fyM),',
' bs:computeBS(DATA.jeRows,DATA.coa,pb.to,fyM),',
' sce:computeSCEPeriod(DATA.jeRows,DATA.coa,pb.from,pb.to),',
' cf:computeCF(DATA.jeRows,DATA.coa,pb.from,pb.to)',
' });',
' }',
// Sheet 1: Cover
' var coverData=[[(DATA.settings.businessName||"")],["Financial Statements"],["For the Year Ended "+fyLongLabel(pkg.year)]];',
' if(pkg.compareYears>0){var pyL=[];for(var p=1;p<=pkg.compareYears;p++)pyL.push(String(year-p));coverData.push(["(With comparative figures for "+pyL.join(", ")+")"]);}',
' XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(coverData),"Cover");',
// Sheet 2: SMR (text only)
' var smrText=[["Statement of Management\\u2019s Responsibility"],[""],["The Management of "+(DATA.settings.businessName||"")+" is responsible for all information and representations contained in the Annual Income Tax Return for the year ended "+fyLongLabel(pkg.year)+"..."],[""],["[BIR-prescribed text continues]"],[""],["Signature: ___________________________"],["(Individual Taxpayer/President/Managing Partner)"],[""],["Signature: ___________________________"],["(Chief Executive Officer or its equivalent)"],[""],["Signature: ___________________________"],["(Chief Financial Officer or its equivalent)"]];',
' XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(smrText),"SMR");',
// Sheet 3-6: BS, IS, SCE, CF - each as data table
' var bsRows=[[(DATA.settings.businessName||"")],["Statement of Financial Position"],["As of "+fyLongLabel(pkg.year)],[""]];',
' var bsHdr=["Particulars","Note"];for(var b=0;b<pkg.periods.length;b++)bsHdr.push(pkg.periods[b].year);',
' bsRows.push(bsHdr);',
' bsRows.push(["Total Assets",""].concat(pkg.periods.map(function(p){return p.bs.totalAssets;})));',
' bsRows.push(["Total Liabilities",""].concat(pkg.periods.map(function(p){return p.bs.totalLiab;})));',
' bsRows.push(["Total Equity",""].concat(pkg.periods.map(function(p){return p.bs.totalEquity;})));',
' bsRows.push(["Total Liabilities and Equity",""].concat(pkg.periods.map(function(p){return p.bs.totalLE;})));',
' XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(bsRows),"Balance Sheet");',
'',
' var isRows=[[(DATA.settings.businessName||"")],["Statement of Income"],["For the Year Ended "+fyLongLabel(pkg.year)],[""]];',
' var isHdr=["Particulars","Note"];for(var b2=0;b2<pkg.periods.length;b2++)isHdr.push(pkg.periods[b2].year);',
' isRows.push(isHdr);',
' isRows.push(["Revenue","13"].concat(pkg.periods.map(function(p){return p.is.totalRevenue;})));',
' isRows.push(["Less: Operating Expenses","14"].concat(pkg.periods.map(function(p){return -p.is.totalOpEx;})));',
' isRows.push(["Net Income Before Income Tax",""].concat(pkg.periods.map(function(p){return p.is.nibt;})));',
' isRows.push(["Less: Income Tax Expense","15"].concat(pkg.periods.map(function(p){return -p.is.totalIT;})));',
' isRows.push(["Net Income",""].concat(pkg.periods.map(function(p){return p.is.netIncome;})));',
' XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(isRows),"Income Statement");',
'',
' var sceRows=[[(DATA.settings.businessName||"")],["Statement of Changes in Equity"],["For the Year Ended "+fyLongLabel(pkg.year)],[""]];',
' var sceHdr=["Particulars"];for(var b3=0;b3<pkg.periods.length;b3++)sceHdr.push(pkg.periods[b3].year);',
' sceRows.push(sceHdr);',
' if(sceIsCorp_()){',
' var sceCorpD=pkg.periods.map(function(p){return computeSCECorp(DATA.jeRows,DATA.coa,p.from,p.to);});',
' sceRows.push(["Share capital, beginning"].concat(sceCorpD.map(function(d){return d.begSC;})));',
' sceRows.push(["Share issuances"].concat(sceCorpD.map(function(d){return d.issuances;})));',
' sceRows.push(["Retained earnings, beginning"].concat(sceCorpD.map(function(d){return d.begRE;})));',
' sceRows.push(["Net income"].concat(sceCorpD.map(function(d){return d.netIncome;})));',
' sceRows.push(["Dividends declared"].concat(sceCorpD.map(function(d){return -d.dividends;})));',
' sceRows.push(["Prior period adjustments"].concat(sceCorpD.map(function(d){return -d.ppa;})));',
' sceRows.push(["Total equity, ending"].concat(sceCorpD.map(function(d){return d.endSC+d.endRE;})));',
' }else{',
' sceRows.push(["Beginning Capital"].concat(pkg.periods.map(function(p){return p.sce.beginningCapital;})));',
' sceRows.push(["Add: Net Income"].concat(pkg.periods.map(function(p){return p.sce.netIncome;})));',
' sceRows.push(["Add: Owner Investments"].concat(pkg.periods.map(function(p){return p.sce.contributions;})));',
' sceRows.push(["Less: Withdrawals"].concat(pkg.periods.map(function(p){return -p.sce.withdrawals;})));',
' sceRows.push(["Ending Capital"].concat(pkg.periods.map(function(p){return p.sce.ending;})));',
' }',
' XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(sceRows),"Changes in Equity");',
'',
' var cfRows=[[(DATA.settings.businessName||"")],["Statement of Cash Flows"],["For the Year Ended "+fyLongLabel(pkg.year)],["Indirect Method"],[""]];',
' var cfHdr=["Particulars"];for(var b4=0;b4<pkg.periods.length;b4++)cfHdr.push(pkg.periods[b4].year);',
' cfRows.push(cfHdr);',
' cfRows.push(["Net Income"].concat(pkg.periods.map(function(p){return p.cf.indirect.netIncome;})));',
' cfRows.push(["Net cash from operating activities"].concat(pkg.periods.map(function(p){return p.cf.indirect.operatingTotal;})));',
' cfRows.push(["Net cash from investing activities"].concat(pkg.periods.map(function(p){return p.cf.indirect.investingTotal;})));',
' cfRows.push(["Net cash from financing activities"].concat(pkg.periods.map(function(p){return p.cf.indirect.financingTotal;})));',
' cfRows.push(["Net increase (decrease) in cash"].concat(pkg.periods.map(function(p){return p.cf.indirect.netChange;})));',
' cfRows.push(["Cash, beginning of period"].concat(pkg.periods.map(function(p){return p.cf.beginCash;})));',
' cfRows.push(["Cash, end of period"].concat(pkg.periods.map(function(p){return p.cf.endCash;})));',
' XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(cfRows),"Cash Flow");',
'',
' XLSX.writeFile(wb,((DATA.settings.businessName||"ECTAXPH").replace(/[^a-zA-Z0-9]/g,"_"))+"_FS_"+pkg.year+".xlsx");',
'}',
'/* ── Utilities ── */',
'function findAcct(rows,title){for(var i=0;i<rows.length;i++){if(rows[i].title===title)return rows[i];}return null;}',
'function fmtGLDate(d){if(!d||d.length<10)return"";return d.substring(5,7)+"/"+d.substring(8,10)+"/"+d.substring(0,4);}',
'function fmt(n){if(n===0||n===null||n===undefined)return"-";if(APP_STATE.hideDecimals)return Math.round(n).toLocaleString("en-US");return n.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});}',
'function fmtZeroBlank(n){if(n===0||n===null||n===undefined)return"";if(APP_STATE.hideDecimals)return Math.round(n).toLocaleString("en-US");return n.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});}',
'function fmtIntOnly(n){if(n===null||n===undefined)return"";return Math.round(n).toLocaleString("en-US");}',
'function fmtDateLabel(d){var mos=["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];return mos[parseInt(d.substring(5,7))]+" "+parseInt(d.substring(8,10))+", "+d.substring(0,4);}',
'function pad(n){return n<10?"0"+n:String(n);}',
'function ld(m,y){return new Date(y,m,0).getDate();}',
'function rnd(n){return Math.round(n*100)/100;}',
'function shiftYear(d,n){return(parseInt(d.substring(0,4))+n)+d.substring(4);}',
'function shiftMonths(d,n){var yr=parseInt(d.substring(0,4)),mo=parseInt(d.substring(5,7)),dy=parseInt(d.substring(8,10));mo+=n;while(mo<1){mo+=12;yr--;}while(mo>12){mo-=12;yr++;}var mx=new Date(yr,mo,0).getDate();if(dy>mx)dy=mx;return yr+"-"+pad(mo)+"-"+pad(dy);}',
'function esc(s){return String(s||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/\\"/g,""").replace(/\'/g,"'");}',
'function escJS(s){return String(s||"").replace(/\\\\/g,"\\\\\\\\").replace(/\'/g,"\\\\\'");}',
'',
'/* ── Centralized date range computation (preset → {from, to}) ── */',
'function computeDateRange(preset,maxDate,fyMonth,todayStr,minDate){',
// BUG FIX: Use todayStr (not maxDate) as the basis for relative presets
// "This Month", "Last Month", etc. should be anchored to today's calendar date,
// not the latest transaction date. Previously using maxDate made "Last Month"
// show Feb when today is April but latest JE is March.
' var basisDate=todayStr||maxDate;',
' var yr=parseInt(basisDate.substring(0,4));',
' var mo=parseInt(basisDate.substring(5,7));',
' var dy=parseInt(basisDate.substring(8,10));',
' var ends={jan:"01-31",feb:"02-28",mar:"03-31",apr:"04-30",may:"05-31",jun:"06-30",jul:"07-31",aug:"08-31",sep:"09-30",oct:"10-31",nov:"11-30",dec:"12-31"};',
' var fyStart=(mo>=fyMonth||fyMonth===1)?yr+"-"+pad(fyMonth)+"-01":(yr-1)+"-"+pad(fyMonth)+"-01";',
' var fyEndYr=fyMonth===1?yr:(mo>=fyMonth?yr+1:yr);',
' var fyEnd=fyMonth===1?yr+"-12-31":(fyEndYr)+"-"+pad(fyMonth-1)+"-"+ld(fyMonth-1,fyEndYr);',
// "As-of" (single date) presets - return {from: date, to: date} so caller can use either
' if(preset==="today")return{from:todayStr,to:todayStr,asOf:todayStr};',
' if(preset==="latest")return{from:maxDate,to:maxDate,asOf:maxDate};',
' if(preset==="eolm"){var lm=mo-1,ly=yr;if(lm<1){lm=12;ly--;}var d=ly+"-"+pad(lm)+"-"+ld(lm,ly);return{from:d,to:d,asOf:d};}',
' if(preset==="eolq"){var cqm=mo<=3?1:mo<=6?4:mo<=9?7:10;var pqm=cqm-3,pyr=yr;if(pqm<1){pqm+=12;pyr--;}var d=pyr+"-"+pad(pqm+2)+"-"+ld(pqm+2,pyr);return{from:d,to:d,asOf:d};}',
' if(preset==="eotq"){var tqe=mo<=3?3:mo<=6?6:mo<=9?9:12;var d=yr+"-"+pad(tqe)+"-"+ld(tqe,yr);return{from:d,to:d,asOf:d};}',
// Month-end presets (jan-dec) - as-of only
' if(ends[preset]){var d=yr+"-"+ends[preset];return{from:d,to:d,asOf:d};}',
// Period presets (from + to) - FULL MONTH / QUARTER / FY (standard practice)
' if(preset==="tm"){var tmEnd=yr+"-"+pad(mo)+"-"+ld(mo,yr);return{from:yr+"-"+pad(mo)+"-01",to:tmEnd,asOf:tmEnd};}',
' if(preset==="lm"){var lm=mo-1,ly=yr;if(lm<1){lm=12;ly--;}return{from:ly+"-"+pad(lm)+"-01",to:ly+"-"+pad(lm)+"-"+ld(lm,ly),asOf:ly+"-"+pad(lm)+"-"+ld(lm,ly)};}',
// Month-to-Date: from start of current month through today
' if(preset==="mtd")return{from:yr+"-"+pad(mo)+"-01",to:todayStr,asOf:todayStr};',
' if(preset==="tq"||preset==="q1"||preset==="q2"||preset==="q3"||preset==="q4"){var qm,qy=yr;if(preset==="tq"){qm=mo<=3?1:mo<=6?4:mo<=9?7:10;}else{qm=preset==="q1"?1:preset==="q2"?4:preset==="q3"?7:10;}var qe=qm+2;var qEnd=qy+"-"+pad(qe)+"-"+ld(qe,qy);return{from:qy+"-"+pad(qm)+"-01",to:qEnd,asOf:qEnd};}',
' if(preset==="lq"){var cqm=mo<=3?1:mo<=6?4:mo<=9?7:10;var pqm=cqm-3,pyr=yr;if(pqm<1){pqm+=12;pyr--;}return{from:pyr+"-"+pad(pqm)+"-01",to:pyr+"-"+pad(pqm+2)+"-"+ld(pqm+2,pyr),asOf:pyr+"-"+pad(pqm+2)+"-"+ld(pqm+2,pyr)};}',
// Quarter-to-Date: from start of current quarter through today
' if(preset==="qtd"){var qtdM=mo<=3?1:mo<=6?4:mo<=9?7:10;return{from:yr+"-"+pad(qtdM)+"-01",to:todayStr,asOf:todayStr};}',
' if(preset==="tfy"||preset==="fy")return{from:fyStart,to:fyEnd,asOf:fyEnd};',
' if(preset==="lfy"){if(fyMonth===1)return{from:(yr-1)+"-01-01",to:(yr-1)+"-12-31",asOf:(yr-1)+"-12-31"};var syr=mo>=fyMonth?yr-1:yr-2;return{from:syr+"-"+pad(fyMonth)+"-01",to:(syr+1)+"-"+pad(fyMonth-1)+"-"+ld(fyMonth-1,syr+1),asOf:(syr+1)+"-"+pad(fyMonth-1)+"-"+ld(fyMonth-1,syr+1)};}',
// Year-to-Date (Fiscal Year to Date): from FY start through today
' if(preset==="ytd")return{from:fyStart,to:todayStr,asOf:todayStr};',
' if(preset==="all")return{from:minDate||maxDate,to:maxDate,asOf:maxDate};',
// fallback: no change (custom etc.)
' return null;',
'}',
'',
'/* ── Centralized report metadata + header rendering ── */',
'function getReportMetadata(title,dateMode,dateData){',
' var settings=DATA.settings||{};',
' var ms=["","January","February","March","April","May","June","July","August","September","October","November","December"];',
' var dateStr="";',
' if(dateMode==="asof"&&dateData&&dateData.date){',
' var d=dateData.date;var mo=parseInt(d.substring(5,7)),dy=parseInt(d.substring(8,10)),yr=d.substring(0,4);',
' dateStr="As of "+ms[mo]+" "+dy+", "+yr;',
' }else if(dateMode==="period"&&dateData&&dateData.from&&dateData.to){',
' var f=dateData.from,t=dateData.to;',
' var fMo=parseInt(f.substring(5,7)),fDy=parseInt(f.substring(8,10)),fYr=f.substring(0,4);',
' var tMo=parseInt(t.substring(5,7)),tDy=parseInt(t.substring(8,10)),tYr=t.substring(0,4);',
' dateStr="For the period "+ms[fMo]+" "+fDy+", "+fYr+" - "+ms[tMo]+" "+tDy+", "+tYr;',
' }',
' return{businessName:settings.businessName||"EC TAXPH",tin:settings.tin||"",address:settings.address||"",title:title,dateSubtitle:dateStr,currencyNote:"(Amounts in Philippine Peso)"};',
'}',
'function renderReportHeader(meta){',
' return"<div class=\\"report-block\\"><div class=\\"fs-header\\"><div class=\\"fs-biz-name\\">"+meta.businessName+"</div><div class=\\"fs-title\\">"+meta.title+"</div><div class=\\"fs-date\\">"+meta.dateSubtitle+"</div><div class=\\"fs-currency\\">"+meta.currencyNote+"</div></div></div>";',
'}',
'',
'/* ── Export ── */',
'function exportExcel(){doExp();}',
'function doExp(){',
' if(ACTIVE_TAB==="fs"){exportFSExcel();return;}',
' var ok={tb:1,gl:1,is:1,bs:1,cf:1,sce:1};',
' if(ok[ACTIVE_TAB]){ensureExcelJS_(expFromDOM);return;}',
' refreshToast_("Excel export is available for reports (Trial Balance, General Ledger, Income Statement, Balance Sheet, Cash Flow, Changes in Equity, FS). For tax forms and books, please use Print / PDF.",true);',
'}',
'function ensureExcelJS_(cb){if(typeof ExcelJS!=="undefined"){cb();return;}var s=document.createElement("script");s.src="https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js";s.onload=function(){cb();};s.onerror=function(){refreshToast_("Cannot load Excel library (ExcelJS CDN).",true);};document.head.appendChild(s);}',
'function xlNum_(t){t=String(t==null?"":t).trim();if(!t||t==="-")return null;if(!/^\\(?-?[0-9,]+(\\.[0-9]+)?\\)?$/.test(t))return null;var neg=t.charAt(0)==="(";var n=parseFloat(t.replace(/[(),]/g,""));if(isNaN(n))return null;return neg?-n:n;}',
'function xlCol_(c){var s="";while(c>0){var m=(c-1)%26;s=String.fromCharCode(65+m)+s;c=Math.floor((c-1)/26);}return s;}',
'function expFromDOM(){',
' var rep=document.getElementById("report");',
' if(!rep||!String(rep.innerText||"").trim()){refreshToast_("Open a report first, then click Export Excel.",true);return;}',
' var names={tb:"Trial Balance",gl:"General Ledger",is:"Income Statement",bs:"Balance Sheet",cf:"Cash Flow",sce:"Changes in Equity"};',
' var wb=new ExcelJS.Workbook();var ws=wb.addWorksheet(names[ACTIVE_TAB]||"Report");',
' var R=0,widths=[],firstBlock=true;',
' function setW(c,len){if(!widths[c]||len>widths[c])widths[c]=len;}',
' function textBlock(el){var txt=String(el.innerText||"").trim();if(!txt)return;var lines=txt.split(/\\n+/);for(var i=0;i<lines.length;i++){var t=lines[i].trim();if(!t)continue;R++;var cl=ws.getCell(R,1);cl.value=t;if(firstBlock&&i===0)cl.font={bold:true,size:13};else if(i===0&&t.length<80)cl.font={bold:true};setW(1,t.length);}firstBlock=false;R++;}',
' function table(tb){',
' var dataRows=[],totalRows=[];',
' var rows=tb.rows;',
' for(var r=0;r<rows.length;r++){var tr=rows[r];var inHead=tr.parentNode&&String(tr.parentNode.tagName).toUpperCase()==="THEAD";',
' var cells=tr.cells;if(!cells||!cells.length)continue;',
' var first=String(cells[0].innerText||"").trim();var second=cells.length>1?String(cells[1].innerText||"").trim():"";',
' var label=first||second;var isTotal=/^(total|totals)\\b/i.test(label);',
' R++;var rowHasNum=false;var cc=0;',
' for(var c=0;c<cells.length;c++){cc++;var txt=String(cells[c].innerText||"").trim();var cl=ws.getCell(R,cc);var n=xlNum_(txt);',
' if(n!==null){var dec=txt.indexOf(".")>=0;cl.value=n;cl.numFmt=dec?"#,##0.00;(#,##0.00)":"#,##0;(#,##0)";rowHasNum=true;}',
' else cl.value=txt;',
' setW(cc,txt.length);',
' if(inHead){cl.font={bold:true};cl.border={bottom:{style:"thin"}};}',
' if(isTotal){cl.font={bold:true};cl.border={top:{style:"thin"}};}',
' }',
' if(!inHead&&isTotal){',
' for(var c2=2;c2<=cc;c2++){var cell=ws.getCell(R,c2);if(typeof cell.value!=="number")continue;var refs=null;',
' if(dataRows.length)refs=xlCol_(c2)+dataRows[0]+":"+xlCol_(c2)+dataRows[dataRows.length-1];',
' else if(totalRows.length){var parts=[];for(var ti=0;ti<totalRows.length;ti++)parts.push(xlCol_(c2)+totalRows[ti]);refs=parts.join(",");}',
' if(refs)cell.value={formula:"SUM("+refs+")",result:cell.value};',
' }',
' totalRows.push(R);dataRows=[];',
' } else if(!inHead&&rowHasNum){dataRows.push(R);}',
' }',
' R++;',
' }',
' function walk(el){for(var i=0;i<el.children.length;i++){var ch=el.children[i];var tg=String(ch.tagName).toUpperCase();',
' if(tg==="TABLE"){table(ch);continue;}',
' if(tg==="BUTTON"||tg==="INPUT"||tg==="SELECT"||tg==="SCRIPT"||tg==="STYLE")continue;',
' if(ch.querySelector&&ch.querySelector("table")){walk(ch);continue;}',
' textBlock(ch);',
' }}',
' walk(rep);',
' for(var w=1;w<widths.length;w++){if(widths[w])ws.getColumn(w).width=Math.min(48,Math.max(10,widths[w]+2));}',
' wb.xlsx.writeBuffer().then(function(buf){var blob=new Blob([buf],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});var a=document.createElement("a");a.href=URL.createObjectURL(blob);a.download=((DATA.settings.businessName||"ECTAXPH").replace(/[^a-zA-Z0-9]/g,"_"))+"_"+(ACTIVE_TAB||"report").toUpperCase()+"_"+new Date().toISOString().substring(0,10)+".xlsx";document.body.appendChild(a);a.click();setTimeout(function(){document.body.removeChild(a);URL.revokeObjectURL(a.href);},800);}).catch(function(e){refreshToast_("Excel export failed: "+e.message,true);});',
'}',
'function expTB(){var periods=LAST_PERIODS;if(!periods||!periods.length)return;var numP=periods.length;var sv=document.getElementById("tbShowVar").value==="yes"&&numP>=2;',
' var search=(document.getElementById("tbSearch").value||"").toLowerCase();',
' var primary=periods[0].tb;',
' var baseRows=primary.rows.filter(function(r){if(r.dr!==0||r.cr!==0)return true;for(var pi=1;pi<numP;pi++){var pr=findAcct(periods[pi].tb.rows,r.title);if(pr&&(pr.dr!==0||pr.cr!==0))return true;}return false;});',
' if(search)baseRows=baseRows.filter(function(r){return r.title.toLowerCase().indexOf(search)>=0||r.code.indexOf(search)>=0;});',
' var hdr=["Code","Account Title"];for(var p=0;p<numP;p++){hdr.push(periods[p].label+" Debit");hdr.push(periods[p].label+" Credit");}if(sv){hdr.push("Variance Amount");hdr.push("Variance %");}',
' var ws=[hdr];var curType="";var typeTot={};for(var pt=0;pt<numP;pt++)typeTot["p"+pt]={};',
' for(var i=0;i<baseRows.length;i++){var row=baseRows[i];',
' if(row.type!==curType){if(curType){var subLn=["","Total "+curType];for(var sp=0;sp<numP;sp++){var st=typeTot["p"+sp][curType]||{dr:0,cr:0};subLn.push(st.dr);subLn.push(st.cr);}if(sv)subLn.push("","");ws.push(subLn);}',
' curType=row.type;for(var pt2=0;pt2<numP;pt2++){if(!typeTot["p"+pt2][curType])typeTot["p"+pt2][curType]={dr:0,cr:0};}',
' var secLn=[curType];ws.push(secLn);}',
' var ln=[row.code,row.title];',
' for(var pi=0;pi<numP;pi++){var pRow=findAcct(periods[pi].tb.rows,row.title);var dr=pRow?pRow.dr:0,cr=pRow?pRow.cr:0;if(!typeTot["p"+pi][curType])typeTot["p"+pi][curType]={dr:0,cr:0};typeTot["p"+pi][curType].dr+=dr;typeTot["p"+pi][curType].cr+=cr;ln.push(dr);ln.push(cr);}',
' if(sv){var cyNet=row.dr-row.cr;var pyRow=findAcct(periods[1].tb.rows,row.title);var pyNet=pyRow?(pyRow.dr-pyRow.cr):0;var va=rnd(cyNet-pyNet);var vp=pyNet!==0?rnd((va/Math.abs(pyNet))*100):cyNet!==0?100:0;ln.push(va);ln.push(vp/100);}',
' ws.push(ln);}',
' if(curType){var subLn2=["","Total "+curType];for(var sp2=0;sp2<numP;sp2++){var st2=typeTot["p"+sp2][curType]||{dr:0,cr:0};subLn2.push(st2.dr);subLn2.push(st2.cr);}if(sv)subLn2.push("","");ws.push(subLn2);}',
' var totLn=["","Totals"];for(var tp=0;tp<numP;tp++){totLn.push(periods[tp].tb.tDR);totLn.push(periods[tp].tb.tCR);}if(sv){totLn.push(rnd(periods[0].tb.tDR-periods[1].tb.tDR));totLn.push("");}ws.push(totLn);',
' var wb=XLSX.utils.book_new();var sheet=XLSX.utils.aoa_to_sheet(ws);',
' var colW=[{wch:8},{wch:35}];for(var cw=0;cw<numP;cw++){colW.push({wch:16});colW.push({wch:16});}if(sv){colW.push({wch:14});colW.push({wch:10});}',
' sheet["!cols"]=colW;XLSX.utils.book_append_sheet(wb,sheet,"Trial Balance");',
' XLSX.writeFile(wb,((DATA.settings.businessName||"ECTAXPH").replace(/[^a-zA-Z0-9]/g,"_"))+"_TB_"+periods[0].tb.asOf+".xlsx");}',
'function expGL(){var from=document.getElementById("glFrom").value,to=document.getElementById("glTo").value,fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var search=(document.getElementById("glSearch").value||"").toLowerCase();',
' var selected=Object.keys(GL_STATE.selectedAccounts).filter(function(k){return GL_STATE.selectedAccounts[k]});',
' var ordered=[];for(var i=0;i<DATA.coa.length;i++){var t=String(DATA.coa[i].title).trim();if(selected.indexOf(t)>=0)ordered.push(t);}',
' var visCols=GL_COLS.filter(function(c){return c.on});',
' var hdr=[];for(var hi=0;hi<visCols.length;hi++)hdr.push(visCols[hi].label);',
' var ws=[hdr];',
' for(var j=0;j<ordered.length;j++){var d=computeGLForAccount(ordered[j],from,to,fyM);if(!d)continue;',
' if(GL_STATE.acctInclude==="with_trans"&&d.entries.length===0)continue;',
' if(GL_STATE.acctInclude==="with_bal"&&d.entries.length===0&&d.opening===0)continue;',
' d.entries=applyFilters(d.entries,d);',
' if(search){d.entries=d.entries.filter(function(en){var desc=(en.desc||"").toLowerCase();var ref=(en.ref||"").toLowerCase();var nm=(en.name||"").toLowerCase();var eid=(en.entryId||"").toLowerCase();return desc.indexOf(search)>=0||ref.indexOf(search)>=0||nm.indexOf(search)>=0||eid.indexOf(search)>=0;});}',
' d.periodDr=0;d.periodCr=0;for(var re=0;re<d.entries.length;re++){d.periodDr+=(d.entries[re].debit||0);d.periodCr+=(d.entries[re].credit||0);}',
' d.periodDr=rnd(d.periodDr);d.periodCr=rnd(d.periodCr);d.closing=rnd(d.opening+d.periodDr-d.periodCr);',
' ws.push([d.code+" - "+d.title]);',
' var obAbs=Math.abs(d.opening);var isDr=String((getAcctInfo(d.title)||{}).normal||"").trim()==="Debit";',
' if(d.showOpening){var obRow=[];for(var oi=0;oi<visCols.length;oi++){var oc=visCols[oi];if(oi===0){obRow.push("Opening Balance");}else if(oc.id==="dr"){obRow.push(isDr?obAbs:0);}else if(oc.id==="cr"){obRow.push(isDr?0:obAbs);}else if(oc.id==="bal"){obRow.push(obAbs);}else{obRow.push("");}}ws.push(obRow);}',
' var sorted=sortEntries(d.entries);',
' var runBal=d.showOpening?d.opening:0;',
' for(var e=0;e<sorted.length;e++){var en=sorted[e];runBal+=(en.debit||0)-(en.credit||0);var row=[];',
' for(var ci=0;ci<visCols.length;ci++){var vc=visCols[ci];',
' if(vc.id==="date")row.push(en.date||"");',
' else if(vc.id==="ref")row.push(en.ref||"");',
' else if(vc.id==="desc")row.push(en.desc||"");',
' else if(vc.id==="name")row.push(en.name||"");',
' else if(vc.id==="dr")row.push(en.debit||0);',
' else if(vc.id==="cr")row.push(en.credit||0);',
' else if(vc.id==="bal")row.push(Math.abs(runBal));',
' else if(vc.id==="source")row.push(en.source||"");',
' else if(vc.id==="entryId")row.push(en.entryId||"");',
' else if(vc.id==="net")row.push((en.debit||0)-(en.credit||0));',
' else if(vc.id==="acctType")row.push(d.type||"");',
' else if(vc.id==="fsGroup")row.push(d.fsGroup||"");',
' else if(vc.id==="taxLine")row.push(d.taxLine||"");',
' else if(vc.id==="noteGroup")row.push(d.noteGroup||"");',
' else if(vc.id==="taxLine2551")row.push(d.taxLine2551||"");',
' else if(vc.id==="month"){var mIdx=parseInt((en.date||"").substring(5,7));row.push(["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][mIdx]||"");}',
' else if(vc.id==="quarter"){var qm=parseInt((en.date||"").substring(5,7));row.push(qm<=3?"Q1":qm<=6?"Q2":qm<=9?"Q3":"Q4");}',
' else row.push("");}',
' ws.push(row);}',
' var mvRow=[];for(var mi=0;mi<visCols.length;mi++){if(mi===0)mvRow.push("Transaction Movement ("+from+" - "+to+")");else if(visCols[mi].id==="dr")mvRow.push(d.periodDr);else if(visCols[mi].id==="cr")mvRow.push(d.periodCr);else if(visCols[mi].id==="bal")mvRow.push(Math.abs(d.periodDr-d.periodCr));else mvRow.push("");}ws.push(mvRow);',
' if(d.showOpening){var clRow=[];var closDr=isDr?rnd(obAbs+d.periodDr):d.periodDr;var closCr=isDr?d.periodCr:rnd(obAbs+d.periodCr);for(var cli=0;cli<visCols.length;cli++){if(cli===0)clRow.push("Closing Balance");else if(visCols[cli].id==="dr")clRow.push(closDr);else if(visCols[cli].id==="cr")clRow.push(closCr);else if(visCols[cli].id==="bal")clRow.push(Math.abs(d.closing));else clRow.push("");}ws.push(clRow);}',
' ws.push([]);}',
' var wb=XLSX.utils.book_new();var sheet=XLSX.utils.aoa_to_sheet(ws);',
' var colW=[];for(var cw=0;cw<visCols.length;cw++){var vc2=visCols[cw];colW.push({wch:vc2.type==="num"?14:vc2.id==="desc"?30:vc2.id==="name"?20:vc2.id==="date"?12:12});}',
' sheet["!cols"]=colW;',
' XLSX.utils.book_append_sheet(wb,sheet,"General Ledger");',
' XLSX.writeFile(wb,((DATA.settings.businessName||"ECTAXPH").replace(/[^a-zA-Z0-9]/g,"_"))+"_GL_"+from+"_"+to+".xlsx");}',
'',
'/* ── Export: by Name (NEW v2.2) ── */',
'function expGL_byName(){var from=document.getElementById("glFrom").value,to=document.getElementById("glTo").value,fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var search=(document.getElementById("glSearch").value||"").toLowerCase();',
' var selected=Object.keys(GL_STATE.selectedAccounts).filter(function(k){return GL_STATE.selectedAccounts[k]});',
' var ordered=[];for(var i=0;i<DATA.coa.length;i++){var t=String(DATA.coa[i].title).trim();if(selected.indexOf(t)>=0)ordered.push(t);}',
// Flatten with account info
' var allEntries=[];',
' for(var j=0;j<ordered.length;j++){var d=computeGLForAccount(ordered[j],from,to,fyM);if(!d)continue;',
' if(GL_STATE.acctInclude==="with_trans"&&d.entries.length===0)continue;',
' if(GL_STATE.acctInclude==="with_bal"&&d.entries.length===0&&d.opening===0)continue;',
' d.entries=applyFilters(d.entries,d);',
' if(search){d.entries=d.entries.filter(function(en){var desc=(en.desc||"").toLowerCase();var ref=(en.ref||"").toLowerCase();var nm=(en.name||"").toLowerCase();var eid=(en.entryId||"").toLowerCase();return desc.indexOf(search)>=0||ref.indexOf(search)>=0||nm.indexOf(search)>=0||eid.indexOf(search)>=0;});}',
' for(var ei=0;ei<d.entries.length;ei++){var en=d.entries[ei];',
' allEntries.push({date:en.date,ref:en.ref||"",desc:en.desc||"",name:en.name||"",source:en.source||"",entryId:en.entryId||"",debit:en.debit||0,credit:en.credit||0,acctCode:d.code,acctTitle:d.title});}}',
// Group by name
' var nameMap={};var nameOrd=[];',
' for(var ai=0;ai<allEntries.length;ai++){var ent=allEntries[ai];',
' var pkey=(ent.name||"").trim()||"(No Name)";',
' if(!nameMap[pkey]){nameMap[pkey]={name:pkey,entries:[]};nameOrd.push(pkey);}',
' nameMap[pkey].entries.push(ent);}',
' nameOrd.sort(function(a,b){if(a==="(No Name)")return 1;if(b==="(No Name)")return -1;return a.localeCompare(b);});',
// Build sheet
' var hdr=["Date","Reference","Description","Account","Source","Debit","Credit"];',
' var ws=[hdr];',
' var grandDr=0,grandCr=0;',
' for(var po=0;po<nameOrd.length;po++){',
' var pname=nameOrd[po],grp=nameMap[pname];',
' var psorted=grp.entries.slice().sort(function(a,b){return a.date<b.date?-1:a.date>b.date?1:0;});',
' ws.push([pname]);',
' var pDr=0,pCr=0;',
' for(var ei2=0;ei2<psorted.length;ei2++){var en=psorted[ei2];',
' pDr+=en.debit;pCr+=en.credit;',
' ws.push([en.date,en.ref,en.desc,en.acctCode+" - "+en.acctTitle,en.source,en.debit,en.credit]);}',
' ws.push(["","","","","Subtotal ("+psorted.length+")",rnd(pDr),rnd(pCr)]);',
' ws.push([]);',
' grandDr+=pDr;grandCr+=pCr;}',
' ws.push(["","","","","Grand Total ("+nameOrd.length+" names)",rnd(grandDr),rnd(grandCr)]);',
' var wb=XLSX.utils.book_new();var sheet=XLSX.utils.aoa_to_sheet(ws);',
' sheet["!cols"]=[{wch:12},{wch:14},{wch:30},{wch:28},{wch:10},{wch:14},{wch:14}];',
' XLSX.utils.book_append_sheet(wb,sheet,"GL by Name");',
' XLSX.writeFile(wb,((DATA.settings.businessName||"ECTAXPH").replace(/[^a-zA-Z0-9]/g,"_"))+"_GL_byName_"+from+"_"+to+".xlsx");}',
'',
'/* ══════════════════════════════════════',
' v3.0 CHAT PARSER ENGINE',
' Pattern-matching, no AI. Filipino + English aware.',
' ══════════════════════════════════════ */',
'',
'var INTENT_REPORTS={',
' tb:["trial balance","tb","trial bal"],',
' is:["income statement","is","p&l","pnl","profit and loss","profit & loss","kita","kinita","sales report","income"],',
' bs:["balance sheet","bs","statement of financial position","sfp","financial position"],',
' cf:["cash flow","cashflow","cash flows","cf","statement of cash flows","flow ng pera"],',
' sce:["changes in equity","sce","equity statement","statement of changes","capital movements"],',
' gl:["general ledger","gl","ledger","tlist","transaction list"],',
' fs:["financial statements","fs","financial statement","full fs","fs package"]',
'};',
'',
'var INTENT_LABELS={tb:"Trial Balance",is:"Income Statement",bs:"Balance Sheet",cf:"Cash Flows",sce:"Changes in Equity",gl:"General Ledger",fs:"Financial Statements"};',
'',
'var INTENT_MONTHS={jan:1,january:1,feb:2,february:2,mar:3,march:3,apr:4,april:4,may:5,jun:6,june:6,jul:7,july:7,aug:8,august:8,sep:9,sept:9,september:9,oct:10,october:10,nov:11,november:11,dec:12,december:12};',
'',
'function executeChat(){if(typeof executeChatV4==="function")return executeChatV4();var inp=document.getElementById("chatInput");var fb=document.getElementById("chatFeedback");if(!inp||!fb)return;var q=inp.value.trim();if(!q){fb.textContent="";return;}var result=parseQuery(q);if(result.error){fb.className="chat-feedback error";fb.textContent="\\u26A0 "+result.error+(result.suggestion?" - "+result.suggestion:"");return;}applyChatIntent(result);CHAT_HISTORY.unshift({query:q,result:result,ts:Date.now()});if(CHAT_HISTORY.length>30)CHAT_HISTORY.pop();fb.className="chat-feedback success";fb.textContent="\\u2713 "+result.feedbackLabel;inp.value="";buildSidebar();setTimeout(function(){if(fb&&fb.textContent.indexOf("\\u2713")===0)fb.textContent="";},3500);}',
'',
'function parseQuery(q){',
' var t=q.toLowerCase().trim();',
' var report=detectReportIntent(t);',
' if(!report)return {error:"Hindi ko ma-detect yung report type",suggestion:"Try: \'trial balance\', \'income statement\', \'balance sheet\', \'cash flows\', \'sce\', \'general ledger\'"};',
// Strip report keywords to isolate dates
' var cleanT=t;',
' var keywords=INTENT_REPORTS[report];',
' for(var k=0;k<keywords.length;k++){',
' var kwPattern=new RegExp("(^|[\\\\s,.])" +keywords[k].replace(/[&]/g,"\\\\&").replace(/\\s+/g,"\\\\s+")+"($|[\\\\s,.])","gi");',
' cleanT=cleanT.replace(kwPattern," ");',
' }',
' cleanT=cleanT.replace(/\\s+/g," ").trim();',
// Split by compare keywords
' var parts=cleanT.split(/\\s+(?:vs\\.?|versus|compare\\s+(?:to|with)|compared\\s+to)\\s+/i);',
' var primary=parseDateChunk(parts[0]);',
' var compare=parts.length>1?parseDateChunk(parts[1]):null;',
// Smart default for missing primary date
' if(!primary){',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' var maxD=dates[dates.length-1]||"2026-12-31";',
' var today=new Date();var todayStr=today.getFullYear()+"-"+pad(today.getMonth()+1)+"-"+pad(today.getDate());',
' var r=computeDateRange("tfy",maxD,fyM,todayStr,dates[0]);',
' if(r)primary={from:r.from,to:r.to,label:"This FY (default)",kind:"period",assumed:true};',
' }',
' if(!primary)return {error:"Hindi ma-parse yung date",suggestion:"Try: \'jan 2026\', \'q1 2026\', \'this month\', \'ytd\'"};',
// BS expects asof, convert period→asof using endpoint
' if(report==="bs"&&primary.kind==="period"){primary={from:primary.to,to:primary.to,label:"As of "+primary.to,kind:"asof"};}',
' var fb=INTENT_LABELS[report]+" - "+primary.label+(compare?" vs "+compare.label:"");',
' return {report:report,primary:primary,compare:compare,feedbackLabel:fb};',
'}',
'',
'function detectReportIntent(t){',
' var pairs=[];',
' for(var key in INTENT_REPORTS){',
' INTENT_REPORTS[key].forEach(function(kw){pairs.push({key:key,kw:kw,len:kw.length});});',
' }',
' pairs.sort(function(a,b){return b.len-a.len;});',
' for(var i=0;i<pairs.length;i++){',
' var pat=new RegExp("(^|[\\\\s,.])" +pairs[i].kw.replace(/[&]/g,"\\\\&").replace(/\\s+/g,"\\\\s+")+"($|[\\\\s,.])","i");',
' if(pat.test(" "+t+" "))return pairs[i].key;',
' }',
' return null;',
'}',
'',
'function parseDateChunk(chunk){',
' if(!chunk)return null;',
' var t=chunk.toLowerCase().trim();',
' if(!t)return null;',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' var maxD=dates[dates.length-1]||"2026-12-31";',
' var today=new Date();var todayStr=today.getFullYear()+"-"+pad(today.getMonth()+1)+"-"+pad(today.getDate());',
// Relative phrases
' if(/^(latest|today|now|ngayon)$/.test(t))return {from:todayStr,to:todayStr,label:"Today",kind:"asof"};',
' if(/^(yesterday|kahapon)$/.test(t)){var yd=new Date(todayStr+"T00:00:00");yd.setDate(yd.getDate()-1);var ystr=yd.getFullYear()+"-"+pad(yd.getMonth()+1)+"-"+pad(yd.getDate());return {from:ystr,to:ystr,label:"Yesterday",kind:"asof"};}',
' if(/^(this week|tw|ngayong linggo)$/.test(t)){var twD=new Date(todayStr+"T00:00:00");var twDow=(twD.getDay()+6)%7;var twMon=new Date(twD);twMon.setDate(twD.getDate()-twDow);var twSun=new Date(twMon);twSun.setDate(twMon.getDate()+6);return {from:twMon.getFullYear()+"-"+pad(twMon.getMonth()+1)+"-"+pad(twMon.getDate()),to:twSun.getFullYear()+"-"+pad(twSun.getMonth()+1)+"-"+pad(twSun.getDate()),label:"This Week",kind:"period"};}',
' if(/^(last week|lw|nakaraang linggo)$/.test(t)){var lwD=new Date(todayStr+"T00:00:00");var lwDow=(lwD.getDay()+6)%7;var lwMon=new Date(lwD);lwMon.setDate(lwD.getDate()-lwDow-7);var lwSun=new Date(lwMon);lwSun.setDate(lwMon.getDate()+6);return {from:lwMon.getFullYear()+"-"+pad(lwMon.getMonth()+1)+"-"+pad(lwMon.getDate()),to:lwSun.getFullYear()+"-"+pad(lwSun.getMonth()+1)+"-"+pad(lwSun.getDate()),label:"Last Week",kind:"period"};}',
' if(/^(this fy|tfy|this financial year|this fiscal year|current fy)$/.test(t)){var r=computeDateRange("tfy",maxD,fyM,todayStr,dates[0]);if(r)return {from:r.from,to:r.to,label:"This FY",kind:"period"};}',
' if(/^(ytd|year to date|year.to.date)$/.test(t)){var r=computeDateRange("ytd",maxD,fyM,todayStr,dates[0]);if(r)return {from:r.from,to:r.to,label:"YTD",kind:"period"};}',
' if(/^(last fy|lfy|last financial year|previous fy|noong fy)$/.test(t)){var r=computeDateRange("lfy",maxD,fyM,todayStr,dates[0]);if(r)return {from:r.from,to:r.to,label:"Last FY",kind:"period"};}',
' if(/^(this quarter|tq|current quarter|ngayong quarter)$/.test(t)){var r=computeDateRange("tq",maxD,fyM,todayStr,dates[0]);if(r)return {from:r.from,to:r.to,label:"This Quarter",kind:"period"};}',
' if(/^(last quarter|lq|previous quarter|noong quarter)$/.test(t)){var r=computeDateRange("lq",maxD,fyM,todayStr,dates[0]);if(r)return {from:r.from,to:r.to,label:"Last Quarter",kind:"period"};}',
' if(/^(this month|tm|ngayong buwan)$/.test(t)){var r=computeDateRange("tm",maxD,fyM,todayStr,dates[0]);if(r)return {from:r.from,to:r.to,label:"This Month",kind:"period"};}',
' if(/^(last month|lm|nakaraan|nakaraang buwan|noong buwan)$/.test(t)){var r=computeDateRange("lm",maxD,fyM,todayStr,dates[0]);if(r)return {from:r.from,to:r.to,label:"Last Month",kind:"period"};}',
' if(/^(mtd|month to date|month.to.date)$/.test(t)){var r=computeDateRange("mtd",maxD,fyM,todayStr,dates[0]);if(r)return {from:r.from,to:r.to,label:"MTD",kind:"period"};}',
' if(/^(qtd|quarter to date|quarter.to.date)$/.test(t)){var r=computeDateRange("qtd",maxD,fyM,todayStr,dates[0]);if(r)return {from:r.from,to:r.to,label:"QTD",kind:"period"};}',
// Year only
' var yearOnly=t.match(/^(\\d{4})$/);',
' if(yearOnly){var y=parseInt(yearOnly[1]);return {from:y+"-01-01",to:y+"-12-31",label:"FY "+y,kind:"period"};}',
// Quarter Q1 2026 / quarter 1 2026
' var qM=t.match(/^q(?:uarter)?\\s*([1-4])(?:\\s+(\\d{4}))?$/);',
' if(qM){var q=parseInt(qM[1]);var qy=qM[2]?parseInt(qM[2]):new Date().getFullYear();var qS=(q-1)*3+1;var qE=qS+2;return {from:qy+"-"+pad(qS)+"-01",to:qy+"-"+pad(qE)+"-"+ld(qE,qy),label:"Q"+q+" "+qy,kind:"period"};}',
// Month + day + year: "feb 28 2026"
' var mdy=t.match(/^([a-z]+)\\s+(\\d{1,2})[,]?\\s+(\\d{4})$/);',
' if(mdy){var mn=mdy[1];if(INTENT_MONTHS[mn]){var m=INTENT_MONTHS[mn];var d=parseInt(mdy[2]);var yr=parseInt(mdy[3]);var ds=yr+"-"+pad(m)+"-"+pad(d);return {from:ds,to:ds,label:capWord(mn)+" "+d+", "+yr,kind:"asof"};}}',
// Month range: "jan 1-31 2026"
' var rgM=t.match(/^([a-z]+)\\s+(\\d{1,2})\\s*-\\s*(\\d{1,2})[,]?\\s+(\\d{4})$/);',
' if(rgM){var mn2=rgM[1];if(INTENT_MONTHS[mn2]){var m2=INTENT_MONTHS[mn2];var d1=parseInt(rgM[2]);var d2=parseInt(rgM[3]);var yr2=parseInt(rgM[4]);return {from:yr2+"-"+pad(m2)+"-"+pad(d1),to:yr2+"-"+pad(m2)+"-"+pad(d2),label:capWord(mn2)+" "+d1+"-"+d2+", "+yr2,kind:"period"};}}',
// Month + year: "jan 2026"
' var my=t.match(/^([a-z]+)\\s+(\\d{4})$/);',
' if(my){var mn3=my[1];if(INTENT_MONTHS[mn3]){var m3=INTENT_MONTHS[mn3];var yr3=parseInt(my[2]);return {from:yr3+"-"+pad(m3)+"-01",to:yr3+"-"+pad(m3)+"-"+ld(m3,yr3),label:capWord(mn3)+" "+yr3,kind:"period"};}}',
// Month alone - assume current year
' var ma=t.match(/^([a-z]+)$/);',
' if(ma&&INTENT_MONTHS[ma[1]]){var m4=INTENT_MONTHS[ma[1]];var curYr=new Date().getFullYear();return {from:curYr+"-"+pad(m4)+"-01",to:curYr+"-"+pad(m4)+"-"+ld(m4,curYr),label:capWord(ma[1])+" "+curYr+" (assumed)",kind:"period"};}',
// ISO date
' var iso=t.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);',
' if(iso)return {from:iso[0],to:iso[0],label:iso[0],kind:"asof"};',
' return null;',
'}',
'',
'function capWord(s){return s.charAt(0).toUpperCase()+s.slice(1);}',
'',
'function applyChatIntent(result){',
' var r=result.report,p=result.primary,c=result.compare;',
' if(r==="tb"){applyChatIntent_TB(p,c);return;}',
' if(r==="is"){applyChatIntent_IS(p,c);return;}',
' if(r==="bs"){applyChatIntent_BS(p,c);return;}',
' if(r==="cf"){applyChatIntent_CF(p,c);return;}',
' if(r==="sce"){applyChatIntent_SCE(p,c);return;}',
' if(r==="gl"){applyChatIntent_GL(p,c);return;}',
' if(r==="fs"){switchTab("fs");return;}',
'}',
'',
'function applyChatIntent_TB(p,c){',
' switchTab("tb");',
' setTimeout(function(){',
' var d=document.getElementById("tbAsOf");if(d)d.value=p.to;',
' var pr=document.getElementById("tbPreset");if(pr)pr.value="custom";',
' var cmp=document.getElementById("tbCmp");',
' if(c){',
' if(cmp){cmp.value="custom";onTBCmpChange();}',
' var cd=document.getElementById("tbCustomDate");if(cd)cd.value=c.to;',
' }else{if(cmp){cmp.value="none";onTBCmpChange();}}',
' recomputeTB();',
' },80);',
'}',
'',
'function applyChatIntent_IS(p,c){',
' switchTab("is");',
' setTimeout(function(){',
' var f=document.getElementById("isFrom");if(f)f.value=p.from;',
' var t=document.getElementById("isTo");if(t)t.value=p.to;',
' var pr=document.getElementById("isPreset");if(pr)pr.value="custom";',
' IS_STATE.from=p.from;IS_STATE.to=p.to;IS_STATE.preset="custom";',
' var cmp=document.getElementById("isCmp");',
' if(c){',
' if(cmp){cmp.value="custom";IS_STATE.compareMode="custom";}',
' IS_STATE.customCompareDate=c.to;',
' var cd=document.getElementById("isCustomCmpDate");if(cd)cd.value=c.to;',
' onISCmpChange();',
' }else{if(cmp){cmp.value="none";IS_STATE.compareMode="none";onISCmpChange();}}',
' recomputeIS();',
' },80);',
'}',
'',
'function applyChatIntent_BS(p,c){',
' switchTab("bs");',
' setTimeout(function(){',
' var d=document.getElementById("bsDate");if(d)d.value=p.to;',
' var pr=document.getElementById("bsPreset");if(pr)pr.value="custom";',
' BS_STATE.asOfDate=p.to;BS_STATE.preset="custom";',
' var cmp=document.getElementById("bsCmp");',
' if(c){',
' if(cmp){cmp.value="custom";BS_STATE.compareMode="custom";}',
' BS_STATE.customCompareDate=c.to;',
' var cd=document.getElementById("bsCustomCmpDate");if(cd)cd.value=c.to;',
' onBSCmpChange();',
' }else{if(cmp){cmp.value="none";BS_STATE.compareMode="none";onBSCmpChange();}}',
' recomputeBS();',
' },80);',
'}',
'',
'function applyChatIntent_CF(p,c){',
' switchTab("cf");',
' setTimeout(function(){',
' var f=document.getElementById("cfFrom");if(f)f.value=p.from;',
' var t=document.getElementById("cfTo");if(t)t.value=p.to;',
' var pr=document.getElementById("cfPreset");if(pr)pr.value="custom";',
' CF_STATE.from=p.from;CF_STATE.to=p.to;CF_STATE.preset="custom";',
' recomputeCF();',
' },80);',
'}',
'',
'function applyChatIntent_SCE(p,c){',
' switchTab("sce");',
' setTimeout(function(){',
' var f=document.getElementById("sceFrom");if(f)f.value=p.from;',
' var t=document.getElementById("sceTo");if(t)t.value=p.to;',
' var pr=document.getElementById("scePreset");if(pr)pr.value="custom";',
' SCE_STATE.from=p.from;SCE_STATE.to=p.to;SCE_STATE.preset="custom";',
' recomputeSCE();',
' },80);',
'}',
'',
'function applyChatIntent_GL(p,c){',
' GL_STATE._pendingFrom=p.from;',
' GL_STATE._pendingTo=p.to;',
' GL_STATE._pendingPreset="custom";',
' switchTab("gl");',
'}',
'',
'function rerunHistory(idx){',
' var item=CHAT_HISTORY[idx];',
' if(!item)return;',
' applyChatIntent(item.result);',
' CHAT_HISTORY.splice(idx,1);',
' CHAT_HISTORY.unshift({query:item.query,result:item.result,ts:Date.now()});',
' buildSidebar();',
' var fb=document.getElementById("chatFeedback");',
' if(fb){fb.className="chat-feedback success";fb.textContent="\\u2713 Re-running: "+item.query;setTimeout(function(){fb.textContent="";},2500);}',
'}',
'',
'/* ═══════════════════════════════════════════════════════════════',
' v4.0 ENGINES - Sprints 2 + 3 + 4',
' Filipino Dictionary, Search, Aggregation, Education,',
' Status, Variance, Compare, Anomaly Daemon,',
' Welcome v2, Suggestion Chips, Multi-turn Context, Help, Router',
' ═══════════════════════════════════════════════════════════════ */',
'',
'/* ─── FIL_DICT (Sprint 2 Engine 1) ─── */',
'var FIL_DICT_PHRASES = [',
' ["ngayong buwan","this month"],["ngayong taon","this year"],["nakaraang buwan","last month"],',
' ["nakaraang taon","last year"],["nakaraang linggo","last week"],["ngayong quarter","this quarter"],',
' ["nakaraang quarter","last quarter"],["q1","q1"],["q2","q2"],["q3","q3"],["q4","q4"],',
' ["this fy","this fy"],["last fy","last fy"]',
'];',
'var FIL_DICT_WORDS = {',
' "ngayon":"today","kahapon":"yesterday","kanina":"today","ngayong":"this","nakaraan":"last","nakaraang":"last",',
' "noong":"last","araw":"day","linggo":"week","buwan":"month","taon":"year",',
' "ipakita":"show","ipakita’":"show","hanapin":"find","hanapan":"find","tingnan":"see",',
' "isara":"collapse","buksan":"expand","kumpara":"compare","kumparahin":"compare","kontra":"vs",',
' "i-export":"export","export":"export",',
' "kita":"income","kinita":"income","benta":"sales","bentahan":"sales","pinagbentahan":"sales",',
' "gastos":"expense","ginastos":"expense","binayad":"paid","bayad":"paid","binili":"bought",',
' "naipon":"cash","pera":"cash","cash":"cash","yaman":"assets","ari-arian":"assets",',
' "utang":"liability","utangin":"liability","kapital":"equity","puhunan":"equity",',
' "lugi":"loss","tubo":"profit","tubo’":"profit","margin":"margin",',
' "magkano":"total","ilan":"count","pinakamalaki":"largest","pinakamaliit":"smallest","top":"top",',
' "average":"average","pinaka-malaki":"largest","pinaka-mataas":"highest","pinaka-mababa":"lowest",',
' "kumusta":"how is","paano":"how","bakit":"why","ano":"what","saan":"where","kailan":"when",',
' "tulong":"help","sagot":"answer","tanong":"ask"',
'};',
'function applyFilipinoDictionary(q){',
' if(!q)return q;',
' var t=" "+q.toLowerCase()+" ";',
' for(var i=0;i<FIL_DICT_PHRASES.length;i++){',
' var fil=FIL_DICT_PHRASES[i][0],en=FIL_DICT_PHRASES[i][1];',
' var re=new RegExp("\\\\b"+fil.replace(/[.*+?^${}()|[\\]\\\\]/g,"\\\\$&")+"\\\\b","gi");',
' t=t.replace(re," "+en+" ");',
' }',
' var tokens=t.split(/\\s+/);',
' var out=[];',
' for(var j=0;j<tokens.length;j++){',
' var tk=tokens[j].toLowerCase();',
' out.push(FIL_DICT_WORDS[tk]?FIL_DICT_WORDS[tk]:tokens[j]);',
' }',
' return out.join(" ").replace(/\\s+/g," ").trim();',
'}',
'',
'/* ─── extractDatePhrase - find date-like substring inside any query ─── */',
'function extractDatePhrase(q){',
' if(!q)return null;',
' var t=q.toLowerCase();',
' var phrases=[',
' /\\b(this\\s+fy|last\\s+fy|this\\s+financial\\s+year|last\\s+financial\\s+year|this\\s+quarter|last\\s+quarter|this\\s+month|last\\s+month|year\\s+to\\s+date|month\\s+to\\s+date|quarter\\s+to\\s+date|ngayong\\s+buwan|nakaraang\\s+buwan|ngayong\\s+taon|nakaraang\\s+taon|ngayong\\s+quarter|nakaraang\\s+quarter|this\\s+week|last\\s+week|ngayong\\s+linggo|nakaraang\\s+linggo)\\b/,',
' /\\b(today|now|ngayon|kahapon|yesterday|tfy|lfy|tq|lq|tm|lm|tw|lw|ytd|mtd|qtd|fy)\\b/,',
' /\\bq[1-4]\\s+\\d{4}\\b/,',
' /\\bq[1-4]\\b/,',
' /\\b(?:january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)\\s+\\d{1,2}\\s*[-\u2013]\\s*\\d{1,2}[,]?\\s+\\d{4}\\b/,',
' /\\b(?:january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)\\s+\\d{1,2}[,]?\\s+\\d{4}\\b/,',
' /\\b(?:january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)\\s+\\d{4}\\b/,',
' /\\b\\d{4}-\\d{2}-\\d{2}\\b/,',
' /\\b\\d{4}\\b/',
' ];',
' for(var i=0;i<phrases.length;i++){',
' var m=t.match(phrases[i]);',
' if(m){var p=parseDateChunk(m[0].trim());if(p)return p;}',
' }',
' return null;',
'}',
'/* ═══════════════════════════════════════════════════════════════',
' v4.0 PHASE 2 - Business Owner UX',
' Date intelligence, AR/AP queries, bare-entity fallback,',
' hybrid action buttons with inline expand',
' ═══════════════════════════════════════════════════════════════ */',
'',
'/* State for inline expansion uniqueness */',
'var INLINE_COUNTER = 0;',
'',
'',
'',
'/* ─── Source badge color helper ─── */',
'function sourceBadgeColor(source) {',
' var s = String(source || "").toUpperCase();',
' if (s === "CDB") return "background:#FEE2E2;color:#991B1B";',
' if (s === "CRB") return "background:#D1FAE5;color:#065F46";',
' if (s === "GJ") return "background:#E5E7EB;color:#374151";',
' return "background:#F3F4F6;color:#6B7280";',
'}',
'',
'/* ─── Account type for display ─── */',
'function getAccountType(accountTitle) {',
' for (var i = 0; i < DATA.coa.length; i++) {',
' if (String(DATA.coa[i].title).trim() === accountTitle) return String(DATA.coa[i].type).trim();',
' }',
' return "";',
'}',
'',
'/* ─── Day-of-week formatter ─── */',
'function fmtDateWithDay(iso) {',
' if (!iso || iso.length < 10) return iso || "";',
' var d = new Date(iso + "T00:00:00");',
' var days = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];',
' var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];',
' return days[d.getDay()] + ", " + months[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();',
'}',
'',
'/* ─── JE Modal v2 - with day-of-week, source badge, account types, net effect ─── */',
'function showJEDetail(date, ref, entryId) {',
' entryId = entryId || "";',
' var legs = DATA.jeRows.filter(function(je) {',
' if (entryId && (je.entryId || "")) return (je.entryId || "") === entryId;',
' return je.date === date && (je.ref || "") === ref;',
' });',
' if (!legs.length) return;',
' var totalDr = 0, totalCr = 0;',
' legs.forEach(function(l) { totalDr += l.debit || 0; totalCr += l.credit || 0; });',
' var sample = legs[0];',
' JE_CUR_ = { source: String(sample.source||""), entryId: String(sample.entryId||""), row: sample.sourceRow||"", date: String(date||""), ref: String(ref||"") };',
' var accts = legs.map(function(l) { return l.account; }).filter(function(a, i, arr) { return arr.indexOf(a) === i; });',
' var acctsCSV = accts.join("|").replace(/\'/g, "\\\\\'");',
'',
' // Net effect summary',
' var netEffectParts = [];',
' var byAccount = {};',
' legs.forEach(function(l) {',
' if (!byAccount[l.account]) byAccount[l.account] = {dr: 0, cr: 0, type: getAccountType(l.account)};',
' byAccount[l.account].dr += l.debit || 0;',
' byAccount[l.account].cr += l.credit || 0;',
' });',
' Object.keys(byAccount).forEach(function(acc) {',
' var a = byAccount[acc];',
' var net = a.dr - a.cr;',
' if (Math.abs(net) < 0.01) return;',
' var verb = "";',
' if (a.type === "Asset") verb = (net > 0 ? "Increased" : "Decreased");',
' else if (a.type === "Liability" || a.type === "Equity") verb = (net < 0 ? "Increased" : "Decreased");',
' else if (a.type === "Revenue") verb = (net < 0 ? "Recognized" : "Reversed");',
' else if (a.type === "Expense") verb = (net > 0 ? "Recorded" : "Reversed");',
' else verb = (net > 0 ? "Debited" : "Credited");',
' netEffectParts.push(verb + " " + acc + " by " + fmt(Math.abs(net)));',
' });',
' var netEffect = netEffectParts.join(" · ");',
'',
' var html = \'<div class="je-modal-overlay" onclick="closeJEModal(event)">\';',
' html += \'<div class="je-modal" onclick="event.stopPropagation()">\';',
' html += \'<div class="je-modal-hdr">\';',
' html += \'<div class="je-modal-title">\' + esc(ref) + (sample.desc ? \' - \' + esc(sample.desc) : \'\') + \'</div>\';',
' html += \'<button class="je-modal-close" onclick="closeJEModal(event)" title="Close (Esc)">×</button>\';',
' html += \'</div>\';',
' html += \'<div class="je-modal-meta">\';',
' html += \'<span>📅 <b>\' + fmtDateWithDay(sample.date) + \'</b></span>\';',
' if (sample.name) html += \'<span>· 👤 <b>\' + esc(sample.name) + \'</b></span>\';',
' html += \'<span>· <span class="je-source-badge" style="\' + sourceBadgeColor(sample.source) + \'">\' + esc(sample.source || \'JE\') + \'</span></span>\';',
' if (sample.entryId) html += \'<span>· ID: <code>\' + esc(sample.entryId) + \'</code></span>\';',
' if (sample.sourceRow) html += \'<span>· Row <b>\' + esc(String(sample.sourceRow)) + \'</b></span>\';',
' html += \'</div>\';',
' html += \'<table class="je-modal-table"><thead><tr><th>Account</th><th>Type</th><th class="num">Debit</th><th class="num">Credit</th></tr></thead><tbody>\';',
' legs.forEach(function(l) {',
' var typ = getAccountType(l.account);',
' var typeBadge = typ ? \'<span class="acct-type-badge \' + typ.toLowerCase() + \'">\' + typ + \'</span>\' : \'\';',
' html += \'<tr><td>\' + esc(l.account) + \'</td>\';',
' html += \'<td>\' + typeBadge + \'</td>\';',
' html += \'<td class="num">\' + (l.debit > 0 ? fmt(l.debit) : \'-\') + \'</td>\';',
' html += \'<td class="num">\' + (l.credit > 0 ? fmt(l.credit) : \'-\') + \'</td></tr>\';',
' });',
' html += \'<tr class="je-modal-total"><td colspan="2">TOTAL</td>\';',
' html += \'<td class="num">\' + fmt(totalDr) + \'</td>\';',
' html += \'<td class="num">\' + fmt(totalCr) + \'</td></tr>\';',
' if (Math.abs(totalDr - totalCr) > 0.01) {',
' html += \'<tr><td colspan="4" style="color:#DC2626;font-style:italic;text-align:center;padding:8px">⚠ Not balanced - diff: \' + fmt(totalDr - totalCr) + \'</td></tr>\';',
' }',
' html += \'</tbody></table>\';',
' if (netEffect) {',
' html += \'<div class="je-net-effect"><b>Net effect:</b> \' + netEffect + \'</div>\';',
' }',
' html += \'<div class="je-modal-actions">\';',
' html += \'<button class="action-btn" onclick="closeJEModal()">Close</button>\';',
' if (sample.entryId) { html += \'<button class="action-btn" onclick="entryDupFromJE_()">Duplicate</button>\'; html += \'<button class="action-btn" onclick="entryEditFromJE_()">Edit entry</button>\'; html += \'<button id="jeVoidBtn" class="action-btn" style="color:#B00020;border-color:#B00020" onclick="entryVoidFromJE_()">Void entry</button>\'; }',
' html += \'<button class="action-btn action-btn-primary" onclick="closeJEModal();drillFromChatToGL(\\\'\' + acctsCSV + \'\\\',\\\'\' + date + \'\\\',\\\'\' + date + \'\\\',\\\'modal\\\');">Open accounts in GL</button>\';',
' html += \'</div>\';',
' html += \'</div></div>\';',
'',
' var existing = document.getElementById(\'jeModalContainer\');',
' if (existing) existing.parentNode.removeChild(existing);',
' var div = document.createElement(\'div\');',
' div.id = \'jeModalContainer\';',
' div.innerHTML = html;',
' document.body.appendChild(div);',
'}',
'function closeJEModal(event) {',
' if (event && event.stopPropagation) event.stopPropagation();',
' var existing = document.getElementById("jeModalContainer");',
' if (existing && existing.parentNode) existing.parentNode.removeChild(existing);',
'}',
'',
'if (typeof window !== "undefined" && !window.__jeModalEscBound) {',
' document.addEventListener("keydown", function(e) {',
' if (e.key === "Escape" || e.keyCode === 27) closeJEModal();',
' });',
' window.__jeModalEscBound = true;',
'}',
'',
'/* ─── drillFromChatToGL v2 - captures previous state for back navigation ─── */',
'function drillFromChatToGL(accountsStr, fromDate, toDate, sourceTag) {',
' var prevSelected = JSON.parse(JSON.stringify(GL_STATE.selectedAccounts || {}));',
' var prevFromInput = (document.getElementById("glFrom") || {}).value || "";',
' var prevToInput = (document.getElementById("glTo") || {}).value || "";',
' var prevSearchVal = (document.getElementById("glSearch") || {}).value || "";',
'',
' var accts = accountsStr.split("|").filter(function(x) { return x && x.length; });',
' GL_STATE.selectedAccounts = {};',
' for (var i = 0; i < accts.length; i++) GL_STATE.selectedAccounts[accts[i]] = true;',
' GL_STATE.page = 1;',
' GL_STATE.groupMode = "account";',
' GL_STATE._pendingFrom = fromDate;',
' GL_STATE._pendingTo = toDate;',
' GL_STATE._pendingPreset = "custom";',
'',
' DRILL_CONTEXT = {',
' from: "chat-" + (sourceTag || "drill"),',
' prevState: {',
' selectedAccounts: prevSelected,',
' from: prevFromInput,',
' to: prevToInput,',
' search: prevSearchVal',
' }',
' };',
'',
' switchTab("gl");',
'}',
'',
'/* ─── backFromChatDrill - restore prev GL state ─── */',
'function backFromChatDrill() {',
' if (!DRILL_CONTEXT) return;',
' var prev = DRILL_CONTEXT.prevState || {};',
' GL_STATE.selectedAccounts = prev.selectedAccounts || {};',
' GL_STATE._pendingFrom = prev.from || "";',
' GL_STATE._pendingTo = prev.to || "";',
' GL_STATE._pendingPreset = "custom";',
' DRILL_CONTEXT = null;',
' if (typeof buildGLControls === "function") buildGLControls();',
' setTimeout(function() {',
' if (prev.search) {',
' var sEl = document.getElementById("glSearch");',
' if (sEl) sEl.value = prev.search;',
' }',
' if (typeof updateGLAcctBtn === "function") updateGLAcctBtn();',
' if (typeof recomputeGL === "function") recomputeGL();',
' }, 80);',
'}',
'',
'/* ─── matchAccountsInQueryGL - match COA titles in user query ─── */',
'function matchAccountsInQueryGL(t) {',
' var queryTokens = t.split(/\\s+/).filter(function(w) { return w.length >= 3; });',
' var skipWords = ["the","and","for","this","last","next","over","under","january","february","march","april","june","july","august","september","october","november","december","jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec","today","yesterday","kahapon","ngayon","tfy","lfy","tq","lq","tm","lm","tw","lw","ytd","mtd","qtd","fy","week","month","year","quarter","ngayong","nakaraang","linggo","buwan","taon"];',
' var matches = [];',
' for (var i = 0; i < DATA.coa.length; i++) {',
' var a = DATA.coa[i];',
' if (a.active && String(a.active).toUpperCase() === "NO") continue;',
' var title = String(a.title).trim();',
' var titleLower = title.toLowerCase();',
' var hits = 0;',
' for (var j = 0; j < queryTokens.length; j++) {',
' var tok = queryTokens[j];',
' if (skipWords.indexOf(tok) >= 0) continue;',
' if (/^q[1-4]$/.test(tok)) continue;',
' if (/^\\d+$/.test(tok)) continue;',
' if (titleLower.indexOf(tok) >= 0) hits++;',
' }',
' if (hits > 0) matches.push(title);',
' }',
' return matches;',
'}',
'',
'/* ─── tryGLAction - control GL view via chat commands ─── */',
'function tryGLAction(q) {',
' if (typeof ACTIVE_TAB === "undefined" || ACTIVE_TAB !== "gl") return null;',
' var t = q.toLowerCase().trim();',
' if (!t) return null;',
'',
' // Skip if matches v3 report intents (those should switch tabs)',
' if (/^(trial\\s+balance|tb|income\\s+statement|\\bis\\b|balance\\s+sheet|\\bbs\\b|cash\\s+flows?|\\bcf\\b|sce|changes\\s+in\\s+equity|financial\\s+statements?|\\bfs\\b)\\s*$/.test(t)) return null;',
'',
' // Clear / reset',
' if (/^(clear|reset|all\\s+accounts|all|show\\s+all)\\s*$/.test(t)) {',
' GL_STATE.selectedAccounts = {};',
' var sEl = document.getElementById("glSearch"); if (sEl) sEl.value = "";',
' var pEl = document.getElementById("glPreset"); if (pEl) pEl.value = "fy";',
' setTimeout(function() {',
' if (typeof applyGLPreset === "function") applyGLPreset();',
' if (typeof updateGLAcctBtn === "function") updateGLAcctBtn();',
' if (typeof recomputeGL === "function") recomputeGL();',
' }, 50);',
' return {silent: true, html: renderAlertCard({severity:"green", title:"✓ GL cleared - all accounts, default date range", text:""})};',
' }',
'',
' // Group mode',
' var groupMatch = t.match(/^group\\s+by\\s+(account|name)/);',
' if (groupMatch) {',
' GL_STATE.groupMode = groupMatch[1];',
' var ggEl = document.getElementById("glGrouping"); if (ggEl) ggEl.value = groupMatch[1];',
' if (typeof recomputeGL === "function") recomputeGL();',
' return {silent: true, html: renderAlertCard({severity:"green", title:"✓ Grouped by " + groupMatch[1], text:""})};',
' }',
'',
' // Explicit search verb',
' var searchMatch = t.match(/^(?:find|search|filter|hanapin)\\s+(.+)/);',
' if (searchMatch && searchMatch[1]) {',
' var sEl2 = document.getElementById("glSearch");',
' if (sEl2) sEl2.value = searchMatch[1].trim();',
' if (typeof recomputeGL === "function") recomputeGL();',
' return {silent: true, html: renderAlertCard({severity:"green", title:"✓ Filtered to \'" + searchMatch[1].trim() + "\'", text:""})};',
' }',
'',
' // Bare query: match accounts + date + free text',
' var dt = (typeof extractDatePhrase === "function") ? extractDatePhrase(t) : null;',
' var coaMatches = matchAccountsInQueryGL(t);',
'',
' // Strip date words and matched account words from query for free text',
' var freeText = t;',
' coaMatches.forEach(function(a) {',
' var aLower = a.toLowerCase();',
' freeText = freeText.split(aLower).join(" ");',
' });',
' freeText = freeText',
' .replace(/\\b(this|last|next|today|now|ngayon|kahapon|yesterday|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|june|july|august|september|october|november|december|q1|q2|q3|q4|fy|ytd|mtd|qtd|tw|lw|tm|lm|tq|lq|tfy|lfy|week|month|year|quarter|ngayong|nakaraang|linggo|buwan|taon)\\b/gi, " ")',
' .replace(/\\b\\d{4}\\b/g, " ")',
' .replace(/\\b\\d+\\b/g, " ")',
' .replace(/\\s+/g, " ").trim();',
'',
' if (dt || coaMatches.length > 0 || freeText.length >= 3) {',
' if (dt) {',
' GL_STATE._pendingFrom = dt.from;',
' GL_STATE._pendingTo = dt.to;',
' GL_STATE._pendingPreset = "custom";',
' }',
' if (coaMatches.length > 0) {',
' GL_STATE.selectedAccounts = {};',
' coaMatches.forEach(function(a) { GL_STATE.selectedAccounts[a] = true; });',
' }',
'',
' setTimeout(function() {',
' if (dt) {',
' var fEl = document.getElementById("glFrom"); if (fEl) fEl.value = dt.from;',
' var tEl = document.getElementById("glTo"); if (tEl) tEl.value = dt.to;',
' var pEl = document.getElementById("glPreset"); if (pEl) pEl.value = "custom";',
' }',
' var sEl3 = document.getElementById("glSearch");',
' if (sEl3) {',
' if (freeText.length >= 3) sEl3.value = freeText;',
' else if (coaMatches.length > 0 || dt) sEl3.value = "";',
' }',
' if (typeof updateGLAcctBtn === "function") updateGLAcctBtn();',
' if (typeof recomputeGL === "function") recomputeGL();',
' }, 50);',
'',
' var labelParts = [];',
' if (coaMatches.length > 0) labelParts.push("Accounts: " + coaMatches.slice(0, 2).join(", ") + (coaMatches.length > 2 ? " +" + (coaMatches.length - 2) : ""));',
' if (freeText.length >= 3) labelParts.push("Search: \'" + freeText + "\'");',
' if (dt) labelParts.push(dt.label);',
'',
' return {silent: true, html: renderAlertCard({severity:"green", title:"✓ GL filtered - " + labelParts.join(" · "), text:""})};',
' }',
'',
' return null;',
'}',
'',
'/* ─── Floating toast for non-GL queries while on GL ─── */',
'function showFloatingToast(html, type) {',
' var existing = document.getElementById("floatingToast");',
' if (existing) existing.parentNode.removeChild(existing);',
' var div = document.createElement("div");',
' div.id = "floatingToast";',
' div.className = "floating-toast";',
' div.innerHTML = \'<div class="floating-toast-hdr"><span class="floating-toast-label">\' + (type || "Result") + \'</span><button class="floating-toast-close" onclick="closeFloatingToast()">×</button></div><div class="floating-toast-body">\' + html + \'</div>\';',
' document.body.appendChild(div);',
'}',
'function closeFloatingToast() {',
' var existing = document.getElementById("floatingToast");',
' if (existing) {',
' existing.style.animation = "toastSlideOut 200ms ease forwards";',
' setTimeout(function() { if (existing.parentNode) existing.parentNode.removeChild(existing); }, 200);',
' }',
'}',
'',
'/* ─── Draggable column resize for GL ─── */',
'function attachGLColumnResizers() {',
' var tables = document.querySelectorAll(".gl-fit-table");',
' for (var ti = 0; ti < tables.length; ti++) {',
' var table = tables[ti];',
' if (table.dataset.resizersAttached === "true") continue;',
' var headers = table.querySelectorAll("thead th");',
' if (!headers.length) continue;',
' table.dataset.resizersAttached = "true";',
' for (var hi = 0; hi < headers.length - 1; hi++) {',
' (function(th) {',
' th.style.position = "relative";',
' var handle = document.createElement("div");',
' handle.className = "col-resize-handle";',
' th.appendChild(handle);',
' handle.addEventListener("mousedown", function(e) {',
' e.preventDefault();',
' e.stopPropagation();',
' var startX = e.clientX;',
' var startWidth = th.offsetWidth;',
' function onMove(ev) {',
' var diff = ev.clientX - startX;',
' var newWidth = Math.max(40, startWidth + diff);',
' th.style.width = newWidth + "px";',
' }',
' function onUp() {',
' document.removeEventListener("mousemove", onMove);',
' document.removeEventListener("mouseup", onUp);',
' document.body.style.cursor = "";',
' document.body.style.userSelect = "";',
' }',
' document.body.style.cursor = "col-resize";',
' document.body.style.userSelect = "none";',
' document.addEventListener("mousemove", onMove);',
' document.addEventListener("mouseup", onUp);',
' });',
' })(headers[hi]);',
' }',
' }',
'}',
'',
'/* Auto-attach resizers when GL renders - patch via setInterval check */',
'if (typeof window !== "undefined" && !window.__glResizeInit) {',
' setInterval(function() {',
' if (typeof attachGLColumnResizers === "function") attachGLColumnResizers();',
' }, 800);',
' window.__glResizeInit = true;',
'}',
'',
'/* Card actions renderer - primary buttons left, muted link right */',
'function renderCardActions(actions) {',
' if (!actions || !actions.length) return "";',
' var primary = [], muted = [];',
' for (var i = 0; i < actions.length; i++) {',
' if (actions[i].muted) muted.push(actions[i]);',
' else primary.push(actions[i]);',
' }',
' var h = \'<div class="card-actions">\';',
' for (var p = 0; p < primary.length; p++) {',
' var a = primary[p];',
' var cls = a.primary ? "action-btn action-btn-primary" : "action-btn";',
' h += \'<button class="\' + cls + \'" onclick="\' + (a.onclick || "") + \'">\' + esc(a.label) + \'</button>\';',
' }',
' for (var m = 0; m < muted.length; m++) {',
' var mm = muted[m];',
' h += \'<button class="action-link" onclick="\' + (mm.onclick || "") + \'">\' + esc(mm.label) + \' ↗</button>\';',
' }',
' h += \'</div>\';',
' return h;',
'}',
'',
'/* AR / AP COA helpers */',
'function findReceivableAccounts() {',
' var out = [];',
' for (var i = 0; i < DATA.coa.length; i++) {',
' var a = DATA.coa[i];',
' if (a.active && String(a.active).toUpperCase() === "NO") continue;',
' if (String(a.type).trim() !== "Asset") continue;',
' if (/receivable|trade.*recv/i.test(a.title)) out.push(String(a.title).trim());',
' }',
' return out;',
'}',
'',
'function findPayableAccounts() {',
' var out = [];',
' for (var i = 0; i < DATA.coa.length; i++) {',
' var a = DATA.coa[i];',
' if (a.active && String(a.active).toUpperCase() === "NO") continue;',
' if (String(a.type).trim() !== "Liability") continue;',
' if (/payable|trade.*pay|accrued.*pay/i.test(a.title)) out.push(String(a.title).trim());',
' }',
' return out;',
'}',
'',
'/* Toggle inline customer/supplier detail expansion */',
'function toggleCustomerDetail(detailId, customerName, accountsCSV, asOfDate, mode) {',
' var container = document.getElementById(detailId);',
' if (!container) return;',
' if (container.dataset.expanded === "true") {',
' container.style.display = "none";',
' container.dataset.expanded = "false";',
' container.innerHTML = "";',
' var togRow = container.previousElementSibling;',
' if (togRow) {',
' var tog = togRow.querySelector(".cust-toggle");',
' if (tog) tog.innerHTML = "▾";',
' }',
' return;',
' }',
' var accounts = accountsCSV.split("|").filter(function(x) { return x && x.length; });',
' var rows = DATA.jeRows.filter(function(je) {',
' return accounts.indexOf(je.account) >= 0 && (je.name || "(No Name)") === customerName && je.date <= asOfDate;',
' }).sort(function(a, b) { return a.date < b.date ? -1 : 1; });',
'',
' if (!rows.length) {',
' container.innerHTML = \'<div style="padding:10px 16px 10px 50px;font-size:11px;color:#6B7280;font-style:italic">Walang transactions para sa customer na ito.</div>\';',
' } else {',
' var html = \'<table class="cust-detail-table"><thead><tr><th>Date</th><th>Ref</th><th>Description</th><th class="num">Debit</th><th class="num">Credit</th></tr></thead><tbody>\';',
' var dr = 0, cr = 0;',
' for (var i = 0; i < rows.length; i++) {',
' var r = rows[i];',
' dr += r.debit || 0;',
' cr += r.credit || 0;',
' html += \'<tr><td>\' + fmtGLDate(r.date) + \'</td><td>\' + esc(r.ref || "") + \'</td><td>\' + esc(r.desc || "") + \'</td><td class="num">\' + (r.debit > 0 ? fmt(r.debit) : "-") + \'</td><td class="num">\' + (r.credit > 0 ? fmt(r.credit) : "-") + \'</td></tr>\';',
' }',
' var bal = (mode === "ap") ? (cr - dr) : (dr - cr);',
' html += \'<tr class="cust-total-row"><td colspan="3">Balance</td><td class="num">\' + fmt(dr) + \'</td><td class="num">\' + fmt(cr) + \'</td></tr>\';',
' html += \'</tbody></table>\';',
' html += \'<div style="padding:6px 16px 8px 50px;font-size:11px;color:#6B7280">Net: \' + fmt(bal) + \'</div>\';',
' container.innerHTML = html;',
' }',
' container.style.display = "block";',
' container.dataset.expanded = "true";',
' var togRow2 = container.previousElementSibling;',
' if (togRow2) {',
' var tog2 = togRow2.querySelector(".cust-toggle");',
' if (tog2) tog2.innerHTML = "▴";',
' }',
'}',
'',
'/* tryReceivablesList - sino may utang sakin */',
'function tryReceivablesList(q) {',
' var t = q.toLowerCase().trim();',
' var triggers = [',
' /^sino\\s+(?:ang\\s+)?(?:may\\s+)?utang(?:\\s+sa(?:\\s+a)?kin)?[\\?\\s]*$/,',
' /^sino\\s+(?:ang\\s+)?may\\s+utang/,',
' /^who\\s+owes\\s+(?:me|us)/,',
' /^list\\s+(?:of\\s+)?(?:my\\s+)?(?:receivables|debtors)/',
' ];',
' var matched = false;',
' for (var i = 0; i < triggers.length; i++) if (triggers[i].test(t)) { matched = true; break; }',
' if (!matched) return null;',
'',
' var arAccounts = findReceivableAccounts();',
' if (!arAccounts.length) {',
' return renderAlertCard({severity:"amber", title:"No AR accounts", text:"Walang Accounts Receivable account sa COA mo."});',
' }',
'',
' var asOfDate = "9999-12-31";',
' var dt = extractDatePhrase(t);',
' if (dt && dt.to) asOfDate = dt.to;',
' else { var dates = DATA.jeRows.map(function(e){return e.date;}).filter(Boolean).sort(); if (dates.length) asOfDate = dates[dates.length-1]; }',
'',
' var byName = {};',
' DATA.jeRows.forEach(function(je) {',
' if (arAccounts.indexOf(je.account) < 0) return;',
' if (je.date > asOfDate) return;',
' var name = (je.name || "(No Name)").trim();',
' if (!byName[name]) byName[name] = 0;',
' byName[name] += (je.debit || 0) - (je.credit || 0);',
' });',
'',
' var rows = Object.keys(byName).map(function(n) { return {name:n, balance:byName[n]}; })',
' .filter(function(x) { return x.balance > 0.005; })',
' .sort(function(a, b) { return b.balance - a.balance; });',
'',
' if (!rows.length) {',
' return renderAlertCard({severity:"green", title:"✓ Walang utang sa’yo", text:"Lahat ng customers nakabayad."});',
' }',
'',
' var total = 0;',
' rows.forEach(function(r) { total += r.balance; });',
' var asOfLabel = dt ? dt.label : asOfDate;',
' var top = rows.slice(0, 10);',
' var hasMore = rows.length > 10;',
' var acctsCSV = arAccounts.join("|").replace(/\'/g, "\\\\\'");',
'',
' var listHtml = \'<div class="cust-list">\';',
' for (var ri = 0; ri < top.length; ri++) {',
' var r = top[ri];',
' var did = "arDetail_" + (++INLINE_COUNTER);',
' var nm = escJS(r.name);',
' listHtml += \'<div class="cust-row" onclick="toggleCustomerDetail(\\\'\' + did + \'\\\',\\\'\' + nm + \'\\\',\\\'\' + acctsCSV + \'\\\',\\\'\' + asOfDate + \'\\\',\\\'ar\\\')">\';',
' listHtml += \'<span class="cust-rank">\' + (ri + 1) + \'</span>\';',
' listHtml += \'<span class="cust-name">\' + esc(r.name) + \'</span>\';',
' listHtml += \'<span class="cust-amt">\' + fmt(r.balance) + \'</span>\';',
' listHtml += \'<span class="cust-toggle">▾</span>\';',
' listHtml += \'</div>\';',
' listHtml += \'<div id="\' + did + \'" class="cust-detail-wrap" style="display:none"></div>\';',
' }',
' listHtml += \'</div>\';',
'',
' var actions = [];',
' if (hasMore) actions.push({label: "Show all (+" + (rows.length - 10) + ")", onclick: "refreshToast_(\'Total: " + fmt(total) + " across " + rows.length + " customers. Click \\\\\'Open in full GL\\\\\' to see all.\',false);"});',
' actions.push({label: "Open in full GL", muted: true, onclick: "drillFromChatToGL(\'" + acctsCSV + "\',\'1900-01-01\',\'" + asOfDate + "\')"});',
'',
' var card = \'<div class="card">\';',
' card += \'<div class="card-hdr"><div class="card-icon green">💰</div>\';',
' card += \'<div class="card-title-block"><div class="card-title">Sino may utang sa’yo</div>\';',
' card += \'<div class="card-subtitle">\' + rows.length + \' customer\' + (rows.length===1?\'\':\'s\') + \' · Total: <b style="color:#1F3864">\' + fmt(total) + \'</b> · As of \' + esc(asOfLabel) + \'</div></div></div>\';',
' card += \'<div class="card-body" style="padding:0">\' + listHtml + \'</div>\';',
' card += renderCardActions(actions);',
' card += \'</div>\';',
' return card;',
'}',
'',
'/* tryPayablesList - sino need bayaran */',
'function tryPayablesList(q) {',
' var t = q.toLowerCase().trim();',
' var triggers = [',
' /^sino\\s+(?:ang\\s+)?(?:need|kailangan|dapat)\\s+bayaran/,',
' /^sino\\s+(?:may\\s+)?(?:utang\\s+(?:ko|natin|namin))/,',
' /^who\\s+do\\s+(?:i|we)\\s+owe/,',
' /^list\\s+(?:of\\s+)?(?:my\\s+)?(?:payables|creditors|suppliers)/',
' ];',
' var matched = false;',
' for (var i = 0; i < triggers.length; i++) if (triggers[i].test(t)) { matched = true; break; }',
' if (!matched) return null;',
'',
' var apAccounts = findPayableAccounts();',
' if (!apAccounts.length) {',
' return renderAlertCard({severity:"amber", title:"No AP accounts", text:"Walang Accounts Payable account sa COA mo."});',
' }',
'',
' var asOfDate = "9999-12-31";',
' var dt = extractDatePhrase(t);',
' if (dt && dt.to) asOfDate = dt.to;',
' else { var dates = DATA.jeRows.map(function(e){return e.date;}).filter(Boolean).sort(); if (dates.length) asOfDate = dates[dates.length-1]; }',
'',
' var byName = {};',
' DATA.jeRows.forEach(function(je) {',
' if (apAccounts.indexOf(je.account) < 0) return;',
' if (je.date > asOfDate) return;',
' var name = (je.name || "(No Name)").trim();',
' if (!byName[name]) byName[name] = 0;',
' byName[name] += (je.credit || 0) - (je.debit || 0);',
' });',
'',
' var rows = Object.keys(byName).map(function(n) { return {name:n, balance:byName[n]}; })',
' .filter(function(x) { return x.balance > 0.005; })',
' .sort(function(a, b) { return b.balance - a.balance; });',
'',
' if (!rows.length) {',
' return renderAlertCard({severity:"green", title:"✓ Walang utang", text:"Lahat ng suppliers bayad na."});',
' }',
'',
' var total = 0;',
' rows.forEach(function(r) { total += r.balance; });',
' var asOfLabel = dt ? dt.label : asOfDate;',
' var top = rows.slice(0, 10);',
' var hasMore = rows.length > 10;',
' var acctsCSV = apAccounts.join("|").replace(/\'/g, "\\\\\'");',
'',
' var listHtml = \'<div class="cust-list">\';',
' for (var ri = 0; ri < top.length; ri++) {',
' var r = top[ri];',
' var did = "apDetail_" + (++INLINE_COUNTER);',
' var nm = escJS(r.name);',
' listHtml += \'<div class="cust-row" onclick="toggleCustomerDetail(\\\'\' + did + \'\\\',\\\'\' + nm + \'\\\',\\\'\' + acctsCSV + \'\\\',\\\'\' + asOfDate + \'\\\',\\\'ap\\\')">\';',
' listHtml += \'<span class="cust-rank">\' + (ri + 1) + \'</span>\';',
' listHtml += \'<span class="cust-name">\' + esc(r.name) + \'</span>\';',
' listHtml += \'<span class="cust-amt">\' + fmt(r.balance) + \'</span>\';',
' listHtml += \'<span class="cust-toggle">▾</span>\';',
' listHtml += \'</div>\';',
' listHtml += \'<div id="\' + did + \'" class="cust-detail-wrap" style="display:none"></div>\';',
' }',
' listHtml += \'</div>\';',
'',
' var actions = [];',
' if (hasMore) actions.push({label: "Show all (+" + (rows.length - 10) + ")", onclick: "refreshToast_(\'Total: " + fmt(total) + " across " + rows.length + " suppliers. Click \\\\\'Open in full GL\\\\\' to see all.\',false);"});',
' actions.push({label: "Open in full GL", muted: true, onclick: "drillFromChatToGL(\'" + acctsCSV + "\',\'1900-01-01\',\'" + asOfDate + "\')"});',
'',
' var card = \'<div class="card">\';',
' card += \'<div class="card-hdr"><div class="card-icon amber">💸</div>\';',
' card += \'<div class="card-title-block"><div class="card-title">Sino need bayaran</div>\';',
' card += \'<div class="card-subtitle">\' + rows.length + \' supplier\' + (rows.length===1?\'\':\'s\') + \' · Total: <b style="color:#1F3864">\' + fmt(total) + \'</b> · As of \' + esc(asOfLabel) + \'</div></div></div>\';',
' card += \'<div class="card-body" style="padding:0">\' + listHtml + \'</div>\';',
' card += renderCardActions(actions);',
' card += \'</div>\';',
' return card;',
'}',
'',
'/* tryBareEntity - fallback for bare queries like "depreciation march 2026" */',
'function tryBareEntity(q) {',
' var t = q.toLowerCase().trim();',
' if (t.length < 4) return null;',
' if (/\\b(trial\\s+balance|tb|income\\s+statement|is|balance\\s+sheet|bs|cash\\s+flows?|cf|sce|changes\\s+in\\s+equity|general\\s+ledger|gl|financial\\s+statements?|fs)\\b/.test(t)) return null;',
' if (/^(magkano|total|sum|count|ilan|top|average|avg|gross|net|profit|margin|kumusta|how|health|status|bakit|why|ano|what|define|explain|expand|collapse|isara|buksan|help|tulong|find|hanapin|search|show|sino|who|compare|kumpara|vs)\\b/.test(t)) return null;',
'',
' var dateFilter = extractDatePhrase(t);',
' var textPart = t',
' .replace(/\\b(this\\s+fy|last\\s+fy|this\\s+financial\\s+year|last\\s+financial\\s+year|this\\s+quarter|last\\s+quarter|this\\s+month|last\\s+month|year\\s+to\\s+date|month\\s+to\\s+date|quarter\\s+to\\s+date|ngayong\\s+buwan|nakaraang\\s+buwan|ngayong\\s+taon|nakaraang\\s+taon|ngayong\\s+quarter|nakaraang\\s+quarter|this\\s+week|last\\s+week|ngayong\\s+linggo|nakaraang\\s+linggo)\\b/gi, " ")',
' .replace(/\\b(today|now|ngayon|kahapon|yesterday|tfy|lfy|tq|lq|tm|lm|tw|lw|ytd|mtd|qtd|fy)\\b/gi, " ")',
' .replace(/\\bq[1-4]\\s+\\d{4}\\b/gi, " ")',
' .replace(/\\bq[1-4]\\b/gi, " ")',
' .replace(/\\b(?:january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)\\b/gi, " ")',
' .replace(/\\b\\d{4}-\\d{2}-\\d{2}\\b/g, " ")',
' .replace(/\\b\\d{4}\\b/g, " ")',
' .replace(/\\b\\d+\\b/g, " ")',
' .replace(/\\s+/g, " ").trim();',
'',
' if (!textPart || textPart.length < 3) return null;',
'',
' var rows = DATA.jeRows.slice();',
' if (dateFilter && dateFilter.from && dateFilter.to) {',
' rows = rows.filter(function(r) { return r.date >= dateFilter.from && r.date <= dateFilter.to; });',
' }',
' var tw = textPart.split(/\\s+/).filter(Boolean);',
' rows = rows.filter(function(r) {',
' var blob = ((r.desc||"") + " " + (r.name||"") + " " + (r.ref||"") + " " + (r.account||"")).toLowerCase();',
' return tw.every(function(w) { return blob.indexOf(w) >= 0; });',
' });',
'',
' if (!rows.length) return null;',
'',
' rows=(typeof dedupJEsByRef==="function")?dedupJEsByRef(rows):rows;',
' rows.sort(function(a, b) { return a.date < b.date ? 1 : a.date > b.date ? -1 : 0; });',
' var top = rows.slice(0, 20);',
' var subtotal = 0;',
' rows.forEach(function(r) { subtotal += (r.amount!==undefined?r.amount:Math.max(r.debit||0, r.credit||0)); });',
'',
' var acctsSet = {};',
' rows.forEach(function(r) { acctsSet[r.account] = true; });',
' var accountsList = Object.keys(acctsSet);',
'',
' var tableRows = top.map(function(r) {var amt = (r.amount!==undefined?r.amount:Math.max(r.debit||0, r.credit||0));var accts=(r.allAccounts||[r.account||""]).join("|").replace(/\'/g,"\\\\\'");var oc="drillFromChatToGL(\'"+accts+"\',\'"+r.date+"\',\'"+r.date+"\')";return [fmtGLDate(r.date),{text: r.ref||"", cls: "tbl-meta"},r.desc||"",{text: r.account||"", cls: "tbl-meta"},{text: fmt(amt), cls: "num tbl-amt", onclick: oc}];});',
'',
' var fromD = dateFilter ? dateFilter.from : "1900-01-01";',
' var toD = dateFilter ? dateFilter.to : "9999-12-31";',
'',
' var actions = [];',
' actions.push({label: "Magkano total?", onclick: "document.getElementById(\'chatInput\').value=\'total " + textPart + (dateFilter ? \' \' + dateFilter.label.toLowerCase() : \'\') + "\';executeChat();"});',
' if (!dateFilter) {',
' actions.push({label: "This month", onclick: "document.getElementById(\'chatInput\').value=\'" + textPart + " this month\';executeChat();"});',
' }',
' actions.push({label: "Open in full GL", muted: true, onclick: "drillFromChatToGL(\'" + accountsList.join("|").replace(/\'/g, "\\\\\'") + "\',\'" + fromD + "\',\'" + toD + "\')"});',
'',
' var subtitle = rows.length + \' transaction\' + (rows.length===1?\'\':\'s\') +',
' \' · Subtotal: <b style="color:#1F3864">\' + fmt(subtotal) + \'</b>\' +',
' (dateFilter ? \' · \' + dateFilter.label : \'\') +',
' (rows.length > 20 ? \' · Showing top 20 by date\' : \'\');',
'',
' var card = renderTableCard({',
' icon: "🔍",',
' iconClass: "blue",',
' title: "Results for \\"" + textPart + "\\"",',
' subtitle: subtitle,',
' cols: [',
' {label:"Date", width:"90px"},',
' {label:"Ref", width:"90px"},',
' {label:"Description"},',
' {label:"Account"},',
' {label:"Amount", num:true}',
' ],',
' rows: tableRows,',
' totalRow: [{text:"Subtotal · "+rows.length+" transactions", colspan:4}, {text:fmt(subtotal), cls:"num"}]',
' });',
'',
' return card + renderCardActions(actions);',
'}',
'',
'/* ─── EDU_DEFS (Sprint 2 Engine 8) ─── */',
'var EDU_DEFS = {',
' "gross profit":{title:"Gross Profit",aka:["gp"],formula:"Sales − Cost of Sales",plain:"Yung kita mo bago mo i-deduct ang operating expenses (rent, salaries, utilities). Direct costs lang ang ibinabawas.",bench:"Healthy retail margin: 25-40%. Below 15% = thin."},',
' "net income":{title:"Net Income",aka:["ni","kita","net profit","tubo"],formula:"Sales − COGS − OpEx − Other Exp − Income Tax",plain:"Yung tunay na natira after lahat ng gastos at buwis. Ito ang “kita” talaga ng business.",bench:"Healthy: 10-20% of sales. Below 5% = needs review."},',
' "nibt":{title:"Net Income Before Income Tax",aka:["net income before tax","pre-tax income"],formula:"Gross Profit + Other Income − OpEx − Other Exp",plain:"Profit bago bayaran ang income tax. Ito ang base ng tax computation.",bench:""},',
' "sales":{title:"Sales / Revenue",aka:["revenue","income","kita","benta"],formula:"Total ng nabentang produkto / serbisyo",plain:"Total amount na pumasok sa business mula sa pagbenta. Net of returns at discounts.",bench:"Track MoM growth para makita ang trend."},',
' "cogs":{title:"Cost of Goods Sold (COGS)",aka:["cost of sales","cos"],formula:"Beginning Inventory + Purchases − Ending Inventory",plain:"Direct cost ng mga ibinenta mong produkto. Para sa retail/trading, ito ang puhunan sa stocks.",bench:"For retail: ~60-75% of sales is normal."},',
' "opex":{title:"Operating Expenses",aka:["operating expense","operating expenses"],formula:"Sum of rent + utilities + salaries + supplies + atbp.",plain:"Lahat ng gastos para mapatakbo ang business araw-araw. Hindi kasama ang COGS.",bench:""},',
' "assets":{title:"Assets",aka:["asset","yaman","ari-arian"],formula:"Cash + Receivables + Inventory + PPE + atbp.",plain:"Lahat ng pag-aari ng business - cash, hindi pa nababayarang utang sa’yo, stocks, kagamitan.",bench:""},',
' "liabilities":{title:"Liabilities",aka:["liability","utang"],formula:"Payables + Loans + Accrued + atbp.",plain:"Lahat ng utang ng business - sa supplier, sa bangko, sa empleyado, sa BIR.",bench:""},',
' "equity":{title:"Equity / Owner’s Equity",aka:["capital","puhunan","kapital","owner equity"],formula:"Beginning Capital + Net Income + Contributions − Withdrawals",plain:"Yung talagang “sa’yo” na bahagi ng business. Assets minus Liabilities. Pera mong nakatambak sa business.",bench:""},',
' "ar":{title:"Accounts Receivable (AR)",aka:["accounts receivable","receivables"],formula:"Total ng utang ng customers sa’yo",plain:"Mga customer na hindi pa nagbabayad. Asset ito kasi inaasahan mong makukuha mo sa kanila.",bench:"AR aging > 60 days = collection problem."},',
' "ap":{title:"Accounts Payable (AP)",aka:["accounts payable","payables"],formula:"Total ng utang mo sa suppliers",plain:"Mga supplier na hindi mo pa nababayaran. Liability ito kasi may obligation kang bayaran.",bench:""},',
' "trial balance":{title:"Trial Balance",aka:["tb"],formula:"List of all accounts with their debit/credit balances",plain:"Listahan ng lahat ng accounts at kung magkano ang balance. Test kung balanced ang books mo - dapat pantay ang Total Debits at Total Credits.",bench:""},',
' "debit":{title:"Debit (DR)",aka:["dr"],formula:"Left side of journal entry. Increases Asset/Expense, decreases Liab/Equity/Income.",plain:"Sa accounting, hindi naman “negatibo” ang debit. Just “left side” ng entry.",bench:""},',
' "credit":{title:"Credit (CR)",aka:["cr"],formula:"Right side of journal entry. Increases Liab/Equity/Income, decreases Asset/Expense.",plain:"Sa accounting, hindi naman “positibo” ang credit. Just “right side” ng entry.",bench:""},',
' "cash flow":{title:"Cash Flow",aka:["cf","statement of cash flows","cashflow"],formula:"Operating + Investing + Financing = Net Change in Cash",plain:"Saan napunta o galing ang cash mo. Different from Net Income kasi may non-cash items (e.g., depreciation).",bench:""},',
' "depreciation":{title:"Depreciation",aka:["depr","accumulated depreciation"],formula:"Cost / Useful Life (straight-line method)",plain:"Yung paunti-unting bawas sa value ng equipment/furniture mo over years. Non-cash expense.",bench:"Useful lives: Office equipment 5y, Furniture 5y, Vehicle 5y, Building 20-30y."},',
' "vat":{title:"Value-Added Tax (VAT)",aka:[],formula:"12% of vatable sales",plain:"Buwis na 12% sa benta. Kung VAT-registered ka, ipo-pass on mo to sa customer (Output VAT) at mababawasan mo ng Input VAT galing sa purchases.",bench:"VAT-registered if annual sales > P3M."},',
' "withholding tax":{title:"Withholding Tax",aka:["wht","ewt"],formula:"% of payment, depending on payee/transaction",plain:"Buwis na ikinakaltas sa bayad bago ipasa sa supplier/empleyado. Ipapasa sa BIR. Ikaw ang “withholding agent.”",bench:""},',
' "cwt":{title:"Creditable Withholding Tax (CWT)",aka:["form 2307"],formula:"WHT na sinangagan sa’yo ng customer (binigyan ka ng Form 2307)",plain:"Pre-paid income tax mo - binawas na ng customer mo at idineposito sa BIR. Pwede mong i-credit sa annual income tax mo.",bench:""},',
' "1701":{title:"BIR Form 1701 (Annual Income Tax Return)",aka:["annual itr"],formula:"Pinaka-annual income tax return for individual taxpayers (graduated rates)",plain:"Annual ITR for sole proprietors using graduated tax rates. Filing deadline: April 15.",bench:""},',
' "1701q":{title:"BIR Form 1701Q (Quarterly Income Tax)",aka:["quarterly itr"],formula:"Quarterly income tax return",plain:"Quarterly ITR. Deadlines: Q1 May 15, Q2 Aug 15, Q3 Nov 15. Q4 walang separate - covered ng 1701 (annual).",bench:""},',
' "1701a":{title:"BIR Form 1701A (8% Optional)",aka:["8 percent","optional rate"],formula:"8% of gross sales (in lieu of graduated + percentage tax)",plain:"For self-employed/professionals na nag-opt sa 8% flat rate. Annual sales must be ≤ P3M.",bench:""},',
' "2551q":{title:"BIR Form 2551Q (Percentage Tax)",aka:["percentage tax"],formula:"Generally 3% of gross receipts (Non-VAT)",plain:"Quarterly percentage tax for Non-VAT registered. Filing deadline: 25 days after quarter-end.",bench:""},',
' "fy":{title:"Fiscal Year (FY)",aka:["financial year"],formula:"12-month accounting period",plain:"Yung accounting period ng business. Karamihan calendar year (Jan-Dec) pero pwedeng iba.",bench:""},',
' "margin":{title:"Margin",aka:["profit margin","gross margin","net margin"],formula:"Gross Margin = GP / Sales × 100. Net Margin = NI / Sales × 100",plain:"Profitability ratio. Ang “margin” ay percent ng sales na natitira after costs.",bench:"GM 25%+ retail. NM 10%+ healthy."}',
'};',
'function findEduDef(q){',
' q=q.toLowerCase();',
' var keys=Object.keys(EDU_DEFS);',
' for(var i=0;i<keys.length;i++){',
' var k=keys[i],d=EDU_DEFS[k];',
' var aliases=[k].concat(d.aka||[]);',
' for(var a=0;a<aliases.length;a++){',
' var al=aliases[a].toLowerCase();',
' if(q.indexOf(al)>=0)return {key:k,def:d,matched:al};',
' }',
' }',
' return null;',
'}',
'function tryEducation(q){',
' var t=q.toLowerCase().trim();',
' var triggers=[/^ano\\s+yung\\s+/,/^ano\\s+ang\\s+/,/^ano\\s+ba\\s+yung\\s+/,/^what\\s+is\\s+/,/^what’s\\s+/,/^whats\\s+/,/^define\\s+/,/^explain\\s+/,/^paano\\s+(?:kinompute|computed)\\s+/,/^how\\s+is\\s+\\w+\\s+computed/];',
' var matchedTrigger=false;',
' for(var i=0;i<triggers.length;i++){if(triggers[i].test(t)){matchedTrigger=true;break;}}',
' if(!matchedTrigger)return null;',
' var found=findEduDef(t);',
' if(!found)return null;',
' var d=found.def,html="";',
' var paragraphs=[];',
' if(d.formula)paragraphs.push("<b>"+esc(d.title)+":</b> "+esc(d.formula));',
' if(d.plain)paragraphs.push(esc(d.plain));',
' var computedBox=tryComputeFromBooks(found.key);',
' var note=d.bench?d.bench:"";',
' return renderProseCard({icon:"📚",title:d.title,paragraphs:paragraphs,computedBox:computedBox,note:note});',
'}',
'function tryComputeFromBooks(key){',
' try{',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' if(!dates.length)return "";',
' var maxD=dates[dates.length-1];',
' var yr=parseInt(maxD.substring(0,4));',
' var fyStart=fyM===1?yr+"-01-01":(parseInt(maxD.substring(5,7))>=fyM?yr+"-"+pad(fyM)+"-01":(yr-1)+"-"+pad(fyM)+"-01");',
' if(key==="gross profit"){var is=computeIS(DATA.jeRows,DATA.coa,fyStart,maxD,fyM);if(is.totalRevenue>0)return "<b>Sa books mo (FY-to-date):</b><br>Sales: <span class=\\"num-inline\\">"+fmt(is.totalRevenue)+"</span><br>Less COGS: <span class=\\"num-inline\\">"+fmt(is.totalCOS)+"</span><br><b>Gross Profit:</b> <span class=\\"num-inline\\">"+fmt(is.grossProfit)+"</span>"+(is.totalRevenue?" · <b>"+(is.grossProfit/is.totalRevenue*100).toFixed(1)+"% margin</b>":"");}',
' if(key==="net income"){var is2=computeIS(DATA.jeRows,DATA.coa,fyStart,maxD,fyM);return "<b>Sa books mo (FY-to-date):</b><br>Net Income: <span class=\\"num-inline\\">"+fmt(is2.netIncome)+"</span>"+(is2.totalRevenue?" · <b>"+(is2.netIncome/is2.totalRevenue*100).toFixed(1)+"% net margin</b>":"");}',
' if(key==="sales"){var is3=computeIS(DATA.jeRows,DATA.coa,fyStart,maxD,fyM);return "<b>Sa books mo:</b><br>FY-to-date Sales: <span class=\\"num-inline\\">"+fmt(is3.totalRevenue)+"</span>";}',
' if(key==="cogs"){var is4=computeIS(DATA.jeRows,DATA.coa,fyStart,maxD,fyM);return "<b>Sa books mo:</b><br>FY-to-date COGS: <span class=\\"num-inline\\">"+fmt(is4.totalCOS)+"</span>";}',
' if(key==="opex"){var is5=computeIS(DATA.jeRows,DATA.coa,fyStart,maxD,fyM);return "<b>Sa books mo:</b><br>FY-to-date OpEx: <span class=\\"num-inline\\">"+fmt(is5.totalOpEx)+"</span>";}',
' if(key==="margin"){var is6=computeIS(DATA.jeRows,DATA.coa,fyStart,maxD,fyM);if(is6.totalRevenue>0)return "<b>Sa books mo:</b><br>Gross Margin: <span class=\\"num-inline\\">"+(is6.grossProfit/is6.totalRevenue*100).toFixed(1)+"%</span><br>Net Margin: <span class=\\"num-inline\\">"+(is6.netIncome/is6.totalRevenue*100).toFixed(1)+"%</span>";}',
' if(key==="assets"||key==="liabilities"||key==="equity"){var bs=computeBS(DATA.jeRows,DATA.coa,maxD,fyM);if(key==="assets")return "<b>Sa books mo:</b><br>Total Assets: <span class=\\"num-inline\\">"+fmt(bs.totalAssets)+"</span>";if(key==="liabilities")return "<b>Sa books mo:</b><br>Total Liabilities: <span class=\\"num-inline\\">"+fmt(bs.totalLiab)+"</span>";return "<b>Sa books mo:</b><br>Total Equity: <span class=\\"num-inline\\">"+fmt(bs.totalEquity)+"</span>";}',
' }catch(e){}',
' return "";',
'}',
'',
'/* ─── dedupJEsByRef - group paired JE legs into one consolidated row ─── */',
'function dedupJEsByRef(rows){',
' var groups={};',
' for(var i=0;i<rows.length;i++){',
' var r=rows[i];',
' var key=r.date+"|"+(r.ref||"")+"|"+(r.entryId||"")+"|"+(r.desc||"");',
' if(!groups[key])groups[key]=[];',
' groups[key].push(r);',
' }',
' var out=[];',
' Object.keys(groups).forEach(function(key){',
' var group=groups[key];',
' var totalDr=0,totalCr=0,nonCashAccts=[],allAccts=[];',
' group.forEach(function(g){',
' totalDr+=g.debit||0;totalCr+=g.credit||0;',
' if(allAccts.indexOf(g.account)<0)allAccts.push(g.account);',
' if(!/cash/i.test(g.account)&&nonCashAccts.indexOf(g.account)<0)nonCashAccts.push(g.account);',
' });',
' var primaryAccount;',
' if(nonCashAccts.length===1)primaryAccount=nonCashAccts[0];',
' else if(nonCashAccts.length>1)primaryAccount=nonCashAccts.join(" + ");',
' else primaryAccount=allAccts[0]||"";',
' var sample=group[0];',
' out.push({',
' date:sample.date,ref:sample.ref,desc:sample.desc,name:sample.name,',
' account:primaryAccount,allAccounts:allAccts,',
' amount:Math.max(totalDr,totalCr),',
' debit:totalDr,credit:totalCr',
' });',
' });',
' return out;',
'}',
'/* ─── SEARCH ENGINE (Sprint 2 Engine 3) ─── */',
'function trySearch(q){',
' var t=q.toLowerCase().trim();',
' var triggers=[/^find\\s+/,/^hanapin\\s+/,/^search\\s+/,/^show\\s+(?:me\\s+)?(?:transactions|entries|payments)\\s+/,/transactions?\\s+(?:with|for|to|from)\\s+/,/payments?\\s+to\\s+/,/payments?\\s+for\\s+/];',
' var matched=false;for(var i=0;i<triggers.length;i++){if(triggers[i].test(t)){matched=true;break;}}',
' if(!matched)return null;',
' var amtFilter=null,dateFilter=null,textFilter="";',
' var amtOver=t.match(/(?:over|greater\\s+than|more\\s+than|>)\\s*([\\d,]+)/);',
' var amtLess=t.match(/(?:less\\s+than|under|<)\\s*([\\d,]+)/);',
' var amtBetween=t.match(/between\\s+([\\d,]+)\\s+and\\s+([\\d,]+)/);',
' if(amtBetween){amtFilter={op:"between",lo:parseFloat(amtBetween[1].replace(/,/g,"")),hi:parseFloat(amtBetween[2].replace(/,/g,""))};}',
' else if(amtOver){amtFilter={op:"over",val:parseFloat(amtOver[1].replace(/,/g,""))};}',
' else if(amtLess){amtFilter={op:"under",val:parseFloat(amtLess[1].replace(/,/g,""))};}',
' dateFilter=(typeof extractDatePhrase==="function"?extractDatePhrase(t):parseDateChunk(t))||null;',
' var clean=t;',
' ["find","hanapin","search","show","me","transactions","transaction","entries","payments","payment","with","for","to","from","over","greater","than","more","less","under","between","and"].forEach(function(w){clean=clean.replace(new RegExp("\\\\b"+w+"\\\\b","g")," ");});',
' clean=clean.replace(/[\\d,.]+/g," ").replace(/\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|june|july|august|september|october|november|december|q1|q2|q3|q4|today|yesterday|this|last|fy|ytd|mtd|qtd|year|month|quarter|week|today)\\b/gi," ");',
' textFilter=clean.replace(/\\s+/g," ").trim();',
' var rows=DATA.jeRows.slice();',
' if(dateFilter&&dateFilter.from&&dateFilter.to){rows=rows.filter(function(r){return r.date>=dateFilter.from&&r.date<=dateFilter.to;});}',
' if(textFilter){var tw=textFilter.split(/\\s+/).filter(Boolean);rows=rows.filter(function(r){var blob=((r.desc||"")+" "+(r.name||"")+" "+(r.ref||"")+" "+(r.account||"")).toLowerCase();return tw.every(function(word){return blob.indexOf(word)>=0;});});}',
' if(amtFilter){rows=rows.filter(function(r){var amt=Math.max(r.debit||0,r.credit||0);if(amtFilter.op==="over")return amt>amtFilter.val;if(amtFilter.op==="under")return amt<amtFilter.val;if(amtFilter.op==="between")return amt>=amtFilter.lo&&amt<=amtFilter.hi;return true;});}',
' if(rows.length===0){return renderAlertCard({severity:"amber",title:"No matches",text:"Walang nahanap na transaction. Try ibang search term o expand mo ang date range."});}',
' rows=(typeof dedupJEsByRef==="function")?dedupJEsByRef(rows):rows;',
' rows.sort(function(a,b){return a.date<b.date?1:a.date>b.date?-1:0;});',
' var top=rows.slice(0,50);',
' var subtotal=0;rows.forEach(function(r){subtotal+=(r.amount!==undefined?r.amount:Math.max(r.debit||0,r.credit||0));});',
' var tableRows=top.map(function(r){var amt=(r.amount!==undefined?r.amount:Math.max(r.debit||0,r.credit||0));var accts=(r.allAccounts||[r.account||""]).join("|").replace(/\'/g,"\\\\\'");var oc="drillFromChatToGL(\'"+accts+"\',\'"+r.date+"\',\'"+r.date+"\')";return [fmtGLDate(r.date),{text:r.ref||"",cls:"tbl-meta"},r.desc||"",{text:r.account||"",cls:"tbl-meta"},{text:fmt(amt),cls:"num tbl-amt",onclick:oc}];});',
' var subtitle=rows.length+" transaction"+(rows.length===1?"":"s")+" · Subtotal: <b style=\\"color:#1F3864\\">"+fmt(subtotal)+"</b>"+(rows.length>50?" · Showing top 50 by date":"");',
' return renderTableCard({icon:"🔍",iconClass:"blue",title:"Search results",subtitle:subtitle,cols:[{label:"Date",width:"90px"},{label:"Ref",width:"90px"},{label:"Description"},{label:"Account"},{label:"Amount",num:true}],rows:tableRows,totalRow:[{text:"Subtotal · "+rows.length+" transactions",colspan:4},{text:fmt(subtotal),cls:"num"}]});',
'}',
'',
'/* ─── AGGREGATION ENGINE (Sprint 2 Engine 4) ─── */',
'function tryAggregation(q){',
' var t=q.toLowerCase().trim();',
' var isTotal=/(?:^|\\s)(?:total|sum|magkano(?:\\s+lahat)?|how\\s+much)/.test(t);',
' var isCount=/(?:^|\\s)(?:count|ilan|how\\s+many)/.test(t);',
' var isAvg=/(?:^|\\s)(?:average|avg|mean|monthly\\s+average)/.test(t);',
' var topMatch=t.match(/top\\s+(\\d+)|pinakamalaki(?:ng)?\\s+(\\d+)|largest\\s+(\\d+)/);',
' var isRatio=/profit\\s+margin|gross\\s+margin|net\\s+margin|growth\\s+rate/.test(t);',
' var hasEntity=/(?:^|\\s)(?:sales|benta|revenue|kita|expens|gastos|opex|cash|naipon|pera|asset|yaman|liab|utang|receivable|payable|inventory)/.test(t);',
' var hasDatePhrase=(typeof extractDatePhrase==="function"&&!!extractDatePhrase(t))||(typeof parseDateChunk==="function"&&!!parseDateChunk(t));',
' if(!isTotal&&!isCount&&!isAvg&&!topMatch&&!isRatio){if(hasEntity&&hasDatePhrase)isTotal=true;else return null;}',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' var maxD=dates[dates.length-1]||"2026-12-31";',
' var dateFilter=(typeof extractDatePhrase==="function"?extractDatePhrase(t):parseDateChunk(t));',
' var fromD=dateFilter?dateFilter.from:(fyM===1?parseInt(maxD.substring(0,4))+"-01-01":(parseInt(maxD.substring(5,7))>=fyM?parseInt(maxD.substring(0,4))+"-"+pad(fyM)+"-01":(parseInt(maxD.substring(0,4))-1)+"-"+pad(fyM)+"-01"));',
' var toD=dateFilter?dateFilter.to:maxD;',
' var periodLabel=dateFilter?dateFilter.label:"FY-to-date";',
' if(isRatio){',
' var is=computeIS(DATA.jeRows,DATA.coa,fromD,toD,fyM);',
' if(/gross\\s+margin/.test(t))return renderKPICard({icon:"📈",iconClass:"green",title:"Gross Margin",label:periodLabel.toUpperCase(),value:is.totalRevenue?(is.grossProfit/is.totalRevenue*100).toFixed(1)+"%":"-",subtitle:"GP / Sales × 100"});',
' if(/net\\s+margin|profit\\s+margin/.test(t))return renderKPICard({icon:"📈",iconClass:"green",title:"Net Margin",label:periodLabel.toUpperCase(),value:is.totalRevenue?(is.netIncome/is.totalRevenue*100).toFixed(1)+"%":"-",subtitle:"NI / Sales × 100"});',
' }',
' var entityType=null;',
' if(/sales|benta|revenue|income(?!\\s+tax)|kita/.test(t))entityType="Revenue";',
' else if(/expens|gastos|opex/.test(t))entityType="Expense";',
' else if(/cash|naipon|pera/.test(t))entityType="Cash";',
' else if(/asset|yaman/.test(t))entityType="Asset";',
' else if(/receivable/.test(t))entityType="Receivable";',
' else if(/payable/.test(t))entityType="Payable";',
' else if(/utang/.test(t)){if(/sakin|sa\\s+akin/.test(t))entityType="Receivable";else if(/\\bko\\b|\\bnatin\\b|\\bnamin\\b/.test(t))entityType="Payable";else entityType="Liability";}',
' else if(/liab/.test(t))entityType="Liability";',
' if(topMatch){',
' var n=parseInt(topMatch[1]||topMatch[2]||topMatch[3]||"5");',
' var typeFilter=entityType||"Expense";',
' var byAcct={};',
' DATA.jeRows.forEach(function(je){',
' if(je.date<fromD||je.date>toD)return;',
' var ai=null;for(var k=0;k<DATA.coa.length;k++)if(String(DATA.coa[k].title).trim()===je.account){ai=DATA.coa[k];break;}',
' if(!ai)return;',
' var ty=String(ai.type).trim();',
' if(typeFilter==="Expense"&&ty!=="Expense")return;',
' if(typeFilter==="Revenue"&&ty!=="Revenue")return;',
' if(typeFilter==="Asset"&&ty!=="Asset")return;',
' if(!byAcct[je.account])byAcct[je.account]=0;',
' var amt=ty==="Revenue"?(je.credit||0)-(je.debit||0):(je.debit||0)-(je.credit||0);',
' byAcct[je.account]+=amt;',
' });',
' var sorted=Object.keys(byAcct).map(function(k){return {account:k,amount:byAcct[k]};}).filter(function(x){return Math.abs(x.amount)>0.005;}).sort(function(a,b){return Math.abs(b.amount)-Math.abs(a.amount);}).slice(0,n);',
' var total=0;sorted.forEach(function(x){total+=x.amount;});',
' var rows=sorted.map(function(x,i){return [(i+1).toString(),x.account,{text:fmt(Math.abs(x.amount)),cls:"num"}];});',
' return renderTableCard({icon:"📊",iconClass:"blue",title:"Top "+sorted.length+" "+(typeFilter||"Expense")+" - "+periodLabel,subtitle:"Total: <b style=\\"color:#1F3864\\">"+fmt(total)+"</b>",cols:[{label:"#",width:"40px"},{label:"Account"},{label:"Amount",num:true}],rows:rows});',
' }',
' if(isCount){',
' var rows2=DATA.jeRows.filter(function(r){return r.date>=fromD&&r.date<=toD;});',
' if(entityType==="Revenue"||entityType==="Expense"||entityType==="Asset"||entityType==="Liability"){',
' rows2=rows2.filter(function(r){var ai=null;for(var k=0;k<DATA.coa.length;k++)if(String(DATA.coa[k].title).trim()===r.account){ai=DATA.coa[k];break;}return ai&&String(ai.type).trim()===entityType;});',
' }',
' return renderKPICard({icon:"📋",iconClass:"blue",title:"Transaction Count",label:periodLabel.toUpperCase()+(entityType?" · "+entityType.toUpperCase():""),value:rows2.length.toLocaleString(),subtitle:rows2.length+" entries in period"});',
' }',
' if(isAvg){',
' var months=monthsBetween(fromD,toD);if(months<1)months=1;',
' if(entityType==="Revenue"){var is7=computeIS(DATA.jeRows,DATA.coa,fromD,toD,fyM);return renderKPICard({icon:"📈",iconClass:"green",title:"Average Monthly Sales",label:periodLabel.toUpperCase(),value:""+fmt(is7.totalRevenue/months),subtitle:"Total "+fmt(is7.totalRevenue)+" ÷ "+months+" months"});}',
' if(entityType==="Expense"){var is8=computeIS(DATA.jeRows,DATA.coa,fromD,toD,fyM);return renderKPICard({icon:"📉",iconClass:"amber",title:"Average Monthly OpEx",label:periodLabel.toUpperCase(),value:""+fmt(is8.totalOpEx/months),subtitle:"Total "+fmt(is8.totalOpEx)+" ÷ "+months+" months"});}',
' }',
' if(isTotal){',
' if(entityType==="Revenue"){var is9=computeIS(DATA.jeRows,DATA.coa,fromD,toD,fyM);return renderKPICard({icon:"💰",iconClass:"green",title:"Total Sales",label:periodLabel.toUpperCase(),value:""+fmt(is9.totalRevenue),subtitle:""});}',
' if(entityType==="Expense"){var is10=computeIS(DATA.jeRows,DATA.coa,fromD,toD,fyM);return renderKPICard({icon:"💸",iconClass:"amber",title:"Total Operating Expenses",label:periodLabel.toUpperCase(),value:""+fmt(is10.totalOpEx),subtitle:""});}',
' if(entityType==="Cash"){var bs2=computeBS(DATA.jeRows,DATA.coa,toD,fyM);var cashTotal=0;if(bs2.types.Asset&&bs2.types.Asset.sections)Object.keys(bs2.types.Asset.sections).forEach(function(s){if(/cash/i.test(s)){cashTotal+=bs2.types.Asset.sections[s].total;}else{Object.keys(bs2.types.Asset.sections[s].noteGroups).forEach(function(ng){bs2.types.Asset.sections[s].noteGroups[ng].accounts.forEach(function(a){if(/cash/i.test(a.title))cashTotal+=a.amount;});});}});return renderKPICard({icon:"💵",iconClass:"green",title:"Total Cash",label:"As of "+periodLabel,value:""+fmt(cashTotal),subtitle:""});}',
' if(entityType==="Receivable"){var arA=findReceivableAccounts();var arT=0;DATA.jeRows.forEach(function(je){if(arA.indexOf(je.account)<0)return;if(je.date>toD)return;arT+=(je.debit||0)-(je.credit||0);});var arActions=renderCardActions([{label:"Sino may utang sakin?",onclick:"document.getElementById(\'chatInput\').value=\'sino may utang sakin\';executeChat();"},{label:"Open in full GL",muted:true,onclick:"drillFromChatToGL(\'"+arA.join("|").replace(/\'/g,"\\\\\'")+"\',\'1900-01-01\',\'"+toD+"\')"}]);return renderKPICard({icon:"💰",iconClass:"green",title:"Total Receivables",label:"As of "+periodLabel,value:""+fmt(arT),subtitle:arA.length+" AR account"+(arA.length===1?"":"s")})+arActions;}',
' if(entityType==="Payable"){var apA=findPayableAccounts();var apT=0;DATA.jeRows.forEach(function(je){if(apA.indexOf(je.account)<0)return;if(je.date>toD)return;apT+=(je.credit||0)-(je.debit||0);});var apActions=renderCardActions([{label:"Sino need bayaran?",onclick:"document.getElementById(\'chatInput\').value=\'sino need bayaran\';executeChat();"},{label:"Open in full GL",muted:true,onclick:"drillFromChatToGL(\'"+apA.join("|").replace(/\'/g,"\\\\\'")+"\',\'1900-01-01\',\'"+toD+"\')"}]);return renderKPICard({icon:"💸",iconClass:"amber",title:"Total Payables",label:"As of "+periodLabel,value:""+fmt(apT),subtitle:apA.length+" AP account"+(apA.length===1?"":"s")})+apActions;}',
' }',
' return null;',
'}',
'',
'/* ─── STATUS / INSIGHT ENGINE (Sprint 3 Engine 6) ─── */',
'function tryStatus(q){',
' var t=q.toLowerCase().trim();',
' if(!/^(?:kumusta|how(?:’s)?\\s+(?:my)?|how\\s+is\\s+my|health\\s+check|status|status\\s+ng|kamusta)/.test(t)&&!/(business|benta|sales|cash|kita|finances)\\s*(?:ko|natin|namin|now|today)?$/.test(t))return null;',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' if(!dates.length)return renderAlertCard({severity:"amber",title:"No data",text:"Walang transactions sa books mo pa."});',
' var maxD=dates[dates.length-1];',
' var yr=parseInt(maxD.substring(0,4)),mo=parseInt(maxD.substring(5,7));',
' var monthStart=yr+"-"+pad(mo)+"-01";',
' var monthEnd=yr+"-"+pad(mo)+"-"+ld(mo,yr);',
' var prevMo=mo-1,prevYr=yr;if(prevMo<1){prevMo=12;prevYr--;}',
' var prevMonthStart=prevYr+"-"+pad(prevMo)+"-01";',
' var prevMonthEnd=prevYr+"-"+pad(prevMo)+"-"+ld(prevMo,prevYr);',
' var fyStart=fyM===1?yr+"-01-01":(mo>=fyM?yr+"-"+pad(fyM)+"-01":(yr-1)+"-"+pad(fyM)+"-01");',
' var isCur=computeIS(DATA.jeRows,DATA.coa,monthStart,monthEnd,fyM);',
' var isPrev=computeIS(DATA.jeRows,DATA.coa,prevMonthStart,prevMonthEnd,fyM);',
' var isYTD=computeIS(DATA.jeRows,DATA.coa,fyStart,maxD,fyM);',
' var bsCur=computeBS(DATA.jeRows,DATA.coa,maxD,fyM);',
' var cashCur=0;Object.keys(bsCur.types.Asset.sections).forEach(function(s){Object.keys(bsCur.types.Asset.sections[s].noteGroups).forEach(function(ng){bsCur.types.Asset.sections[s].noteGroups[ng].accounts.forEach(function(a){if(/cash/i.test(a.title))cashCur+=a.amount;});});});',
' var bsPrev=computeBS(DATA.jeRows,DATA.coa,prevMonthEnd,fyM);',
' var cashPrev=0;Object.keys(bsPrev.types.Asset.sections).forEach(function(s){Object.keys(bsPrev.types.Asset.sections[s].noteGroups).forEach(function(ng){bsPrev.types.Asset.sections[s].noteGroups[ng].accounts.forEach(function(a){if(/cash/i.test(a.title))cashPrev+=a.amount;});});});',
' var salesDelta=isPrev.totalRevenue!==0?((isCur.totalRevenue-isPrev.totalRevenue)/Math.abs(isPrev.totalRevenue)*100):(isCur.totalRevenue!==0?100:0);',
' var niDelta=isPrev.netIncome!==0?((isCur.netIncome-isPrev.netIncome)/Math.abs(isPrev.netIncome)*100):(isCur.netIncome!==0?100:0);',
' var cashDelta=cashCur-cashPrev;',
' var monthName=["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][mo];',
' var insight="";',
' if(isCur.totalRevenue>0){',
' var nm=isCur.netIncome/isCur.totalRevenue*100;',
' if(nm>15)insight="<b>Bottom line:</b> Maganda ang takbo. "+nm.toFixed(1)+"% net margin. Keep it up!";',
' else if(nm>5)insight="<b>Bottom line:</b> Profitable pero may room to improve. "+nm.toFixed(1)+"% net margin.";',
' else if(nm>0)insight="<b>Bottom line:</b> Marginal lang ang profit ("+nm.toFixed(1)+"%). Tingnan ang OpEx.";',
' else insight="<b>Bottom line:</b> Lugi sa buwan na ‘to. Investigate kung anong dahilan.";',
' }else insight="<b>Bottom line:</b> Walang sales sa "+monthName+" pa. Kelangan ng action.";',
' var cells=[',
' {label:"Sales",value:""+fmt(isCur.totalRevenue),delta:(salesDelta>=0?"▲ +":"▼ ")+salesDelta.toFixed(1)+"% MoM",deltaClass:salesDelta>=0?"up":"down"},',
' {label:"Net Income",value:""+fmt(isCur.netIncome),delta:(niDelta>=0?"▲ +":"▼ ")+niDelta.toFixed(1)+"% MoM",deltaClass:niDelta>=0?"up":"down"},',
' {label:"Cash",value:""+fmt(cashCur),delta:(cashDelta>=0?"▲ +":"▼ ")+""+fmt(Math.abs(cashDelta))+" MoM",deltaClass:cashDelta>=0?"up":"down"},',
' {label:"OpEx",value:""+fmt(isCur.totalOpEx),delta:isCur.totalRevenue>0?(isCur.totalOpEx/isCur.totalRevenue*100).toFixed(1)+"% of sales":"",deltaClass:"neutral"},',
' {label:"YTD Sales",value:""+fmt(isYTD.totalRevenue),delta:"FY-to-date",deltaClass:"neutral"},',
' {label:"YTD NI",value:""+fmt(isYTD.netIncome),delta:isYTD.totalRevenue>0?(isYTD.netIncome/isYTD.totalRevenue*100).toFixed(1)+"% margin":"",deltaClass:"neutral"}',
' ];',
' return renderMultiKPI({icon:"📈",iconClass:"green",title:monthName+" "+yr+" Health Check",subtitle:"Month-to-date metrics",cells:cells,insight:insight});',
'}',
'',
'/* ─── VARIANCE ENGINE (Sprint 3 Engine 5) ─── */',
'function tryVariance(q){',
' var t=q.toLowerCase().trim();',
' if(!/^(?:bakit|why|anong\\s+dahilan|what\\s+drove|what\\s+caused|reason\\s+for)/.test(t))return null;',
' var entityType=null;',
' if(/expens|gastos|opex|mataas\\s+(?:yung|ang)?\\s*expens|high(?:er)?\\s+expens/.test(t))entityType="Expense";',
' else if(/sales|benta|revenue|income(?!\\s+tax)|kita/.test(t))entityType="Revenue";',
' else entityType="Expense";',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' if(!dates.length)return null;',
' var maxD=dates[dates.length-1];',
' var yr=parseInt(maxD.substring(0,4)),mo=parseInt(maxD.substring(5,7));',
' var monthStart=yr+"-"+pad(mo)+"-01",monthEnd=maxD;',
' var prevMo=mo-1,prevYr=yr;if(prevMo<1){prevMo=12;prevYr--;}',
' var prevStart=prevYr+"-"+pad(prevMo)+"-01",prevEnd=prevYr+"-"+pad(prevMo)+"-"+ld(prevMo,prevYr);',
' var byAcctCur={},byAcctPrev={};',
' DATA.jeRows.forEach(function(je){',
' var ai=null;for(var k=0;k<DATA.coa.length;k++)if(String(DATA.coa[k].title).trim()===je.account){ai=DATA.coa[k];break;}',
' if(!ai||String(ai.type).trim()!==entityType)return;',
' var amt=entityType==="Revenue"?(je.credit||0)-(je.debit||0):(je.debit||0)-(je.credit||0);',
' if(je.date>=monthStart&&je.date<=monthEnd){if(!byAcctCur[je.account])byAcctCur[je.account]=0;byAcctCur[je.account]+=amt;}',
' if(je.date>=prevStart&&je.date<=prevEnd){if(!byAcctPrev[je.account])byAcctPrev[je.account]=0;byAcctPrev[je.account]+=amt;}',
' });',
' var allAccts={};Object.keys(byAcctCur).forEach(function(k){allAccts[k]=true;});Object.keys(byAcctPrev).forEach(function(k){allAccts[k]=true;});',
' var deltas=Object.keys(allAccts).map(function(k){var cur=byAcctCur[k]||0,prev=byAcctPrev[k]||0;return {account:k,prev:prev,cur:cur,delta:cur-prev,pct:prev!==0?((cur-prev)/Math.abs(prev)*100):(cur!==0?100:0)};}).filter(function(x){return Math.abs(x.delta)>0.005;});',
' deltas.sort(function(a,b){return Math.abs(b.delta)-Math.abs(a.delta);});',
' var top5=deltas.slice(0,5);',
' if(top5.length===0)return renderAlertCard({severity:"amber",title:"No variance",text:"Walang significant change between this month and last month."});',
' var totalCur=0,totalPrev=0;Object.keys(byAcctCur).forEach(function(k){totalCur+=byAcctCur[k];});Object.keys(byAcctPrev).forEach(function(k){totalPrev+=byAcctPrev[k];});',
' var totalDelta=totalCur-totalPrev;',
' var maxAbs=Math.max.apply(null,top5.map(function(x){return Math.abs(x.delta);}));',
' var monthName=["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][mo];',
' var prevMonthName=["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][prevMo];',
' var rows=top5.map(function(x){var bw=Math.abs(x.delta)/maxAbs*100;var cls=entityType==="Expense"?(x.delta>0?"":"green"):(x.delta>0?"green":"");return {label:x.account,prior:""+fmt(Math.abs(x.prev)),current:""+fmt(Math.abs(x.cur)),barWidth:bw,barClass:cls,delta:(x.delta>0?"+":"")+fmt(x.delta),pct:(x.pct>0?"+":"")+x.pct.toFixed(0)+"%",pctClass:x.delta>0?"up":"down"};});',
' var topDriver=top5[0];',
' var insight="<b>Top driver: "+esc(topDriver.account)+"</b> contributed "+fmt(Math.abs(topDriver.delta))+(totalDelta!==0?" ("+(Math.abs(topDriver.delta)/Math.abs(totalDelta)*100).toFixed(0)+"%)":"")+" of the "+(totalDelta>=0?"+":"")+fmt(totalDelta)+" change.";',
' return renderVarianceCard({title:entityType+" change: "+monthName+" vs "+prevMonthName,subtitle:"Total: "+fmt(totalPrev)+" → "+fmt(totalCur)+" · "+(totalDelta>=0?"+":"")+""+fmt(Math.abs(totalDelta)),rows:rows,insight:insight});',
'}',
'',
'/* ─── COMPARE ENGINE (Sprint 3 Engine 7) ─── */',
'/* ─── applyCompareToReport - set the report\'s compare mode + dates + recompute ─── */',
'function applyCompareToReport(tab,sideA,sideB){',
' if(!tab)return null;',
' if(tab==="is"){',
' IS_STATE.from=sideA.from;IS_STATE.to=sideA.to;IS_STATE.preset="custom";',
' IS_STATE.compareMode="custom";IS_STATE.customCompareDate=sideB.to;',
' setTimeout(function(){',
' var fEl=document.getElementById("isFrom");if(fEl)fEl.value=sideA.from;',
' var tEl=document.getElementById("isTo");if(tEl)tEl.value=sideA.to;',
' var pEl=document.getElementById("isPreset");if(pEl)pEl.value="custom";',
' var cEl=document.getElementById("isCmp");if(cEl)cEl.value="custom";',
' var cdEl=document.getElementById("isCustomCmpDate");if(cdEl)cdEl.value=sideB.to;',
' if(typeof onISCmpChange==="function")onISCmpChange();else if(typeof recomputeIS==="function")recomputeIS();',
' },50);',
' return "Income Statement";',
' }',
' if(tab==="bs"){',
' BS_STATE.asOfDate=sideA.to;BS_STATE.preset="custom";',
' BS_STATE.compareMode="custom";BS_STATE.customCompareDate=sideB.to;',
' setTimeout(function(){',
' var dEl=document.getElementById("bsDate");if(dEl)dEl.value=sideA.to;',
' var pEl=document.getElementById("bsPreset");if(pEl)pEl.value="custom";',
' var cEl=document.getElementById("bsCmp");if(cEl)cEl.value="custom";',
' var cdEl=document.getElementById("bsCustomCmpDate");if(cdEl)cdEl.value=sideB.to;',
' if(typeof onBSCmpChange==="function")onBSCmpChange();else if(typeof recomputeBS==="function")recomputeBS();',
' },50);',
' return "Balance Sheet";',
' }',
' if(tab==="tb"){',
' TB_STATE.asOf=sideA.to;TB_STATE.preset="custom";TB_STATE.cmp="custom";TB_STATE.customDate=sideB.to;',
' setTimeout(function(){',
' var aEl=document.getElementById("tbAsOf");if(aEl)aEl.value=sideA.to;',
' var pEl=document.getElementById("tbPreset");if(pEl)pEl.value="custom";',
' var cEl=document.getElementById("tbCmp");if(cEl)cEl.value="custom";',
' var cdEl=document.getElementById("tbCustomDate");if(cdEl)cdEl.value=sideB.to;',
' if(typeof onTBCmpChange==="function")onTBCmpChange();else if(typeof recomputeTB==="function")recomputeTB();',
' },50);',
' return "Trial Balance";',
' }',
' if(tab==="cf"){',
' CF_STATE.from=sideA.from;CF_STATE.to=sideA.to;CF_STATE.preset="custom";',
' CF_STATE.compareMode="custom";',
' setTimeout(function(){',
' var fEl=document.getElementById("cfFrom");if(fEl)fEl.value=sideA.from;',
' var tEl=document.getElementById("cfTo");if(tEl)tEl.value=sideA.to;',
' var pEl=document.getElementById("cfPreset");if(pEl)pEl.value="custom";',
' var cEl=document.getElementById("cfCmp");if(cEl)cEl.value="custom";',
' var cdEl=document.getElementById("cfCustomDate");if(cdEl)cdEl.value=sideB.from||sideB.to;',
' if(typeof onCFCmpChange==="function")onCFCmpChange();else if(typeof recomputeCF==="function")recomputeCF();',
' },50);',
' return "Cash Flows";',
' }',
' if(tab==="sce"){',
' SCE_STATE.from=sideA.from;SCE_STATE.to=sideA.to;SCE_STATE.preset="custom";',
' SCE_STATE.compareMode="custom";SCE_STATE.customCompareFrom=sideB.from;SCE_STATE.customCompareTo=sideB.to;',
' setTimeout(function(){',
' var fEl=document.getElementById("sceFrom");if(fEl)fEl.value=sideA.from;',
' var tEl=document.getElementById("sceTo");if(tEl)tEl.value=sideA.to;',
' var pEl=document.getElementById("scePreset");if(pEl)pEl.value="custom";',
' var cEl=document.getElementById("sceCmp");if(cEl)cEl.value="custom";',
' var cfEl=document.getElementById("sceCmpFrom");if(cfEl)cfEl.value=sideB.from;',
' var ctEl=document.getElementById("sceCmpTo");if(ctEl)ctEl.value=sideB.to;',
' if(typeof onSCECmpChange==="function")onSCECmpChange();else if(typeof recomputeSCE==="function")recomputeSCE();',
' },50);',
' return "Changes in Equity";',
' }',
' return null;',
'}',
'function tryCompare(q){',
' var t=q.toLowerCase().trim();',
' if(!/\\bvs\\.?\\b|\\bversus\\b|\\bkumpara\\b|\\bkontra\\b|\\bcompare\\b|\\bcompared\\s+to\\b/.test(t))return null;',
' var parts=t.split(/\\s+(?:vs\\.?|versus|kumpara(?:\\s+sa)?|kontra|compare(?:d)?\\s+(?:to|with))\\s+/i);',
' if(parts.length!==2)return null;',
' var sideA=parseDateChunk(parts[0])||parseDateChunk(parts[0].replace(/^.*?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|q1|q2|q3|q4|\\d{4})/,"$1"));',
' var sideB=parseDateChunk(parts[1])||parseDateChunk(parts[1].replace(/^.*?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|q1|q2|q3|q4|\\d{4})/,"$1"));',
' if(!sideA||!sideB)return null;',
' if(typeof ACTIVE_TAB!=="undefined"&&(ACTIVE_TAB==="is"||ACTIVE_TAB==="bs"||ACTIVE_TAB==="tb"||ACTIVE_TAB==="cf"||ACTIVE_TAB==="sce")){',
' var rname=applyCompareToReport(ACTIVE_TAB,sideA,sideB);',
' if(rname)return {silent:true,html:renderAlertCard({severity:"green",title:"\u2713 Comparing "+sideA.label+" vs "+sideB.label+" on "+rname,text:""})};',
' }',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var isA=computeIS(DATA.jeRows,DATA.coa,sideA.from,sideA.to,fyM);',
' var isB=computeIS(DATA.jeRows,DATA.coa,sideB.from,sideB.to,fyM);',
' function dlt(a,b){var d=a-b;var p=b!==0?(d/Math.abs(b)*100):(a!==0?100:0);return {delta:d,pct:p};}',
' var rows=[',
' ["Sales",fmt(isA.totalRevenue),fmt(isB.totalRevenue),{text:(function(){var x=dlt(isA.totalRevenue,isB.totalRevenue);return (x.delta>=0?"+":"-")+""+fmt(Math.abs(x.delta))+" ("+(x.pct>=0?"+":"")+x.pct.toFixed(1)+"%)";})(),cls:"num "+(isA.totalRevenue>=isB.totalRevenue?"delta-up":"delta-down")}],',
' ["COGS",fmt(isA.totalCOS),fmt(isB.totalCOS),{text:(function(){var x=dlt(isA.totalCOS,isB.totalCOS);return (x.delta>=0?"+":"-")+""+fmt(Math.abs(x.delta))+" ("+(x.pct>=0?"+":"")+x.pct.toFixed(1)+"%)";})(),cls:"num "+(isA.totalCOS<=isB.totalCOS?"delta-up":"delta-down")}],',
' ["Gross Profit",fmt(isA.grossProfit),fmt(isB.grossProfit),{text:(function(){var x=dlt(isA.grossProfit,isB.grossProfit);return (x.delta>=0?"+":"-")+""+fmt(Math.abs(x.delta))+" ("+(x.pct>=0?"+":"")+x.pct.toFixed(1)+"%)";})(),cls:"num "+(isA.grossProfit>=isB.grossProfit?"delta-up":"delta-down")}],',
' ["OpEx",fmt(isA.totalOpEx),fmt(isB.totalOpEx),{text:(function(){var x=dlt(isA.totalOpEx,isB.totalOpEx);return (x.delta>=0?"+":"-")+""+fmt(Math.abs(x.delta))+" ("+(x.pct>=0?"+":"")+x.pct.toFixed(1)+"%)";})(),cls:"num "+(isA.totalOpEx<=isB.totalOpEx?"delta-up":"delta-down")}],',
' ["Net Income",fmt(isA.netIncome),fmt(isB.netIncome),{text:(function(){var x=dlt(isA.netIncome,isB.netIncome);return (x.delta>=0?"+":"-")+""+fmt(Math.abs(x.delta))+" ("+(x.pct>=0?"+":"")+x.pct.toFixed(1)+"%)";})(),cls:"num "+(isA.netIncome>=isB.netIncome?"delta-up":"delta-down")}]',
' ];',
' var insight=isA.netIncome>=isB.netIncome?"<b>"+esc(sideA.label)+" outperformed "+esc(sideB.label)+"</b> on Net Income.":"<b>"+esc(sideA.label)+" trailed "+esc(sideB.label)+"</b> on Net Income.";',
' return renderCompareCard({title:sideA.label+" vs "+sideB.label,subtitle:"Period comparison",headers:[{label:"Metric"},{label:sideA.label,num:true},{label:sideB.label,num:true},{label:"Δ",num:true}],rows:rows,insight:insight});',
'}',
'',
'/* ─── ANOMALY DAEMON (Sprint 3 Engine 2) ─── */',
'var ANOMALY_RESULTS=[];',
'/* An adjusting/accrual entry (depreciation, COGS adjustment, tax accrual, etc.) - lumpy by design, so EXCLUDED from expense-spike detection to kill false alarms like "Income Tax Expense up 99x". */',
'function isAdjusting_(je){var id=String((je&&je.entryId)||""),rf=String((je&&je.ref)||"");return /^(ADJ|AJE)/i.test(id)||/^(AJE|ADJ|DEP|INS|OPT|IT-)/i.test(rf);}',
'function runAnomalyDaemon(){',
' ANOMALY_RESULTS=[];',
' try{',
' runBooksHealthCheck_();',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' if(!dates.length)return;',
' var maxD=dates[dates.length-1];',
' var yr=parseInt(maxD.substring(0,4)),mo=parseInt(maxD.substring(5,7));',
' var monthStart=yr+"-"+pad(mo)+"-01";',
' var bs=computeBS(DATA.jeRows,DATA.coa,maxD,fyM);',
' if(!bs.balanced){ANOMALY_RESULTS.push({cat:"tb",severity:"red",title:"Trial Balance not balanced",text:"Off by "+fmt(Math.abs(bs.imbalance))+" as of "+maxD+". Check for missing/incorrect entries."});}',
' var byAcctMonth={},countByAcctMonth={},topEntry={};var q=String.fromCharCode(39);',
' DATA.jeRows.forEach(function(je){if(je.date<monthStart||je.date>maxD)return;if(isAdjusting_(je))return;var ai=null;for(var k=0;k<DATA.coa.length;k++)if(String(DATA.coa[k].title).trim()===je.account){ai=DATA.coa[k];break;}if(!ai||String(ai.type).trim()!=="Expense")return;var v=(je.debit||0)-(je.credit||0);if(!byAcctMonth[je.account]){byAcctMonth[je.account]=0;countByAcctMonth[je.account]=0;}byAcctMonth[je.account]+=v;countByAcctMonth[je.account]++;if(v>0&&(!topEntry[je.account]||v>topEntry[je.account].amt))topEntry[je.account]={amt:v,date:je.date,desc:je.desc||""};});',
' var avgWindow=6,sixMoStart=shiftMonths(monthStart,-avgWindow);',
' var byAcctAvg={};',
' DATA.jeRows.forEach(function(je){if(je.date<sixMoStart||je.date>=monthStart)return;if(isAdjusting_(je))return;var ai=null;for(var k=0;k<DATA.coa.length;k++)if(String(DATA.coa[k].title).trim()===je.account){ai=DATA.coa[k];break;}if(!ai||String(ai.type).trim()!=="Expense")return;if(!byAcctAvg[je.account])byAcctAvg[je.account]=0;byAcctAvg[je.account]+=(je.debit||0)-(je.credit||0);});',
' Object.keys(byAcctMonth).forEach(function(acct){var avg=(byAcctAvg[acct]||0)/avgWindow;var cur=byAcctMonth[acct];if(avg>1000&&cur>avg*2){var te=topEntry[acct];var rc=te?(" Largest: "+fmt(te.amt)+" on "+te.date+(te.desc?" ("+esc(te.desc)+")":"")):"";ANOMALY_RESULTS.push({severity:"amber",title:esc(acct)+" up "+(cur/avg).toFixed(1)+"x this month",text:fmt(cur)+" vs "+fmt(avg)+" 6-mo average."+rc,onclick:"drillDown("+q+escJS(acct)+q+")"});}});',
' var totalExp=0;Object.values(byAcctMonth).forEach(function(v){totalExp+=v;});',
' var expType={};for(var et=0;et<DATA.coa.length;et++){if(String(DATA.coa[et].type).trim()==="Expense")expType[String(DATA.coa[et].title).trim()]=1;}',
' DATA.jeRows.forEach(function(je){if(je.date<monthStart||je.date>maxD)return;if(isAdjusting_(je))return;if(!expType[String(je.account||"").trim()])return;var amt=Math.max(je.debit||0,je.credit||0);if(amt>0&&totalExp>0&&amt>totalExp*0.3){ANOMALY_RESULTS.push({severity:"amber",title:"Single expense is "+(amt/totalExp*100).toFixed(0)+"% of monthly expenses",text:esc(je.account)+(je.desc?" \\u00b7 "+esc(je.desc):"")+" \\u00b7 "+fmt(amt)+" on "+je.date,onclick:"drillDown("+q+escJS(je.account)+q+")"});}});',
' var seen={},dupOrd=[];',
' DATA.jeRows.forEach(function(je){if(je.date<monthStart||je.date>maxD)return;var amt=Math.max(je.debit||0,je.credit||0);if(amt<100)return;var k=je.date+"|"+je.account+"|"+amt.toFixed(0);if(seen[k]===undefined){seen[k]={n:0,acct:je.account,date:je.date,amt:amt};dupOrd.push(k);}seen[k].n++;});',
' dupOrd.forEach(function(k){var d2=seen[k];if(d2.n>1)ANOMALY_RESULTS.push({severity:"amber",title:"Possible duplicate - "+fmt(d2.amt)+" \\u00d7 "+d2.n,text:esc(d2.acct)+" \\u00b7 "+d2.date+" \\u00b7 same date and amount appears "+d2.n+" times."});});',
' var cashByMonth=[];',
' for(var i=2;i>=0;i--){var pMo=mo-i,pYr=yr;while(pMo<1){pMo+=12;pYr--;}var pEnd=pYr+"-"+pad(pMo)+"-"+ld(pMo,pYr);if(pEnd>maxD)pEnd=maxD;try{var pBS=computeBS(DATA.jeRows,DATA.coa,pEnd,fyM);var pCash=0;Object.keys(pBS.types.Asset.sections).forEach(function(s){Object.keys(pBS.types.Asset.sections[s].noteGroups).forEach(function(ng){pBS.types.Asset.sections[s].noteGroups[ng].accounts.forEach(function(a){if(/cash/i.test(a.title))pCash+=a.amount;});});});cashByMonth.push(pCash);}catch(e){}}',
' if(cashByMonth.length===3&&cashByMonth[2]<cashByMonth[1]&&cashByMonth[1]<cashByMonth[0]){ANOMALY_RESULTS.push({severity:"amber",title:"Cash declining 3 months straight",text:""+fmt(cashByMonth[0])+" → "+fmt(cashByMonth[1])+" → "+fmt(cashByMonth[2])+". Watch your runway."});}',
' }catch(e){}',
'}',
'',
'/* ─── BOOKS HEALTH CHECK (pre-flight: unbalanced rows, missing/dup Entry ID, GJ group imbalance) ─── */',
'function jeDrill_(date,ref,entryId){var q=String.fromCharCode(39);return "showJEDetail("+q+String(date||"")+q+","+q+String(ref||"")+q+","+q+String(entryId||"")+q+")";}',
'function runBooksHealthCheck_(){',
' try{',
' var rows=DATA.jeRows||[];',
' var grp={},ord=[];',
' for(var i=0;i<rows.length;i++){var je=rows[i];var src=je.source||"";',
' if(src==="GJ")continue;',
' if(!je.sourceRow)continue;',
' var k=src+"|"+je.sourceRow;',
' if(!grp[k]){grp[k]={src:src,row:je.sourceRow,dr:0,cr:0,entryId:je.entryId||"",date:je.date,ref:je.ref||""};ord.push(k);}',
' grp[k].dr+=je.debit||0;grp[k].cr+=je.credit||0;',
' if(!grp[k].entryId&&je.entryId)grp[k].entryId=je.entryId;',
' }',
' var idseen={};',
' for(var o=0;o<ord.length;o++){var g=grp[ord[o]];',
' if(Math.abs(Math.round((g.dr-g.cr)*100)/100)>0.01)ANOMALY_RESULTS.push({cat:"entries",severity:"red",title:g.src+" row "+g.row+": entry not balanced",text:"DR "+fmt(g.dr)+" vs CR "+fmt(g.cr)+" (off "+fmt(Math.abs(g.dr-g.cr))+")"+(g.entryId?" \\u00b7 "+esc(g.entryId):"")+(g.ref?" \\u00b7 "+esc(g.ref):""),onclick:jeDrill_(g.date,g.ref,g.entryId)});',
' var id=String(g.entryId||"").trim();',
' if(!id)ANOMALY_RESULTS.push({cat:"entries",severity:"red",title:g.src+" row "+g.row+": missing Entry ID",text:"Needs a reference ID for tracing."+(g.ref?" Ref "+esc(g.ref):"")+" \\u00b7 "+g.date,onclick:jeDrill_(g.date,g.ref)});',
' else{var dk=g.src+"|"+id;if(idseen[dk]&&idseen[dk]!==g.row)ANOMALY_RESULTS.push({cat:"entries",severity:"amber",title:g.src+": duplicate Entry ID "+esc(id),text:"Found in rows "+idseen[dk]+" and "+g.row+" \\u00b7 must be unique per entry.",onclick:jeDrill_(g.date,g.ref,id)});else idseen[dk]=g.row;}',
' }',
' var gj={},go=[];',
' for(var j=0;j<rows.length;j++){var r2=rows[j];if((r2.source||"")!=="GJ")continue;',
' var gid=String(r2.entryId||"").trim();',
' if(!gid){ANOMALY_RESULTS.push({cat:"entries",severity:"red",title:"GJ: journal line missing Entry ID",text:esc(r2.account)+(r2.ref?" \\u00b7 "+esc(r2.ref):"")+" \\u00b7 "+r2.date,onclick:jeDrill_(r2.date,r2.ref)});continue;}',
' if(!gj[gid]){gj[gid]={dr:0,cr:0,date:r2.date,ref:r2.ref||""};go.push(gid);}',
' gj[gid].dr+=r2.debit||0;gj[gid].cr+=r2.credit||0;',
' }',
' for(var k2=0;k2<go.length;k2++){var gg=gj[go[k2]];if(Math.abs(Math.round((gg.dr-gg.cr)*100)/100)>0.01)ANOMALY_RESULTS.push({cat:"entries",severity:"red",title:"GJ entry "+esc(go[k2])+": not balanced",text:"DR "+fmt(gg.dr)+" vs CR "+fmt(gg.cr)+" (off "+fmt(Math.abs(gg.dr-gg.cr))+") \\u00b7 check the debit and credit lines.",onclick:jeDrill_(gg.date,gg.ref,go[k2])});}',
' var rt=String((DATA.settings&&(DATA.settings.regType||DATA.settings.registrationType))||"").toUpperCase();',
' var isVat=rt.indexOf("VAT")>=0&&rt.indexOf("NON")<0;',
' var coaType={},coaSet={};',
' for(var c3=0;c3<(DATA.coa||[]).length;c3++){var ct=String(DATA.coa[c3].title||"").trim();coaSet[ct]=1;coaType[ct]=String(DATA.coa[c3].type||"").trim();}',
' if(isVat){',
' var revTot=0;',
' for(var ri=0;ri<rows.length;ri++){var rr2=rows[ri];if(coaType[String(rr2.account||"").trim()]==="Revenue")revTot+=(rr2.credit||0)-(rr2.debit||0);}',
' var sv=(DATA.salesVat&&DATA.salesVat.vatSummary)||{};',
' var sAll=(sv.totalVatableSales||0)+(sv.totalOutputVAT||0)+(sv.totalZeroRatedSales||0)+(sv.totalExemptSales||0);',
' if(revTot>0&&sAll===0)ANOMALY_RESULTS.push({cat:"setup",severity:"red",title:"VAT setup missing: no VAT-tagged sales columns",text:"You are VAT Registered but no sales column is tagged (row 5: VATable / Exempt / Zero-Rated / Output Tax). 2550Q and SLS will be empty. See the VAT Setup Guide."});',
' if((sv.totalVatableSales||0)>0&&(sv.totalOutputVAT||0)===0)ANOMALY_RESULTS.push({cat:"setup",severity:"red",title:"VATable sales found but Output VAT is zero",text:"Your sales are tagged VATable but no Output VAT was recorded. Check that you have an Output VAT column tagged Output Tax (row 5) and that the 12% VAT is encoded separately from the net sales. See the VAT Setup Guide."});',
' var pv=(DATA.purchaseVat&&DATA.purchaseVat.vatSummary)||{};',
' var pAll=(pv.totalInputTax||0)+(pv.totalTaxableNet||0)+(pv.totalExempt||0)+(pv.totalZeroRated||0);',
' if(revTot>0&&pAll===0)ANOMALY_RESULTS.push({cat:"setup",severity:"amber",title:"VAT setup: no VAT-tagged purchase columns",text:"No purchase column is tagged (row 5: Input Tax / VATable Goods / VATable Services / VATable Capital Goods). Input VAT will not appear in 2550Q or SLP."});',
' var _svS=(sv.totalVatableSales||0),_svO=(sv.totalOutputVAT||0);',
' if(_svO>0&&Math.abs(_svS*0.12-_svO)>1){var _sBad=((DATA.salesVat&&DATA.salesVat.ssjRows)||[]).filter(function(r){return (r.outputVAT||0)>0&&(r.vatable||0)===0;});ANOMALY_RESULTS.push({cat:"setup",severity:"red",title:"VATable Sales do not tie to Output Tax (31A \\u00d7 12% \\u2260 31B)",text:"Off by "+fmt(Math.abs(_svS*0.12-_svO))+". A sale has Output VAT but its sales/base column is not tagged VATable (row 5) - the base is dropped from 2550Q line 31A while the VAT stays in 31B."+(_sBad.length?" Check: "+esc(_sBad.slice(0,5).map(function(r){return r.ref||r.date||"?";}).join(", "))+".":"")+" Tag the base column to match.",onclick:(_sBad[0]?jeDrill_(_sBad[0].date,_sBad[0].ref):null)});}',
' var _pvN=(pv.totalTaxableNet||0),_pvI=(pv.totalInputTax||0);',
' if(_pvI>0&&Math.abs(_pvN*0.12-_pvI)>1){var _pBad=((DATA.purchaseVat&&DATA.purchaseVat.spjRows)||[]).filter(function(r){return (r.inputTax||0)>0&&(r.taxableNet||0)===0;});ANOMALY_RESULTS.push({cat:"setup",severity:"red",title:"Input Tax does not tie to VATable Purchases (44A \\u00d7 12% \\u2260 44B)",text:"Off by "+fmt(Math.abs(_pvN*0.12-_pvI))+". A purchase has Input VAT but its base/expense column is not tagged (row 5: VATable Goods / Capital / Services) - the base is dropped from 2550Q line 44A while the VAT stays in 44B."+(_pBad.length?" Check: "+esc(_pBad.slice(0,5).map(function(r){return r.ref||r.date||"?";}).join(", "))+".":"")+" Tag the base column to match.",onclick:(_pBad[0]?jeDrill_(_pBad[0].date,_pBad[0].ref):null)});}',
' }',
' var unk={},uord=[];',
' for(var u=0;u<rows.length;u++){var jr=rows[u];var an=String(jr.account||"").trim();if(!an||coaSet[an])continue;if(!unk[an]){unk[an]={n:0,amt:0,date:jr.date,ref:jr.ref||"",src:jr.source||""};uord.push(an);}unk[an].n++;unk[an].amt+=Math.max(jr.debit||0,jr.credit||0);}',
' for(var k3=0;k3<uord.length;k3++){var ua=unk[uord[k3]];ANOMALY_RESULTS.push({cat:"setup",severity:"red",title:"Account not in COA: "+esc(uord[k3]),text:(ua.src||"")+" \\u00b7 "+ua.n+" line(s) \\u00b7 "+fmt(ua.amt)+" \\u00b7 these amounts are EXCLUDED from reports. Add this account to the Chart of Accounts or rename the book column header to match an existing account.",onclick:jeDrill_(ua.date,ua.ref)});}',
' /* Abnormal balance check (2026-06-05): account net vs COA normal balance */',
' var abMaxD="";for(var ab0=0;ab0<rows.length;ab0++){var abd=rows[ab0].date||"";if(abd>abMaxD)abMaxD=abd;}',
' var abNet={},abOrd=[];',
' for(var ab1=0;ab1<rows.length;ab1++){var abr=rows[ab1];var abA=String(abr.account||"").trim();if(!abA||!coaSet[abA])continue;if(!(abA in abNet)){abNet[abA]=0;abOrd.push(abA);}abNet[abA]+=(abr.debit||0)-(abr.credit||0);}',
' var abNorm={};for(var ab2=0;ab2<DATA.coa.length;ab2++){var abc=DATA.coa[ab2];if(abc.active&&String(abc.active).toUpperCase()==="NO")continue;abNorm[String(abc.title).trim()]=String(abc.normal||"").trim();}',
' for(var ab3=0;ab3<abOrd.length;ab3++){var abT=abOrd[ab3],abV=Math.round(abNet[abT]*100)/100,abN=abNorm[abT];if(!abN||abV===0)continue;',
' var abBad=(abN==="Debit"&&abV<-0.005)||(abN==="Credit"&&abV>0.005);',
' if(abBad){var abQ=String.fromCharCode(39);ANOMALY_RESULTS.push({cat:"tb",severity:"amber",title:"Abnormal balance: "+esc(abT),text:"This account normally has a "+abN+" balance but shows "+fmt(Math.abs(abV))+" on the "+(abV>0?"Debit":"Credit")+" side. Check for a missing beginning balance, a wrong-side entry, or a leftover test entry.",onclick:"drillFromChatToGL("+abQ+escJS(abT)+abQ+","+abQ+"1900-01-01"+abQ+","+abQ+(abMaxD||"2099-12-31")+abQ+")"});}}',
' /* Corp/Individual equity setup rules (2026-06-05, D8) */',
' var hcIsCorp=String((DATA.settings&&DATA.settings.taxpayerClass)||"").toUpperCase().indexOf("NON")===0;',
' var hcActive={};for(var hc1=0;hc1<DATA.coa.length;hc1++){var hca=DATA.coa[hc1];if(hca.active&&String(hca.active).toUpperCase()==="NO")continue;hcActive[String(hca.title).trim()]=1;}',
' var hcQ=String.fromCharCode(39);var hcOC="Owner"+hcQ+"s Capital",hcOW="Owner"+hcQ+"s Withdrawals";',
' if(hcIsCorp){',
' if(!hcActive["Retained Earnings"])ANOMALY_RESULTS.push({cat:"setup",severity:"red",title:"Retained Earnings account missing or inactive",text:"You are set as Non-Individual (corporation/partnership). The equity rollup needs an active account titled Retained Earnings in the Chart of Accounts. Set it to Active = YES."});',
' if(hcActive[hcOC]||hcActive[hcOW])ANOMALY_RESULTS.push({cat:"setup",severity:"amber",title:"Owner accounts are still active",text:"You are set as Non-Individual but "+hcOC+" or "+hcOW+" is still Active in the Chart of Accounts. Set them to NO and use Share Capital and Retained Earnings instead."});',
' }else{',
' if(hcActive["Share Capital"]||hcActive["Retained Earnings"])ANOMALY_RESULTS.push({cat:"setup",severity:"amber",title:"Corporate equity accounts are active",text:"You are set as Individual but Share Capital or Retained Earnings is Active in the Chart of Accounts. Set them to NO and use "+hcOC+" and "+hcOW+" instead."});',
' }',
' /* Books Start Date suggestion (2026-06-05): composite opening-balance entry detected but C87 is blank */',
' if(!cfBsd_()){',
' var obFirst="";for(var ob1=0;ob1<rows.length;ob1++){var obD=rows[ob1].date||"";if(obD&&(!obFirst||obD<obFirst))obFirst=obD;}',
' if(obFirst){var obGroups={};',
' for(var ob2=0;ob2<rows.length;ob2++){var obr=rows[ob2];if((obr.date||"")!==obFirst||obr.source!=="GJ")continue;var obK=obr.entryId||obr.ref||"?";if(!obGroups[obK])obGroups[obK]=0;obGroups[obK]++;}',
' for(var obK2 in obGroups){if(obGroups[obK2]>=8){ANOMALY_RESULTS.push({cat:"setup",severity:"amber",title:"Possible opening-balance entry - set your Books Start Date",text:"Entry "+esc(obK2)+" on "+esc(obFirst)+" looks like a beginning-balance setup entry ("+obGroups[obK2]+" lines on your earliest date). Set the Books Start Date in Settings (cell C87) to "+esc(obFirst)+" so the Cash Flow report treats it as opening balances, not cash activity."});break;}}',
' }',
' }',
' /* Books Start Date misconfig guardrail (2026-06-08, AZ PACIFIC): C87 set too late - real CRB/CDB/SJ/PJ activity dated on/before C87 is wrongly excluded from Cash Flow. Fresh startups should leave C87 BLANK. */',
' else {',
' var bsdSet=cfBsd_();var exclReal=0,exclDates={},exclSrc={},exclFirst="",exclLast="";',
' for(var bx=0;bx<rows.length;bx++){var bxr=rows[bx];var bxd=bxr.date||"";if(!bxd||bxd>bsdSet)continue;var bxs=bxr.source||"";if(bxs==="CRB"||bxs==="CDB"||bxs==="SJ"||bxs==="PJ"){exclReal++;exclDates[bxd]=1;exclSrc[bxs]=1;if(!exclFirst||bxd<exclFirst)exclFirst=bxd;if(bxd>exclLast)exclLast=bxd;}}',
' if(exclReal>0){var ndays=Object.keys(exclDates).length;var srcList=Object.keys(exclSrc).join(", ");ANOMALY_RESULTS.push({cat:"setup",severity:"amber",title:"Books Start Date may be set too late",text:exclReal+" cash/sales/purchase transaction(s) ("+srcList+") dated on or before "+esc(bsdSet)+" are being EXCLUDED from the Cash Flow (dates "+esc(exclFirst)+(exclLast!==exclFirst?" to "+esc(exclLast):"")+", "+ndays+" day(s)). If this is a NEW business with no migration, the Books Start Date (Settings C87) should be BLANK. If migrating from old books, set it to the day BEFORE your first real transaction - not a date that already has real activity (like an incorporation date with the capital deposit and first expenses)."});}',
' }',
' }catch(e){}',
'}',
'',
'/* ─── HELP SYSTEM (Sprint 4) ─── */',
'function tryHelp(q){',
' var t=q.toLowerCase().trim();',
' if(!/^(?:help|tulong|\\?|what\\s+can\\s+i\\s+(?:ask|do)|paano\\s+gamitin)$/.test(t))return null;',
' var paragraphs=[',
' "<b>Pwede mo akong tanungin tungkol sa:</b>",',
' "• <b>Reports</b> - “trial balance”, “income statement jan 2026”, “balance sheet”, “cash flows q1”",',
' "• <b>Status</b> - “kumusta business ko?”, “health check”",',
' "• <b>Search</b> - “find meralco”, “hanapin rent over 5000”",',
' "• <b>Aggregation</b> - “magkano total expense”, “top 5 expenses”, “average monthly sales”",',
' "• <b>Variance</b> - “bakit mataas yung expenses?”, “why is sales down?”",',
' "• <b>Compare</b> - “q1 2026 vs q1 2025”, “jan vs feb”",',
' "• <b>Definitions</b> - “ano yung gross profit?”, “what is net income?”, “define cogs”"',
' ];',
' return renderProseCard({icon:"❓",title:"Help - Anong pwede mong tanungin",paragraphs:paragraphs,note:"Type your question sa chatbar at i-press Enter."});',
'}',
'',
'/* ─── SUGGESTION CHIP ENGINE (Sprint 4) ─── */',
'function getSuggestionsForIntent(intentType){',
' var map={',
' "search":["Open in GL","Trend last 6 months","Top 5 by amount"],',
' "aggregation":["Compare to last month","By month","Top categories"],',
' "status":["Show concerns","Top 5 expenses","Compare YoY"],',
' "variance":["Show transactions","Compare to LY","Top drivers"],',
' "compare":["Show variance","Trend monthly","Drill into account"],',
' "education":["Show me an example","Related concepts","Compute for me"],',
' "report":["Compare period","Export Excel","Drill into account"],',
' "welcome":["Trial balance","Income statement","Balance sheet","Top 5 expenses","How is my business?","Help"],',
' "help":["Trial balance","How is my business?","Top 5 expenses","Find meralco","Income statement"]',
' };',
' return map[intentType]||map["welcome"];',
'}',
'',
'/* ─── MULTI-TURN CONTEXT (Sprint 4) ─── */',
'var CONV_CONTEXT={lastQuery:"",lastIntent:"",lastResult:null,lastEntities:{},history:[]};',
'function storeContext(query,intent,result,entities){CONV_CONTEXT.lastQuery=query;CONV_CONTEXT.lastIntent=intent;CONV_CONTEXT.lastResult=result;CONV_CONTEXT.lastEntities=entities||{};CONV_CONTEXT.history.unshift({query:query,intent:intent,ts:Date.now()});if(CONV_CONTEXT.history.length>3)CONV_CONTEXT.history.pop();}',
'function tryContextRefinement(q){',
' var t=q.toLowerCase().trim();',
' if(!CONV_CONTEXT.lastIntent)return null;',
' if(/^(?:show\\s+more|more|details?|show\\s+transactions)$/.test(t)&&CONV_CONTEXT.lastIntent==="search"){return null;}',
' if(/^(?:by\\s+month|monthly|breakdown)$/.test(t)&&CONV_CONTEXT.lastIntent==="aggregation"){return null;}',
' if(/^(?:compare\\s+(?:to\\s+)?last\\s+year|vs\\s+last\\s+year|yoy)$/.test(t)){return null;}',
' return null;',
'}',
'',
'/* ─── WELCOME CARD V2 (Sprint 4) ─── */',
'function renderWelcomeCardV2(){',
' var hr=new Date().getHours();',
' var greet=(hr<12?"Good morning":(hr<18?"Good afternoon":"Good evening"));',
' var nm=(DATA&&DATA.settings&&DATA.settings.businessName)?DATA.settings.businessName:"Ed";',
' var dt=new Date();',
' var dayN=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][dt.getDay()];',
' var monN=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][dt.getMonth()];',
' var dtStr=dayN+", "+monN+" "+dt.getDate()+", "+dt.getFullYear();',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var dates=DATA.jeRows.map(function(e){return e.date}).filter(Boolean).sort();',
' var hasData=dates.length>0;',
' var h="<div class=\\"welcome-card card\\">";',
' h+="<div class=\\"welcome-greeting\\">"+greet+", "+esc(nm)+"! 👋</div>";',
' h+="<div class=\\"welcome-sub\\">"+esc(nm)+" \\u00B7 "+dtStr+"</div>";',
' h+=renderBirDeadlines_();',
' if(hasData){',
' if(ANOMALY_RESULTS&&ANOMALY_RESULTS.length>0){',
' h+="<div style=\\"margin:8px 0\\">";',
' var hcCats=concernCats_();',
' for(var ci=0;ci<hcCats.length;ci++){',
' var hcList=ANOMALY_RESULTS.filter(function(a){return (a.cat||"anomaly")===hcCats[ci].id;});',
' if(!hcList.length)continue;',
' var hcFirst=hcList[0];',
' h+=renderAlertCard({severity:hcFirst.severity,title:hcFirst.title,text:hcFirst.text,onclick:hcFirst.onclick});',
' if(hcList.length>1)h+="<div style=\\"margin:2px 0 10px\\"><button class=\\"action-btn\\" style=\\"font-size:11px;padding:4px 12px\\" onclick=\\"showConcernsCat("+ci+")\\">Show all "+hcList.length+" "+hcCats[ci].btnLabel+"</button></div>";',
' }',
' h+="<div style=\\"margin-top:10px\\"><button class=\\"action-btn action-btn-primary\\" style=\\"font-size:12px;padding:6px 14px\\" onclick=\\"showAllConcerns()\\">Show all concerns ("+ANOMALY_RESULTS.length+")</button></div>";',
' h+="</div>";',
' }',
' }',
' h+="<div style=\\"font-size:11px;color:#6B7280;font-weight:600;text-transform:uppercase;letter-spacing:.5px;margin:12px 0 8px\\">Try asking:</div>";',
' h+=renderSuggestionChips([',
' {label:"How is my business?",onclick:"document.getElementById(\'chatInput\').value=\'how is my business\';executeChat();"},',
' {label:"Top 5 expenses",onclick:"document.getElementById(\'chatInput\').value=\'top 5 expenses\';executeChat();"},',
' {label:"Trial balance",onclick:"document.getElementById(\'chatInput\').value=\'trial balance\';executeChat();"},',
' {label:"Income statement",onclick:"document.getElementById(\'chatInput\').value=\'income statement\';executeChat();"},',
' {label:"Show concerns",onclick:"showAllConcerns();"},',
' {label:"Help",onclick:"document.getElementById(\'chatInput\').value=\'help\';executeChat();"}',
' ]);',
' h+="</div>";',
' pushMessage("system",h);',
'}',
'function birMonthName_(m){return ["January","February","March","April","May","June","July","August","September","October","November","December"][m-1]||"";}',
'/* combined display label (form + attachment names) for glance views (welcome card, full-year) */',
'function birFormLabel_(it){var f=it.form;if(it.atts&&it.atts.length){var n=[];for(var i=0;i<it.atts.length;i++)n.push(it.atts[i].name);f+=" + "+n.join(" + ");}return f;}',
'/* BIR deadlines for a given month (v1 informational, gated by VAT/Non-VAT + Individual/Non-Individual). Rules from BIR / Taxumo / JuanTax; verify exact date - holiday/weekend shifts to next working day. */',
'function birDeadlinesForMonth_(y,m,isVAT,isIndiv){',
' var lastDay=new Date(y,m,0).getDate();var out=[];var qd={1:1,4:1,7:1,10:1};',
' function add(day,form,desc,cond,atts){out.push({day:day,form:form,desc:desc,cond:cond||"",atts:atts||null});}',
' add(m===1?15:10,"1601-C","Withholding tax on compensation (prev. month)","if employer");',
' if(!qd[m])add(10,"0619-E","Monthly expanded withholding (prev. month)","if withholding agent");',
' if(qd[m]){',
' if(isVAT)add(25,"2550Q","Quarterly VAT return","",[{key:"SLSP",name:"SLSP",note:"Summary list of sales & purchases (eSubmission/DAT)"}]);else add(25,"2551Q","Quarterly percentage tax (3%)","if not on 8% income tax");',
' add(lastDay,"1601-EQ","Quarterly expanded withholding","if withholding agent",[{key:"QAP",name:"QAP",note:"Quarterly alphalist of payees (eSubmission)"}]);',
' }',
' if(isIndiv){',
' if(m===5)add(15,"1701Q (Q1)","Quarterly income tax Q1","",[{key:"SAWT",name:"SAWT",note:"if may 2307 (eSubmission)"}]);',
' if(m===8)add(15,"1701Q (Q2)","Quarterly income tax Q2","",[{key:"SAWT",name:"SAWT",note:"if may 2307 (eSubmission)"}]);',
' if(m===11)add(15,"1701Q (Q3)","Quarterly income tax Q3","",[{key:"SAWT",name:"SAWT",note:"if may 2307 (eSubmission)"}]);',
' if(m===4)add(15,"1701 / 1701A","Annual income tax return (prev. year)","",[{key:"SAWT",name:"SAWT",note:"if may 2307"}]);',
' }else{',
' if(m===5)add(30,"1702Q (Q1)","Quarterly income tax corp Q1","");',
' if(m===8)add(29,"1702Q (Q2)","Quarterly income tax corp Q2","");',
' if(m===11)add(29,"1702Q (Q3)","Quarterly income tax corp Q3","");',
' if(m===4)add(15,"1702-RT","Annual income tax corp (prev. year)","");',
' }',
' if(m===1){',
' add(15,"Loose-leaf books","Bind + register prior-year books","PTU loose-leaf only");',
' add(30,"Inventory List","Annual inventory list (prev. year)","if maintaining inventory");',
' add(31,"1604-C","Annual alphalist - compensation","if employer");',
' add(31,"2316 (issue)","Give certificates to employees","if employer");',
' }',
' if(m===2)add(28,"2316 (submit)","Submit to BIR - substituted filing","if employer");',
' if(m===3)add(1,"1604-E","Annual alphalist - expanded withholding","if withholding agent");',
' if(m===4)add(30,"eAFS","ITR/AFS attachments (15 days after ITR)","if required to attach AFS");',
' if(m===1||m===7)add(31,"Lessee Information Statement (LIS)","Semi-annual list of tenants/lessees - RR 12-2011","if commercial lessor");',
' out.sort(function(a,b){return a.day-b.day;});return out;',
'}',
'function renderBirDeadlines_(){',
' if(typeof DATA==="undefined"||!DATA.settings)return "";',
' var s=DATA.settings;var rt=String(s.regType||"").toLowerCase();var isVAT=rt.indexOf("non")<0&&rt.indexOf("vat")>=0;',
' var tc=String(s.taxpayerClass||"").toLowerCase();var isIndiv=tc.indexOf("non")<0;',
' var now=new Date();var Y=now.getFullYear(),M=now.getMonth()+1,D=now.getDate();',
' var items=birDeadlinesForMonth_(Y,M,isVAT,isIndiv).filter(function(it){return !it.cond||birCondOk_(it.cond);});',
' var h="<div class=\\"dl-head\\">\\uD83D\\uDCC5 BIR deadlines \\u00B7 "+birMonthName_(M)+" "+Y+"</div><div class=\\"dl-list\\">";',
' if(!items.length)h+="<div class=\\"dl-empty\\">Walang regular na BIR deadline ngayong buwan.</div>";',
' for(var i=0;i<items.length;i++){var it=items[i];var passed=it.day<D;',
' h+="<div class=\\"dl-row"+(passed?" dl-passed":"")+"\\"><span class=\\"dl-date\\">"+birMonthName_(M).substring(0,3)+" "+it.day+"</span><span class=\\"dl-info\\"><span class=\\"dl-form\\">"+esc(birFormLabel_(it))+"</span>"+(it.cond?" <span class=\\"dl-cond\\">"+esc(it.cond)+"</span>":"")+"<span class=\\"dl-desc\\">"+esc(it.desc)+"</span></span>"+(passed?"<span class=\\"dl-tag\\">passed</span>":"")+"</div>";}',
' h+="</div>";',
' var nx=null;for(var mo=0;mo<7&&!nx;mo++){var yy=Y+Math.floor((M-1+mo)/12),mm=((M-1+mo)%12)+1;var lst=birDeadlinesForMonth_(yy,mm,isVAT,isIndiv).filter(function(it){return !it.cond||birCondOk_(it.cond);});for(var k=0;k<lst.length;k++){var dd=new Date(yy,mm-1,lst[k].day);if(dd>now){nx={it:lst[k],y:yy,m:mm};break;}}}',
' if(nx)h+="<div class=\\"dl-next\\">Next: <b>"+esc(birFormLabel_(nx.it))+"</b> on "+birMonthName_(nx.m).substring(0,3)+" "+nx.it.day+", "+nx.y+"</div>";',
' h+="<div class=\\"dl-note\\">Informational lang \\u00B7 holiday/weekend \\u2192 next working day \\u00B7 i-verify sa BIR.</div>";',
' h+="<div style=\\"margin-top:9px\\"><button class=\\"action-btn\\" style=\\"font-size:11px;padding:4px 12px\\" onclick=\\"switchTab(\'taxcal\')\\">Open Tax Calendar</button></div>";',
' return "<div class=\\"dl-card\\">"+h+"</div>";',
'}',
'/* ─── Tax Calendar view (P1): conditional toggles persisted in localStorage; VAT/Indiv gating from Settings ─── */',
'var BIR_TG=null;',
'function birToggles_(){',
' if(BIR_TG)return BIR_TG;',
' var d={emp:false,wht:false,inv:false,ptu:false,lessor:false};',
' try{var raw=localStorage.getItem("ectax_bir_tg");if(raw){var o=JSON.parse(raw);d.emp=!!o.emp;d.wht=!!o.wht;d.inv=!!o.inv;d.ptu=!!o.ptu;d.lessor=!!o.lessor;}}catch(e){}',
' BIR_TG=d;return d;',
'}',
'function birSetToggle_(k,v){var t=birToggles_();t[k]=v;BIR_TG=t;try{localStorage.setItem("ectax_bir_tg",JSON.stringify(t));}catch(e){}}',
'function birCondOk_(cond){var t=birToggles_();if(cond==="if employer")return t.emp;if(cond==="if withholding agent")return t.wht;if(cond==="if maintaining inventory")return t.inv;if(cond==="PTU loose-leaf only")return t.ptu;if(cond==="if commercial lessor")return t.lessor;return true;}',
'function tcToggleChip_(k,label,forms,on){return "<button class=\\"tc-toggle"+(on?" on":"")+"\\" onclick=\\"tcToggle(\'"+k+"\')\\"><span class=\\"tc-ico\\">"+(on?"\\u2713":"\\u2715")+"</span> "+esc(label)+(forms?" <span class=\\"tc-forms\\">"+esc(forms)+"</span>":"")+"</button>";}',
'function tcToggle(k){var t=birToggles_();birSetToggle_(k,!t[k]);if(typeof ACTIVE_TAB!=="undefined"&&ACTIVE_TAB==="taxcal")buildTaxCalendar();}',
'function buildTaxCalendar(){',
' var c=document.getElementById("controls");if(c)c.innerHTML="";',
' var rep=document.getElementById("report");if(!rep)return;',
' if(typeof DATA==="undefined"||!DATA.settings){rep.innerHTML="";return;}',
' var s=DATA.settings;var rt=String(s.regType||s.registrationType||"").toLowerCase();var isVAT=rt.indexOf("non")<0&&rt.indexOf("vat")>=0;',
' var tc=String(s.taxpayerClass||"").toLowerCase();var isIndiv=tc.indexOf("non")<0;',
' var Y=new Date().getFullYear();var today=new Date();today.setHours(0,0,0,0);',
' var items=[];for(var m=1;m<=12;m++){var lst=birDeadlinesForMonth_(Y,m,isVAT,isIndiv);for(var i=0;i<lst.length;i++){var it=lst[i];if(it.cond&&!birCondOk_(it.cond))continue;items.push({date:new Date(Y,m-1,it.day),m:m,day:it.day,form:it.form,desc:it.desc,cond:it.cond,atts:it.atts||null,key:Y+"|"+it.form+"|"+m+"-"+it.day});}}',
' items.sort(function(a,b){return a.date-b.date;});',
' function dTo(d){return Math.round((d-today)/86400000);}',
' function sOf(d){var k=dTo(d);if(k<0)return"passed";if(k<=14)return"soon";return"up";}',
' var TG=birToggles_();',
' var h="<div class=\\"taxcal-wrap\\">";',
' h+="<div class=\\"taxcal-head\\"><span class=\\"taxcal-title\\">\\uD83D\\uDCC5 Tax Calendar \\u00B7 "+Y+"</span><span class=\\"taxcal-chips\\"><span class=\\"tc-chip\\">"+(isVAT?"VAT":"Non-VAT")+"</span><span class=\\"tc-chip\\">"+(isIndiv?"Individual":"Non-Individual")+"</span></span></div>";',
' h+="<div class=\\"taxcal-sub\\">I-set para tumugma sa BIR Certificate of Registration (Form 2303) mo:</div>";',
' h+="<div class=\\"tc-toggles\\">"+tcToggleChip_("emp","I have employees","1601-C \\u00B7 1604-C \\u00B7 2316",TG.emp)+tcToggleChip_("wht","Withholding agent (EWT)","0619-E \\u00B7 1601-EQ \\u00B7 1604-E",TG.wht)+tcToggleChip_("inv","I sell or produce inventory","Inventory List",TG.inv)+tcToggleChip_("ptu","I use loose-leaf books","",TG.ptu)+tcToggleChip_("lessor","I am a commercial lessor","LIS",TG.lessor)+"</div>";',
' TC_REG=[];var fil=tcFilings_();',
' function attKey2(it,a){return it.key+"|"+a.name;}',
' function allDone2(it){if(!fil[it.key])return false;if(it.atts){for(var i=0;i<it.atts.length;i++){if(!fil[attKey2(it,it.atts[i])])return false;}}return true;}',
' var overdue=items.filter(function(x){var k=dTo(x.date);return k<0&&k>=-95&&!allDone2(x);});',
' var future=items.filter(function(x){return x.date>=today;});',
' var upN=future.slice(0,4);var laterN=future.slice(4);',
' var sumTxt=(overdue.length?overdue.length+" overdue \\u00B7 ":"")+(upN.length?"next: "+birMonthName_(upN[0].m).substring(0,3)+" "+upN[0].day:"all clear");',
' h+="<div class=\\"tc-summ\\">"+esc(sumTxt)+"</div>";',
' if(overdue.length){h+="<div class=\\"tc-sec-red\\">\\u26A0 Needs attention ("+overdue.length+")</div>";for(var o=0;o<overdue.length;o++)h+=tcFormRow_(overdue[o],"overdue",Y,fil,today);}',
' h+="<div class=\\"tc-label\\">Up next</div>";',
' if(!upN.length)h+="<div class=\\"dl-empty\\">No more deadlines this year for your setup.</div>";',
' for(var u=0;u<upN.length;u++)h+=tcFormRow_(upN[u],"up",Y,fil,today);',
' if(laterN.length){h+="<div class=\\"tc-label\\">Later this year</div>";var curM=0;for(var L=0;L<laterN.length;L++){var itl=laterN[L];if(itl.m!==curM){curM=itl.m;h+="<div class=\\"tc-month\\">"+birMonthName_(curM)+"</div>";}h+=tcLaterRow_(itl,fil);}}',
' h+="<div class=\\"dl-note\\">Informational \\u00B7 mark mo kapag na-file/na-submit \\u00B7 holiday/weekend \\u2192 next working day \\u00B7 verify sa BIR.</div>";',
' h+="</div>";rep.innerHTML=h;',
'}',
'var TC_REG=[];var TC_EDIT=null;',
'function tcFilings_(){return (typeof DATA!=="undefined"&&DATA.taxFilings)?DATA.taxFilings:{};}',
'function isoToday_(){var d=new Date();function p(n){return n<10?"0"+n:""+n;}return d.getFullYear()+"-"+p(d.getMonth()+1)+"-"+p(d.getDate());}',
'function tcCtrlHTML_(i,key,rec,label,editing){',
' if(rec)return "<span class=\\"tc-filed\\">\\u2713 "+(rec.dateFiled?esc(String(rec.dateFiled).substring(0,10)):"done")+(rec.ref?" \\u00B7 "+esc(rec.ref):"")+" <button class=\\"tc-undo\\" onclick=\\"tcUnmark("+i+")\\">undo</button></span>";',
' if(editing)return "<span class=\\"tc-panel\\"><input type=\\"date\\" id=\\"tcd"+i+"\\" value=\\""+isoToday_()+"\\"><input type=\\"text\\" id=\\"tcr"+i+"\\" placeholder=\\"Ref no. (optional)\\"><button class=\\"tc-save\\" onclick=\\"tcSave("+i+")\\">Save</button><button class=\\"tc-cancel\\" onclick=\\"tcCancel()\\">Cancel</button></span>";',
' return "<button class=\\"tc-mark\\" onclick=\\"tcOpen("+i+")\\">"+esc(label)+"</button>";',
'}',
'function tcFormRow_(it,kind,Y,fil,today){',
' var dn=Math.round((it.date-today)/86400000);',
' var per=it.m+"/"+it.day;var formRec=fil[it.key]||null;var atts=it.atts||[];',
' var pend=0;for(var i=0;i<atts.length;i++){if(!fil[it.key+"|"+atts[i].name])pend++;}',
' var allDone=!!formRec&&pend===0;',
' var bc=allDone?"#1B7A43":(kind==="overdue"?"#B00020":(dn<=14?"#BA7517":"#D7DBE0"));',
' var lbl=kind==="overdue"?("overdue "+(-dn)+"d"):(dn===0?"due today":"in "+dn+"d");',
' var lblCol=kind==="overdue"?"#B00020":(dn<=14?"#BA7517":"#9CA3AF");',
' var fIdx=TC_REG.length;TC_REG.push({key:it.key,year:Y,period:per,form:it.form});',
' var h="<div class=\\"tc-row\\" style=\\"border-left-color:"+bc+"\\"><div class=\\"tc-row-top\\">";',
' h+="<div class=\\"tc-rdate\\">"+birMonthName_(it.m).substring(0,3)+" "+it.day+"<div class=\\"tc-rcd\\" style=\\"color:"+lblCol+"\\">"+(formRec?"":esc(lbl))+"</div></div>";',
' h+="<div class=\\"tc-rmain\\"><div class=\\"tc-rform\\">"+esc(it.form)+(formRec&&pend>0?" <span class=\\"tc-pend\\">"+pend+" attachment pending</span>":"")+"</div><div class=\\"tc-rdesc\\">"+esc(it.desc)+"</div></div>";',
' h+="<div class=\\"tc-ract\\">"+tcCtrlHTML_(fIdx,it.key,formRec,"Mark filed",TC_EDIT===it.key)+"</div></div>";',
' for(var a=0;a<atts.length;a++){var ak=it.key+"|"+atts[a].name;var arec=fil[ak]||null;var aIdx=TC_REG.length;TC_REG.push({key:ak,year:Y,period:per,form:atts[a].name});',
' h+="<div class=\\"tc-att\\"><span class=\\"tc-att-ico\\">\\u21B3</span><div class=\\"tc-att-main\\"><div class=\\"tc-att-name\\">"+esc(atts[a].name)+" <span class=\\"tc-att-tag\\">attachment</span></div><div class=\\"tc-rdesc\\">"+esc(atts[a].note||"")+"</div></div><div class=\\"tc-ract\\">"+tcCtrlHTML_(aIdx,ak,arec,"Mark submitted",TC_EDIT===ak)+"</div></div>";}',
' h+="</div>";return h;',
'}',
'function tcLaterRow_(it,fil){',
' var atts=it.atts||[];var pend=0;for(var i=0;i<atts.length;i++){if(!fil[it.key+"|"+atts[i].name])pend++;}',
' var formRec=fil[it.key]||null;var allDone=!!formRec&&pend===0;',
' var stt=allDone?"<span class=\\"tc-st-done\\">\\u2713 filed</span>":(formRec?"<span class=\\"tc-st-part\\">filed \\u00B7 "+pend+" pending</span>":"<span class=\\"tc-st-up\\">upcoming</span>");',
' return "<div class=\\"tc-later\\"><span class=\\"tc-rdate2\\">"+birMonthName_(it.m).substring(0,3)+" "+it.day+"</span><span class=\\"tc-lform\\">"+esc(birFormLabel_(it))+"</span>"+stt+"</div>";',
'}',
'function tcOpen(i){var r=TC_REG[i];if(r){TC_EDIT=r.key;buildTaxCalendar();}}',
'function tcCancel(){TC_EDIT=null;buildTaxCalendar();}',
'function tcSave(i){var r=TC_REG[i];if(!r)return;var dEl=document.getElementById("tcd"+i),rEl=document.getElementById("tcr"+i);var dv=(dEl&&dEl.value)||isoToday_();var rv=(rEl&&rEl.value)||"";if(typeof DATA!=="undefined"){if(!DATA.taxFilings)DATA.taxFilings={};DATA.taxFilings[r.key]={year:r.year,period:r.period,form:r.form,dateFiled:dv,ref:rv};}TC_EDIT=null;buildTaxCalendar();tcPersist_("apiMarkFiled",{key:r.key,year:r.year,period:r.period,form:r.form,dateFiled:dv,ref:rv});}',
'function tcUnmark(i){var r=TC_REG[i];if(!r)return;if(typeof DATA!=="undefined"&&DATA.taxFilings)delete DATA.taxFilings[r.key];TC_EDIT=null;buildTaxCalendar();tcPersist_("apiUnmarkFiled",r.key);}',
'function tcPersist_(fn,arg){if(typeof google==="undefined"||!google.script||!google.script.run||!DATA.spreadsheetId){refreshToast_("Saved on this view. Open via the Sheet menu link para ma-save sa workbook mo.",true);return;}google.script.run.withSuccessHandler(function(res){if(res&&res.filings){DATA.taxFilings=res.filings;if(typeof ACTIVE_TAB!=="undefined"&&ACTIVE_TAB==="taxcal")buildTaxCalendar();}else if(res&&res.__error){refreshToast_(res.__error,true);}}).withFailureHandler(function(e){refreshToast_("Save failed: "+((e&&e.message)||e),true);})[fn](DATA.spreadsheetId,arg);}',
'function showBirFullYear(){',
' if(typeof DATA==="undefined"||!DATA.settings)return;',
' var s=DATA.settings;var rt=String(s.regType||"").toLowerCase();var isVAT=rt.indexOf("non")<0&&rt.indexOf("vat")>=0;',
' var tc=String(s.taxpayerClass||"").toLowerCase();var isIndiv=tc.indexOf("non")<0;',
' var Y=new Date().getFullYear();',
' var html="<div class=\\"dl-head\\">\\uD83D\\uDCC5 Full-Year BIR Calendar \\u00B7 "+Y+"</div>";',
' for(var m=1;m<=12;m++){var lst=birDeadlinesForMonth_(Y,m,isVAT,isIndiv);if(!lst.length)continue;',
' html+="<div style=\\"font-weight:700;color:#1F3864;margin:9px 0 3px;font-size:12px\\">"+birMonthName_(m)+"</div><div class=\\"dl-list\\">";',
' for(var i=0;i<lst.length;i++){var it=lst[i];html+="<div class=\\"dl-row\\"><span class=\\"dl-date\\">"+birMonthName_(m).substring(0,3)+" "+it.day+"</span><span class=\\"dl-info\\"><span class=\\"dl-form\\">"+esc(birFormLabel_(it))+"</span>"+(it.cond?" <span class=\\"dl-cond\\">"+esc(it.cond)+"</span>":"")+"<span class=\\"dl-desc\\">"+esc(it.desc)+"</span></span></div>";}',
' html+="</div>";}',
' html+="<div class=\\"dl-note\\">Base sa Settings mo (VAT/Non-VAT \\u00B7 Individual/Non-Individual). Conditional items tagged. Holiday/weekend \\u2192 next working day. Verify sa BIR.</div>";',
' pushMessage("user","show full-year BIR calendar");',
' pushMessage("system","<div class=\\"dl-card\\">"+html+"</div>");',
'}',
'function concernCats_(){return [{id:"setup",label:"Setup issues",btnLabel:"setup issues"},{id:"entries",label:"Entry issues",btnLabel:"entry issues"},{id:"tb",label:"Trial Balance issues",btnLabel:"trial balance issues"},{id:"anomaly",label:"Unusual activity",btnLabel:"unusual activity"}];}',
'function showConcernsCat(ci){',
' var cat=concernCats_()[ci];if(!cat)return;',
' var list=(ANOMALY_RESULTS||[]).filter(function(a){return (a.cat||"anomaly")===cat.id;});',
' if(!list.length)return;',
' pushMessage("user","show "+cat.btnLabel);',
' var html="<div style=\\"font-size:12px;font-weight:700;color:#1F3864;margin-bottom:6px\\">"+esc(cat.label)+" ("+list.length+")</div>";',
' for(var i=0;i<list.length;i++){html+=renderAlertCard({severity:list[i].severity,title:list[i].title,text:list[i].text,onclick:list[i].onclick});}',
' pushMessage("system",html);',
'}',
'function showAllConcerns(){',
' if(!ANOMALY_RESULTS||ANOMALY_RESULTS.length===0){pushMessage("system",renderAlertCard({severity:"green",title:"No concerns","text":"No flagged issues in your books. Keep it up!"}));return;}',
' pushMessage("user","show concerns");',
' var html="";var sacCats=concernCats_();',
' for(var c=0;c<sacCats.length;c++){var sacList=ANOMALY_RESULTS.filter(function(a){return (a.cat||"anomaly")===sacCats[c].id;});if(!sacList.length)continue;',
' html+="<div style=\\"font-size:12px;font-weight:700;color:#1F3864;margin:10px 0 6px\\">"+esc(sacCats[c].label)+" ("+sacList.length+")</div>";',
' for(var i=0;i<sacList.length;i++){var al=sacList[i];html+=renderAlertCard({severity:al.severity,title:al.title,text:al.text,onclick:al.onclick});}',
' }',
' pushMessage("system",html);',
'}',
'',
'/* ─── INTENT ROUTER (Sprint 4) ─── */',
'',
'/* ─── ACTION ENGINE (Sprint 4 add-on) - context-aware expand/collapse ─── */',
'function tryAction(q){',
' var t=q.toLowerCase().trim();',
' var hasExpand=/\\b(expand|buksan|open|i-expand|i-buksan|palakihin|show\\s+all|ipakita\\s+lahat)\\b/.test(t);',
' var hasCollapse=/\\b(collapse|isara|close|i-collapse|i-isara|tago\\s+lahat|hide\\s+all)\\b/.test(t);',
' if(!hasExpand&&!hasCollapse)return null;',
' if(hasExpand&&hasCollapse)return null;',
' var targetTab=null;',
' if(/\\b(income\\s+statement|\\bis\\b)/.test(t))targetTab="is";',
' else if(/\\b(balance\\s+sheet|\\bbs\\b)/.test(t))targetTab="bs";',
' else if(/\\b(trial\\s+balance|\\btb\\b)/.test(t))targetTab="tb";',
' else targetTab=ACTIVE_TAB||"";',
' if(!targetTab){return renderAlertCard({severity:"amber",title:"Walang report na bukas",text:"Type \\"income statement\\", \\"balance sheet\\", or \\"trial balance\\" muna, tapos sabihin mong \\"expand\\" o \\"collapse\\"."});}',
' var needsSwitch=ACTIVE_TAB!==targetTab;',
' if(needsSwitch)switchTab(targetTab);',
' var doAction=function(){',
' if(targetTab==="is"){',
' if(hasExpand)IS_STATE.collapsedSections={};',
' else IS_STATE.collapsedSections={Revenue:true,"Cost of Sales":true,"Other Income":true,"Operating Expenses":true,"Other Expenses":true,"Income Tax":true};',
' if(typeof recomputeIS==="function")recomputeIS();',
' }else if(targetTab==="bs"){',
' if(hasExpand){BS_STATE.collapsedTypes={};BS_STATE.collapsedFSGroups={};}',
' else{BS_STATE.collapsedTypes={};BS_TYPES.forEach(function(t){BS_STATE.collapsedTypes[t]=true;});BS_STATE.collapsedFSGroups={};for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;var tp=String(a.type).trim();if(tp!=="Asset"&&tp!=="Liability")continue;var fsg=String(a.fsGroup||"").trim();if(!fsg)fsg=tp;BS_STATE.collapsedFSGroups[tp+"|"+fsg]=true;}}',
' if(typeof recomputeBS==="function")recomputeBS();',
' }else if(targetTab==="tb"){',
' var keys=["Asset","Liability","Equity","Income","Expense","NetIncome"];',
' for(var k=0;k<keys.length;k++)TB_STATE.collapsed[keys[k]]=!hasExpand;',
' if(typeof recomputeTB==="function")recomputeTB();',
' }',
' };',
' setTimeout(doAction,needsSwitch?180:0);',
' var label=targetTab==="is"?"Income Statement":targetTab==="bs"?"Balance Sheet":targetTab==="tb"?"Trial Balance":targetTab.toUpperCase();',
' var verb=hasExpand?"Expanded":"Collapsed";',
' return renderAlertCard({severity:"green",title:"\\u2713 "+verb+" "+label,text:"Sections "+(hasExpand?"opened":"closed")+"."});',
'}',
'',
'function routeIntentV4(qEN,qOriginal){',
' function tryFn(fn){var r=fn(qOriginal);if(!r&&qEN!==qOriginal)r=fn(qEN);return r;}',
' var ctxResult=tryContextRefinement(qOriginal);if(ctxResult)return {type:"context",html:ctxResult};',
' var actionResult=tryFn(tryAction);if(actionResult)return {type:"action",html:actionResult};',
' var glActionResult=tryFn(tryGLAction);if(glActionResult){if(typeof glActionResult==="object"&&glActionResult.silent)return {type:"action",html:glActionResult.html};return {type:"glaction",html:glActionResult};}',
' var helpResult=tryFn(tryHelp);if(helpResult)return {type:"help",html:helpResult};',
' var statusResult=tryFn(tryStatus);if(statusResult)return {type:"status",html:statusResult};',
' var arResult=tryFn(tryReceivablesList);if(arResult)return {type:"ar",html:arResult};',
' var apResult=tryFn(tryPayablesList);if(apResult)return {type:"ap",html:apResult};',
' var compareResult=tryFn(tryCompare);if(compareResult){if(typeof compareResult==="object"&&compareResult.silent)return {type:"action",html:compareResult.html};return {type:"compare",html:compareResult};}',
' var varianceResult=tryFn(tryVariance);if(varianceResult)return {type:"variance",html:varianceResult};',
' var aggResult=tryFn(tryAggregation);if(aggResult)return {type:"aggregation",html:aggResult};',
' var searchResult=tryFn(trySearch);if(searchResult)return {type:"search",html:searchResult};',
' var eduResult=tryFn(tryEducation);if(eduResult)return {type:"education",html:eduResult};',
' var bareResult=tryFn(tryBareEntity);if(bareResult)return {type:"search",html:bareResult};',
' return null;',
'}',
'',
'/* ─── EXECUTE CHAT V4 (replaces v3) ─── */',
'function executeChatV4(){',
' var inp=document.getElementById("chatInput");',
' var fb=document.getElementById("chatFeedback");',
' if(!inp||!fb)return;',
' var qOriginal=inp.value.trim();',
' if(!qOriginal){fb.textContent="";return;}',
' var qEN=applyFilipinoDictionary(qOriginal);',
' var v4=routeIntentV4(qEN,qOriginal);',
' if(v4&&v4.html){',
' if(v4.type==="action"){var m=v4.html.match(/alert-title[^>]*>([^<]+)</);fb.className="chat-feedback success";fb.textContent=m?m[1].trim():"\\u2713 Done";inp.value="";setTimeout(function(){if(fb&&fb.textContent.indexOf("\\u2713")===0)fb.textContent="";},2200);return;}',
' pushMessage("user",esc(qOriginal));',
' inp.value="";',
' var typingEl=(typeof showTypingIndicator==="function")?showTypingIndicator():null;',
' setTimeout(function(){',
' if(typingEl&&typeof removeTypingIndicator==="function")removeTypingIndicator(typingEl);',
' if(CONV_STATE&&CONV_STATE.mode==="report"&&v4.type!=="action"){if(ACTIVE_TAB==="gl"&&typeof showFloatingToast==="function"){showFloatingToast(v4.html,v4.type);if(typingEl&&typeof removeTypingIndicator==="function")removeTypingIndicator(typingEl);fb.className="chat-feedback success";fb.textContent="\u2713 "+v4.type+" (toast)";setTimeout(function(){if(fb&&fb.textContent.indexOf("\u2713")===0)fb.textContent="";},2500);return;}else if(typeof enterChatMode==="function")enterChatMode();}',
' pushMessage("system",v4.html);',
' var chips=getSuggestionsForIntent(v4.type);',
' if(chips&&chips.length)pushMessage("system",renderSuggestionChips(chips.map(function(c){return {label:c,onclick:"document.getElementById(\'chatInput\').value=\'"+c.toLowerCase().replace(/\'/g,"\\\\\'")+"\';executeChat();"};})));',
' storeContext(qOriginal,v4.type,v4,{});',
' CHAT_HISTORY.unshift({query:qOriginal,result:{intent:v4.type},ts:Date.now()});',
' if(CHAT_HISTORY.length>30)CHAT_HISTORY.pop();',
' fb.className="chat-feedback success";fb.textContent="✓ "+v4.type;',
' buildSidebar();',
' setTimeout(function(){if(fb&&fb.textContent.indexOf("✓")===0)fb.textContent="";},2500);',
' },280);',
' return;',
' }',
' var result=parseQuery(qEN);',
' if(result.error){fb.className="chat-feedback error";fb.textContent="⚠ "+result.error+(result.suggestion?" - "+result.suggestion:"");return;}',
' applyChatIntent(result);',
' CHAT_HISTORY.unshift({query:qOriginal,result:result,ts:Date.now()});',
' if(CHAT_HISTORY.length>30)CHAT_HISTORY.pop();',
' storeContext(qOriginal,"report",result,{});',
' fb.className="chat-feedback success";fb.textContent="✓ "+result.feedbackLabel;',
' inp.value="";',
' buildSidebar();',
' setTimeout(function(){if(fb&&fb.textContent.indexOf("✓")===0)fb.textContent="";},3500);',
'}',
'',
'/* ═══════════════════════════════════════════',
' BIR LOOSE-LEAF PRINTABLE BOOKS',
' CRB_Print | CDB_Print | GJ_Print | GL_Print',
' ═══════════════════════════════════════════ */',
'var BOOK_STATE={from:"",to:"",preset:"tfy",gjMode:"all",glAccount:"all",showCover:false,showSworn:true};',
'',
'function bookDefaults(){',
' var dates=DATA.jeRows.map(function(e){return e.date;}).filter(function(d){return d&&d.length>=10;}).sort();',
' var maxD=dates[dates.length-1]||new Date().toISOString().substring(0,10);',
' var yr=parseInt(maxD.substring(0,4));',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' if(fyM===1){return{from:yr+"-01-01",to:yr+"-12-31"};}',
' var sY=yr,eY=yr+1,eM=fyM-1;',
' var pad2=function(n){return n<10?"0"+n:""+n;};',
' var lastDay=function(m,y){return new Date(y,m,0).getDate();};',
' return{from:sY+"-"+pad2(fyM)+"-01",to:eY+"-"+pad2(eM)+"-"+pad2(lastDay(eM,eY))};',
'}',
'',
'function applyBookPreset(){',
' var p=document.getElementById("bookPreset").value;',
' if(p==="custom")return;',
' var def=bookDefaults();',
' var dates=DATA.jeRows.map(function(e){return e.date;}).filter(function(d){return d&&d.length>=10;}).sort();',
' var maxD=dates[dates.length-1]||new Date().toISOString().substring(0,10);',
' var yr=parseInt(maxD.substring(0,4));',
' var fyM=parseInt(DATA.settings.fyStartMonth)||1;',
' var pad2=function(n){return n<10?"0"+n:""+n;};',
' var ld=function(m,y){return new Date(y,m,0).getDate();};',
' var f="",t="";',
' if(p==="tfy"){f=def.from;t=def.to;}',
' else if(p==="lfy"){var py=yr-1;if(fyM===1){f=py+"-01-01";t=py+"-12-31";}else{f=py+"-"+pad2(fyM)+"-01";t=yr+"-"+pad2(fyM-1)+"-"+pad2(ld(fyM-1,yr));}}',
' else if(p==="q1"){f=yr+"-01-01";t=yr+"-03-31";}',
' else if(p==="q2"){f=yr+"-04-01";t=yr+"-06-30";}',
' else if(p==="q3"){f=yr+"-07-01";t=yr+"-09-30";}',
' else if(p==="q4"){f=yr+"-10-01";t=yr+"-12-31";}',
' else if(p==="all"){f=dates[0]||def.from;t=maxD;}',
' document.getElementById("bookFrom").value=f;',
' document.getElementById("bookTo").value=t;',
' BOOK_STATE.from=f;BOOK_STATE.to=t;BOOK_STATE.preset=p;',
' rebuildBookContent();',
'}',
'',
'function bookOwnerInfo(){',
' var s=DATA.settings||{};',
' // Taxpayer Classification aware (Individual vs Non-Individual)',
' var isNonIndiv=(s.taxpayerClass==="Non-Individual");',
' var ownerName,ownerDesignation,displayName,registeredName;',
' if(isNonIndiv){',
' ownerName=s.repName||"[Authorized Representative Name]";',
' ownerDesignation=s.repTitle||"Authorized Representative";',
' var regN=s.registeredNameNonInd||"",tradeN=s.businessName||"";',
' registeredName=regN||tradeN||"[Registered Name]";',
' if(regN && tradeN && regN!==tradeN){displayName=regN+" doing business as "+tradeN;}',
' else{displayName=regN||tradeN||"[Business Name]";}',
' }else{',
' var ln=s.ownerLast||"",fn=s.ownerFirst||"",mn=s.ownerMiddle||"";',
' var fnMn=[fn,mn].filter(function(x){return x;}).join(" ");',
' ownerName=ln?(fnMn?ln+", "+fnMn:ln):"[Owner Name]";',
' ownerDesignation="Owner";',
' registeredName=ownerName;',
' displayName=s.businessName||"[Business Name]";',
' }',
' return{',
' name:displayName,',
' registeredName:registeredName,',
' tin:s.tin||"___-___-___-_____",',
' address:s.address||"[Business Address]",',
' ownerName:ownerName,',
' ownerDesignation:ownerDesignation,',
' permitNo:s.permitNo||"_________________"',
' };',
'}',
'',
'function bkMonthAbbr(m){var a=["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"];return a[m-1]||"";}',
'function bkFmtDate(iso){if(!iso||iso.length<10)return"";var p=iso.substring(0,10).split("-");var months=["January","February","March","April","May","June","July","August","September","October","November","December"];return months[parseInt(p[1])-1]+" "+parseInt(p[2])+", "+p[0];}',
'function bkFmtMMDDYY(iso){if(!iso||iso.length<10)return"";var p=iso.substring(0,10).split("-");return p[1]+"/"+p[2]+"/"+p[0].substring(2);}',
'',
'function bkFmtDateRange(from,to){',
' if(!from||!to||from.length<10||to.length<10)return"";',
' var months=["January","February","March","April","May","June","July","August","September","October","November","December"];',
' var f=from.substring(0,10).split("-"),t=to.substring(0,10).split("-");',
' var fY=f[0],fM=parseInt(f[1]),fD=parseInt(f[2]);',
' var tY=t[0],tM=parseInt(t[1]),tD=parseInt(t[2]);',
' if(fY===tY)return months[fM-1]+" "+fD+" to "+months[tM-1]+" "+tD+", "+fY;',
' return months[fM-1]+" "+fD+", "+fY+" to "+months[tM-1]+" "+tD+", "+tY;',
'}',
'',
'function buildBookHeader(title,from,to,extraFilter){',
' var o=bookOwnerInfo();',
' var h="<div class=\\"bk-pagehdr\\">";',
' h+="<div class=\\"bk-title\\">"+esc(title)+"</div>";',
' h+="<div class=\\"bk-entity\\">"+esc(o.registeredName)+" | TIN: "+esc(o.tin)+" | "+esc(o.address)+"</div>";',
' h+="<div class=\\"bk-period-line\\">"+bkFmtDateRange(from,to)+"</div>";',
' h+="</div>";',
' return h;',
'}',
'',
'function buildBookControls(tab){',
' var def=bookDefaults();',
' if(!BOOK_STATE.from)BOOK_STATE.from=def.from;',
' if(!BOOK_STATE.to)BOOK_STATE.to=def.to;',
' var h="<div class=\\"ctrl-bar\\">";',
' h+="<div class=\\"ctrl-group\\"><label>Period</label><select id=\\"bookPreset\\" onchange=\\"applyBookPreset()\\">";',
' var presets=[["tfy","This Fiscal Year"],["lfy","Last Fiscal Year"],["q1","Q1"],["q2","Q2"],["q3","Q3"],["q4","Q4"],["all","All Dates"],["custom","Custom Range"]];',
' for(var i=0;i<presets.length;i++){var p=presets[i];h+="<option value=\\""+p[0]+"\\""+(BOOK_STATE.preset===p[0]?" selected":"")+">"+p[1]+"</option>";}',
' h+="</select></div>";',
' h+="<div class=\\"ctrl-group\\"><label>From</label><input type=\\"date\\" id=\\"bookFrom\\" value=\\""+BOOK_STATE.from+"\\" onchange=\\"BOOK_STATE.from=this.value;BOOK_STATE.preset=\'custom\';document.getElementById(\'bookPreset\').value=\'custom\';rebuildBookContent();\\"></div>";',
' h+="<div class=\\"ctrl-group\\"><label>To</label><input type=\\"date\\" id=\\"bookTo\\" value=\\""+BOOK_STATE.to+"\\" onchange=\\"BOOK_STATE.to=this.value;BOOK_STATE.preset=\'custom\';document.getElementById(\'bookPreset\').value=\'custom\';rebuildBookContent();\\"></div>";',
' if(tab==="gl_book"){',
' var bkAcc=(BOOK_STATE.glAccount&&BOOK_STATE.glAccount!=="all")?BOOK_STATE.glAccount:"All Accounts";',
' h+="<div class=\\"ctrl-group\\"><label>Account</label><input id=\\"bookGLAcct\\" class=\\"ntxn-in ss-input\\" data-ss=\\"acctall\\" value=\\""+esc(bkAcc)+"\\" onchange=\\"bookAcctPick_(this)\\" placeholder=\\"Pick or type...\\" autocomplete=\\"off\\" style=\\"min-width:220px\\"></div>";',
' }',
' h+="<div class=\\"ctrl-group book-print-actions\\" style=\\"margin-left:auto;flex-direction:row;gap:14px;align-items:center;\\">";',
' if(tab==="gj_book"){',
' h+="<label style=\\"display:flex;align-items:center;gap:5px;font-size:12px;color:var(--navy);cursor:pointer;font-weight:500;\\" title=\\"Checked: includes CRB+CDB+GJ. Unchecked: GJ source book only (adjusting entries).\\"><input type=\\"checkbox\\" "+(BOOK_STATE.gjMode==="all"?"checked":"")+" onchange=\\"BOOK_STATE.gjMode=this.checked?\'all\':\'gj\';rebuildBookContent();\\"> All Books</label>";',
' }',
' h+="<label style=\\"display:flex;align-items:center;gap:5px;font-size:12px;color:var(--navy);cursor:pointer;font-weight:500;\\"><input type=\\"checkbox\\" "+(BOOK_STATE.showCover?"checked":"")+" onchange=\\"BOOK_STATE.showCover=this.checked;rebuildBookContent();\\"> Cover Page</label>";',
' h+="<label style=\\"display:flex;align-items:center;gap:5px;font-size:12px;color:var(--navy);cursor:pointer;font-weight:500;\\"><input type=\\"checkbox\\" "+(BOOK_STATE.showSworn?"checked":"")+" onchange=\\"BOOK_STATE.showSworn=this.checked;rebuildBookContent();\\"> Sworn Statement</label>";',
' h+="</div>";',
' h+="</div>";',
' return h;',
'}',
'',
'function buildBookCover(title,from,to,volumeNo){',
' var o=bookOwnerInfo();',
' var h="<div class=\\"bk-cover\\">";',
' h+="<div style=\\"font-size:12px;color:#6B7280;letter-spacing:.5px;\\">REPUBLIC OF THE PHILIPPINES</div>";',
' h+="<div style=\\"font-size:13px;color:#1F3864;font-weight:700;margin-top:4px;\\">BUREAU OF INTERNAL REVENUE</div>";',
' h+="<div class=\\"bk-cover-title\\">"+esc(title)+"</div>";',
' h+="<div style=\\"font-size:14px;color:#374151;margin-top:8px;\\">Loose-Leaf Books of Accounts</div>";',
' h+="<div class=\\"bk-cover-meta\\">";',
' h+="<div class=\\"bk-cover-row\\"><span class=\\"bk-cover-lbl\\">Registered Name</span><span>"+esc(o.registeredName)+"</span></div>";',
' h+="<div class=\\"bk-cover-row\\"><span class=\\"bk-cover-lbl\\">TIN</span><span>"+esc(o.tin)+"</span></div>";',
' h+="<div class=\\"bk-cover-row\\"><span class=\\"bk-cover-lbl\\">Business Address</span><span>"+esc(o.address)+"</span></div>";',
' h+="<div class=\\"bk-cover-row\\"><span class=\\"bk-cover-lbl\\">Period Covered</span><span>"+bkFmtDate(from)+" - "+bkFmtDate(to)+"</span></div>";',
' h+="<div class=\\"bk-cover-row\\"><span class=\\"bk-cover-lbl\\">Volume No.</span><span>"+esc(volumeNo||"01")+"</span></div>";',
' h+="<div class=\\"bk-cover-row\\"><span class=\\"bk-cover-lbl\\">BIR Permit to Use No.</span><span>"+esc(o.permitNo)+"</span></div>";',
' h+="</div>";',
' h+="<div style=\\"margin-top:60px;font-size:10px;color:#6B7280;\\">Printed under BIR Permit to Use Loose-Leaf Books of Accounts</div>";',
' h+="</div>";',
' return h;',
'}',
'',
'function buildBookSworn(title,from,to,totalPages){',
' var o=bookOwnerInfo();',
' var h="<div class=\\"bk-sworn\\">";',
' h+="<h3>VERIFICATION AND SWORN DECLARATION</h3>";',
' h+="<p style=\\"text-align:left;\\"><b>REPUBLIC OF THE PHILIPPINES</b><br>";',
' h+="<b>__________________________)</b> S.S.</p>";',
' h+="<p>I, <b>"+esc(o.ownerName)+"</b>, of legal age, <b>"+esc(o.ownerDesignation)+"</b> of <b>"+esc(o.name)+"</b>, with TIN <b>"+esc(o.tin)+"</b> and business address at <b>"+esc(o.address)+"</b>, after having been duly sworn in accordance with law, do hereby depose and state:</p>";',
' h+="<p><b>1.</b> That the loose-leaf <b>"+esc(title)+"</b> for the period covered from <b>"+bkFmtDate(from)+"</b> to <b>"+bkFmtDate(to)+"</b> consists of a total of <b>"+(totalPages||"____")+"</b> pages, sequentially numbered from Page 1 to Page <b>"+(totalPages||"____")+"</b>;</p>";',
' h+="<p><b>2.</b> That the entries contained in the said book of accounts are true, correct, and accurately reflect the transactions of <b>"+esc(o.name)+"</b> for the said period;</p>";',
' h+="<p><b>3.</b> That the said book of accounts was generated under BIR Permit to Use Loose-Leaf Books of Accounts No. <b>"+esc(o.permitNo)+"</b> issued by the Bureau of Internal Revenue;</p>";',
' h+="<p><b>4.</b> That this declaration is being executed in compliance with the requirements of the Bureau of Internal Revenue under existing rules and regulations.</p>";',
' h+="<p>IN WITNESS WHEREOF, I have hereunto set my hand this <b>____</b> day of <b>____________</b>, 20<b>____</b> at <b>__________________</b>, Philippines.</p>";',
' h+="<div class=\\"bk-sworn-sig\\"><div class=\\"bk-sworn-line\\">"+esc(o.ownerName)+"</div><div style=\\"font-size:10px;color:#6B7280;margin-top:2px;\\">Affiant</div></div>";',
' h+="<p style=\\"margin-top:32px;\\">SUBSCRIBED AND SWORN to before me this _____ day of __________, 20____, affiant exhibiting to me his/her competent evidence of identity ____________________ issued at __________ on __________.</p>";',
' h+="<p style=\\"margin-top:16px;\\">Doc. No. _____;<br>Page No. _____;<br>Book No. _____;<br>Series of 20____.</p>";',
' h+="<div class=\\"bk-sworn-sig\\" style=\\"margin-top:40px;\\"><div class=\\"bk-sworn-line\\">NOTARY PUBLIC</div></div>";',
' h+="</div>";',
' return h;',
'}',
'',
'function bkFmt(n){if(!n||Math.abs(n)<0.005)return"-";var v=Math.abs(n).toFixed(2);v=v.replace(/\\B(?=(\\d{3})+(?!\\d))/g,",");return n<0?"("+v+")":v;}',
'',
'function bkFilterRows(from,to,mode){',
' var rows=[];',
' for(var i=0;i<DATA.jeRows.length;i++){',
' var je=DATA.jeRows[i];',
' if(!je.date||je.date<from||je.date>to)continue;',
' if(mode==="crb"&&je.source!=="CRB")continue;',
' if(mode==="cdb"&&je.source!=="CDB")continue;',
' if(mode==="sj"&&je.source!=="SJ")continue;',
' if(mode==="pj"&&je.source!=="PJ")continue;',
' if(mode==="gj"&&je.source!=="GJ")continue;',
' rows.push(je);',
' }',
' rows.sort(function(a,b){',
' if(a.date!==b.date)return a.date<b.date?-1:1;',
' var ar=a.ref||"",br=b.ref||"";',
' if(ar!==br)return ar<br?-1:1;',
' return 0;',
' });',
' return rows;',
'}',
'',
'function bkGroupByRef(rows){',
' var groups=[];var seen={};',
' for(var i=0;i<rows.length;i++){',
' var je=rows[i];',
' var k=(je.date||"")+"|"+(je.ref||"")+"|"+(je.entryId||"")+"|"+(je.source||"");',
' if(!seen[k]){seen[k]={date:je.date,ref:je.ref,desc:je.desc,name:je.name,entryId:je.entryId,source:je.source,legs:[]};groups.push(seen[k]);}',
' seen[k].legs.push(je);',
' }',
' return groups;',
'}',
'',
'function bkAcctCode(title){for(var i=0;i<DATA.coa.length;i++){if(String(DATA.coa[i].title).trim()===String(title).trim())return DATA.coa[i].code||"";}return"";}',
'function bkAcctNormal(title){for(var i=0;i<DATA.coa.length;i++){if(String(DATA.coa[i].title).trim()===String(title).trim()){var n=String(DATA.coa[i].normal||"DR").toUpperCase();return n.indexOf("CR")===0?"CR":"DR";}}return"DR";}',
'',
'function renderJournalBook(title,mode,from,to){',
' var rows=bkFilterRows(from,to,mode==="all"?"":mode);',
' var groups=bkGroupByRef(rows);',
' var h="<div class=\\"book-print\\">";',
' if(BOOK_STATE.showCover){h+=buildBookCover(title,from,to,"01");h+="<div class=\\"book-page-break\\"></div>";}',
' h+=buildBookHeader(title,from,to,"");',
' h+="<table class=\\"bk-tbl\\"><thead><tr>";',
' h+="<th>Ln</th><th>Mo</th><th>Day</th><th>Particulars</th><th>P.R.</th><th>Debit</th><th>Credit</th>";',
' h+="</tr></thead><tbody>";',
' if(!groups.length){h+="<tr class=\\"bk-empty\\"><td colspan=\\"7\\">No entries for the selected period.</td></tr>";}',
' var ln=1,prevYr="",prevMo="",prevDay="";',
' var totalDr=0,totalCr=0;',
' for(var gi=0;gi<groups.length;gi++){',
' var g=groups[gi];',
' var dt=(g.date||"").substring(0,10);',
' var yr=dt.substring(0,4),mo=parseInt(dt.substring(5,7))||0,day=parseInt(dt.substring(8,10))||0;',
' if(yr!==prevYr&&yr){',
' h+="<tr class=\\"bk-yr-row\\"><td class=\\"bk-ln\\">"+(ln++)+"</td><td class=\\"bk-mo\\">"+yr+"</td><td class=\\"bk-day\\"></td><td class=\\"bk-particulars\\"></td><td class=\\"bk-pr\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-num\\"></td></tr>";',
' prevYr=yr;prevMo="";prevDay="";',
' }',
' var legs=g.legs.slice().sort(function(a,b){var ad=(a.debit||0)>0?0:1,bd=(b.debit||0)>0?0:1;if(ad!==bd)return ad-bd;return 0;});',
' var hasHdrRow=g.ref||g.desc;',
' for(var li=0;li<legs.length;li++){',
' var leg=legs[li];',
' var moStr="",dayStr="";',
' if(li===0){',
' if(mo!==parseInt(prevMo)){moStr=bkMonthAbbr(mo);prevMo=mo;}',
' if(day!==parseInt(prevDay)||mo!==parseInt(prevMo)){dayStr=day;prevDay=day;}',
' }',
' var indent=(leg.credit||0)>0?" ":"";',
' var particulars=indent+esc(leg.account);',
' h+="<tr><td class=\\"bk-ln\\">"+(ln++)+"</td>";',
' h+="<td class=\\"bk-mo\\">"+moStr+"</td>";',
' h+="<td class=\\"bk-day\\">"+dayStr+"</td>";',
' h+="<td class=\\"bk-particulars\\">"+particulars+"</td>";',
' h+="<td class=\\"bk-pr\\">"+esc(bkAcctCode(leg.account))+"</td>";',
' h+="<td class=\\"bk-num\\">"+bkFmt(leg.debit||0)+"</td>";',
' h+="<td class=\\"bk-num\\">"+bkFmt(leg.credit||0)+"</td></tr>";',
' totalDr+=(leg.debit||0);totalCr+=(leg.credit||0);',
' }',
' if(hasHdrRow){',
' var descTxt="";',
' if(g.desc){descTxt+=esc(g.desc);}',
' else if(g.name){descTxt+=esc(g.name);}',
' if(g.ref)descTxt+=(descTxt?" ":"")+"("+esc(g.ref)+")";',
' h+="<tr><td class=\\"bk-ln\\">"+(ln++)+"</td><td class=\\"bk-mo\\"></td><td class=\\"bk-day\\"></td><td class=\\"bk-particulars\\" style=\\"font-style:italic;color:#6B7280;padding-left:24px;\\">"+descTxt+"</td><td class=\\"bk-pr\\"></td><td class=\\"bk-num\\">-</td><td class=\\"bk-num\\">-</td></tr>";',
' }',
' }',
' if(groups.length){',
' h+="<tr class=\\"bk-totals\\"><td colspan=\\"5\\" class=\\"bk-particulars\\" style=\\"text-align:right;padding-right:12px;\\">TOTAL</td>";',
' h+="<td class=\\"bk-num\\">"+bkFmt(totalDr)+"</td>";',
' h+="<td class=\\"bk-num\\">"+bkFmt(totalCr)+"</td></tr>";',
' if(Math.abs(totalDr-totalCr)>0.01){',
' h+="<tr><td colspan=\\"7\\" style=\\"color:#DC2626;font-style:italic;text-align:center;background:#FEF2F2;\\">⚠ Out of balance - diff: "+bkFmt(totalDr-totalCr)+"</td></tr>";',
' }',
' }',
' h+="</tbody></table>";',
' if(BOOK_STATE.showSworn){h+="<div class=\\"book-page-break\\"></div>";h+=buildBookSworn(title,from,to,null);}',
' h+="</div>";',
' return h;',
'}',
'',
'function renderGLBook(from,to){',
' var h="<div class=\\"book-print\\">";',
' if(BOOK_STATE.showCover){h+=buildBookCover("GENERAL LEDGER",from,to,"01");h+="<div class=\\"book-page-break\\"></div>";}',
' h+=buildBookHeader("GENERAL LEDGER",from,to,"");',
' h+="<table class=\\"bk-tbl\\"><thead><tr>";',
' h+="<th>Ln</th><th>Mo</th><th>Day</th><th>Particulars</th><th>Ref</th><th>Debit</th><th>Credit</th><th>Balance</th>";',
' h+="</tr></thead><tbody>";',
' var coa=DATA.coa.slice().sort(function(a,b){return String(a.code||"").localeCompare(String(b.code||""));});',
' var ln=1,anyAcct=false;',
' for(var ai=0;ai<coa.length;ai++){',
' var ac=coa[ai];',
' var inact=ac.active&&String(ac.active).toUpperCase()==="NO";',
' if(inact)continue;',
' var title=String(ac.title).trim();',
' if(BOOK_STATE.glAccount!=="all"&&BOOK_STATE.glAccount!==title)continue;',
' var opening=0,priorCount=0;',
' for(var pi=0;pi<DATA.jeRows.length;pi++){var pj=DATA.jeRows[pi];if(pj.account!==title||!pj.date||pj.date>=from)continue;opening+=(pj.debit||0)-(pj.credit||0);priorCount++;}',
' var perRows=[];',
' for(var pi2=0;pi2<DATA.jeRows.length;pi2++){var pj2=DATA.jeRows[pi2];if(pj2.account!==title||!pj2.date||pj2.date<from||pj2.date>to)continue;perRows.push(pj2);}',
' if(!perRows.length&&priorCount===0)continue;',
' perRows.sort(function(a,b){if(a.date!==b.date)return a.date<b.date?-1:1;var ar=a.ref||"",br=b.ref||"";if(ar!==br)return ar<br?-1:1;return 0;});',
' anyAcct=true;',
' var normal=bkAcctNormal(title);',
' var normalLabel=normal==="DR"?"Debit":"Credit";',
' var code=ac.code||"";',
' h+="<tr class=\\"bk-acct-hdr\\"><td colspan=\\"8\\" class=\\"bk-acct-name\\"><b>"+esc(title)+(code?" ("+esc(code)+")":"")+"</b><span class=\\"bk-norm-pill\\">"+normalLabel+"</span></td></tr>";',
' var openSigned=normal==="DR"?opening:-opening;',
' if(priorCount>0){',
' h+="<tr class=\\"bk-opening\\"><td class=\\"bk-ln\\">"+(ln++)+"</td><td class=\\"bk-mo\\"></td><td class=\\"bk-day\\"></td><td class=\\"bk-particulars\\">Beginning Balance - as of "+bkFmtDate(from)+"</td><td class=\\"bk-ref\\"></td><td class=\\"bk-num\\">-</td><td class=\\"bk-num\\">-</td><td class=\\"bk-bal\\">"+bkFmt(openSigned)+"</td></tr>";',
' }',
' var prevYr="",prevMo="",prevDay="";',
' var runBal=opening;',
' var periodDr=0,periodCr=0;',
' for(var ri=0;ri<perRows.length;ri++){',
' var je=perRows[ri];',
' runBal+=(je.debit||0)-(je.credit||0);',
' periodDr+=(je.debit||0);periodCr+=(je.credit||0);',
' var dt=je.date||"";',
' var yr=dt.substring(0,4),mo=parseInt(dt.substring(5,7))||0,day=parseInt(dt.substring(8,10))||0;',
' if(yr!==prevYr&&yr){h+="<tr class=\\"bk-yr-row\\"><td class=\\"bk-ln\\">"+(ln++)+"</td><td class=\\"bk-mo\\">"+yr+"</td><td class=\\"bk-day\\"></td><td class=\\"bk-particulars\\"></td><td class=\\"bk-ref\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-bal\\"></td></tr>";prevYr=yr;prevMo="";prevDay="";}',
' var moStr="",dayStr="";',
' if(mo!==parseInt(prevMo)){moStr=bkMonthAbbr(mo);prevMo=mo;}',
' if(day!==parseInt(prevDay)){dayStr=day;prevDay=day;}',
' var balShown=normal==="DR"?runBal:-runBal;',
' var descTxt="";',
' if(je.desc)descTxt=esc(je.desc);',
' else if(je.name)descTxt=esc(je.name);',
' else descTxt=esc(je.source||"");',
' h+="<tr><td class=\\"bk-ln\\">"+(ln++)+"</td>";',
' h+="<td class=\\"bk-mo\\">"+moStr+"</td>";',
' h+="<td class=\\"bk-day\\">"+dayStr+"</td>";',
' h+="<td class=\\"bk-particulars\\">"+descTxt+"</td>";',
' h+="<td class=\\"bk-ref\\">"+esc(je.ref||"")+"</td>";',
' h+="<td class=\\"bk-num\\">"+bkFmt(je.debit||0)+"</td>";',
' h+="<td class=\\"bk-num\\">"+bkFmt(je.credit||0)+"</td>";',
' h+="<td class=\\"bk-bal\\">"+bkFmt(balShown)+"</td></tr>";',
' }',
' if(perRows.length){',
' var closeSigned=normal==="DR"?runBal:-runBal;',
' h+="<tr class=\\"bk-closing\\"><td colspan=\\"5\\" class=\\"bk-particulars\\" style=\\"text-align:right;padding-right:12px;\\">Ending Balance as of "+bkFmtDate(to)+"</td>";',
' h+="<td class=\\"bk-num\\">"+bkFmt(periodDr)+"</td>";',
' h+="<td class=\\"bk-num\\">"+bkFmt(periodCr)+"</td>";',
' h+="<td class=\\"bk-bal\\">"+bkFmt(closeSigned)+"</td></tr>";',
' }',
' }',
' if(!anyAcct){h+="<tr class=\\"bk-empty\\"><td colspan=\\"8\\">No accounts with activity for the selected period.</td></tr>";}',
' h+="</tbody></table>";',
' if(BOOK_STATE.showSworn){h+="<div class=\\"book-page-break\\"></div>";h+=buildBookSworn("GENERAL LEDGER",from,to,null);}',
' h+="</div>";',
' return h;',
'}',
'',
'function rebuildBookContent(){',
' var tab=ACTIVE_TAB;',
' var from=BOOK_STATE.from,to=BOOK_STATE.to;',
' var html="<div class=\\"book-wrap\\">";',
' if(tab==="crb_book")html+=renderJournalBook("CASH RECEIPTS BOOK","crb",from,to);',
' else if(tab==="cdb_book")html+=renderJournalBook("CASH DISBURSEMENTS BOOK","cdb",from,to);',
' else if(tab==="sj_book")html+=renderJournalBook("SALES JOURNAL","sj",from,to);',
' else if(tab==="ssj_book")html+=renderSubsidiarySalesBook(from,to);',
' else if(tab==="pj_book")html+=renderJournalBook("PURCHASE JOURNAL","pj",from,to);',
' else if(tab==="spj_book")html+=renderSubsidiaryPurchaseBook(from,to);',
' else if(tab==="gj_book")html+=renderJournalBook("GENERAL JOURNAL",BOOK_STATE.gjMode,from,to);',
' else if(tab==="gl_book")html+=renderGLBook(from,to);',
' html+="</div>";',
' document.getElementById("report").innerHTML=html;',
'}',
'',
'function buildBookPrint(tab){',
' ACTIVE_TAB=tab;',
' if(typeof enterReportMode==="function")enterReportMode();',
' if(typeof buildBookControls==="function")document.getElementById("controls").innerHTML=buildBookControls(tab);',
' if(typeof rebuildBookContent==="function")rebuildBookContent();',
'}',
].join('\n'); }
/* ════════════════════════ SALES-SIDE CLIENT VIEWS ════════════════════════
Appended to the main JS in buildHTML_ (same <script>, shared scope). Renders
the Subsidiary Sales Journal (loose-leaf book), the SLS RELIEF report, and
the SLS DAT (per customer per month). Called by switchTab + rebuildBookContent.
VAT classification is server-side (VAT Tag row only); these are view-only. */
/* ════════════════════════ SUBSIDIARY LEDGERS + AGING ════════════════════════
Customer (A/R) and Supplier (A/P) subsidiary ledgers with running balance,
per-party activity totals (total sales / collection / purchases / payments),
and open-item FIFO aging. Client-side over DATA.jeRows (name/tin carried).
Appended to main JS in buildHTML_. */
/* ════════════════════════ NEW TRANSACTION (write-back) ════════════════════════
Xero-style Receive Money (CRB) / Spend Money (CDB). Builds a balanced entry,
writes it to the SoT via google.script.run.saveTransaction, then re-reads via
apiRefresh so reports update immediately. VAT auto-split (net + 12%) for
VAT-registered. HO books only in Phase 1 (branch entry = later phase). */
function getNewTxnJS_() { return `
/* ════════ NEW TRANSACTION (write-back): Receive/Spend Money, Invoice, Bill, Journal, Receive Payment, Pay Bill ════════
Guided entry -> google.script.run.saveTransaction / saveJournal -> apiRefresh. VAT tag per line; auto-create columns
handled server-side; customer/supplier details captured for SLS/SLP. HO books only (branch entry = later phase). */
var NTXN_T={
receive:{book:"CRB",label:"Receive Money",sub:"Cash IN - Cash Receipts Book. Debit cash, credit income.",sales:true,cash:"DR",line:"CR",vat:"output",partyLbl:"Received from"},
spend:{book:"CDB",label:"Spend Money",sub:"Cash OUT - Cash Disbursements Book. Debit expense, credit cash.",sales:false,cash:"CR",line:"DR",vat:"input",partyLbl:"Paid to"},
invoice:{book:"SJ",label:"Invoice",sub:"Credit sale - Sales Journal. Debit Accounts Receivable, credit income.",sales:true,control:"Accounts Receivable",controlSide:"DR",line:"CR",vat:"output",partyLbl:"Customer"},
bill:{book:"PJ",label:"Bill",sub:"Credit purchase - Purchase Journal. Debit expense, credit Accounts Payable.",sales:false,control:"Accounts Payable",controlSide:"CR",line:"DR",vat:"input",partyLbl:"Supplier"},
journal:{book:"GJ",label:"Journal",sub:"General Journal - free-form debits and credits.",journal:true},
payar:{book:"CRB",label:"Receive Payment",sub:"Collect an open invoice - Cash Receipts Book.",pay:"ar",partyLbl:"Customer"},
payap:{book:"CDB",label:"Pay Bill",sub:"Settle an open bill - Cash Disbursements Book.",pay:"ap",partyLbl:"Supplier"}
};
var NEWTXN_STATE=null;
var NTXN_LASTCASH={CRB:"",CDB:""};
var NTXN_SECTIONS=[{key:"receive",label:"Receive Money",types:["receive"]},{key:"spend",label:"Spend Money",types:["spend"]},{key:"invoice",label:"Invoice",types:["invoice","payar"]},{key:"bill",label:"Bill",types:["bill","payap"]},{key:"journal",label:"Journal",types:["journal"]}];
function ntxnSectionOf_(t){for(var i=0;i<NTXN_SECTIONS.length;i++)if(NTXN_SECTIONS[i].types.indexOf(t)>=0)return NTXN_SECTIONS[i];return null;}
function ntxnAge_(d){if(!d)return 0;var t=new Date(d+"T00:00:00"),n=new Date();var a=Math.floor((n-t)/86400000);return a<0?0:a;}
var NTXN_BOOKLBL={receive:{code:"CRB",book:"Cash Receipts Book",cue:"Cash in - sales, collections",bg:"#E6F4EA",fg:"#1E7E34"},spend:{code:"CDB",book:"Cash Disbursements Book",cue:"Cash out - expenses, bills",bg:"#FDECEA",fg:"#B00020"},invoice:{code:"SJ",book:"Sales Journal",cue:"Credit sale - sold now, customer pays later",bg:"#E8F0FE",fg:"#1F3864"},bill:{code:"PJ",book:"Purchase Journal",cue:"Credit purchase - bought now, you pay later",bg:"#FFF4DE",fg:"#92600A"},journal:{code:"GJ",book:"General Journal",cue:"Adjustments - corrections, accruals, others",bg:"#EEF1F5",fg:"#5F5E5A"}};
function ntxnMenuToggle(){if(document.getElementById("ntxnMenu")){ntxnMenuClose_();return;}var btn=document.getElementById("tbNewBtn");var r=btn?btn.getBoundingClientRect():{bottom:46,right:320};var d=document.createElement("div");d.id="ntxnMenu";d.className="ntxn-menu";d.style.top=(r.bottom+6)+"px";d.style.left=Math.max(8,r.right-320)+"px";var h="<div class=\\"ntxn-menu-hdr\\">New transaction - pick a book</div>";["receive","spend","invoice","bill","journal"].forEach(function(t){var L=NTXN_BOOKLBL[t];h+="<div class=\\"ntxn-menu-item\\" onclick=\\"ntxnOpen('"+t+"')\\"><div class=\\"ntxn-menu-ico\\" style=\\"background:"+L.bg+";color:"+L.fg+"\\">"+L.code+"</div><div style=\\"flex:1;min-width:0\\"><div class=\\"ntxn-menu-t1\\">"+L.book+"</div><div class=\\"ntxn-menu-t2\\">"+L.cue+"</div></div></div>";});d.innerHTML=h;document.body.appendChild(d);setTimeout(function(){document.addEventListener("click",ntxnMenuOutside_);},0);}
function ntxnMenuClose_(){var m=document.getElementById("ntxnMenu");if(m&&m.parentNode)m.parentNode.removeChild(m);document.removeEventListener("click",ntxnMenuOutside_);}
function ntxnMenuOutside_(e){var m=document.getElementById("ntxnMenu");if(!m)return;var btn=document.getElementById("tbNewBtn");if(m.contains(e.target)||(btn&&btn.contains(e.target)))return;ntxnMenuClose_();}
function ntxnOpen(t){ntxnMenuClose_();NEWTXN_STATE=null;ntxnInit_();NEWTXN_STATE.type=t;switchTab("newtxn");}
function ntxnFromBME(type){
var legs=(typeof bmeLegs_==="function")?bmeLegs_():[];
ntxnMenuClose_();NEWTXN_STATE=null;ntxnInit_();
var s=NEWTXN_STATE;s.type=type;
if(typeof BME_STATE!=="undefined"&&BME_STATE){s.date=BME_STATE.date||"";s.desc=BME_STATE.desc||"";}
s.vatMode="inc";
var cfg=NTXN_T[type]||NTXN_T.journal;
if(cfg.journal){
var jl=[];for(var i=0;i<legs.length;i++){var l=legs[i];if(!l.side||l.amt<=0)continue;jl.push({a:l.title,dr:l.side==="DEBIT"?String(l.amt):"",cr:l.side==="CREDIT"?String(l.amt):""});}
while(jl.length<4)jl.push({a:"",s:"",m:""});s.jlines=jl;
}else{
var cashLines=[],lines=[];
var ctlRe=cfg.control==="Accounts Receivable"?/^accounts receivable/i:(cfg.control==="Accounts Payable"?/^accounts payable/i:null);
for(var j=0;j<legs.length;j++){var lg=legs[j];if(!lg.side||lg.amt<=0)continue;var sd=lg.side==="DEBIT"?"DR":"CR";
if(cfg.cash&&lg.isCash&&sd===cfg.cash){cashLines.push({a:lg.title,m:String(lg.amt)});continue;}
if(ctlRe&&ctlRe.test(lg.title)&&sd===cfg.controlSide)continue;
lines.push({a:lg.title,t:"",m:String(lg.amt)});}
if(cashLines.length)s.cashLines=cashLines;
while(lines.length<4)lines.push({a:"",t:"",m:""});s.lines=lines;
}
switchTab("newtxn");try{window.scrollTo(0,0);}catch(e){}
}
/* ===== Edit / Void an existing entry (modal over GL) ===== */
var EDIT_STATE=null, JE_CUR_={source:"",entryId:"",row:""};
function eeTypeForBook_(b){return ({CRB:"receive",CDB:"spend",SJ:"invoice",PJ:"bill",GJ:"journal"})[String(b||"").toUpperCase()]||"journal";}
function eeDeployMsg_(){return "Edit / Void needs the latest deploy of the WebApp. In the Apps Script editor: Deploy \\u2192 Manage deployments \\u2192 Edit (pencil) \\u2192 Version: New version \\u2192 Deploy. Then re-launch from the Sheet and reload this page.";}
function entryEditFromJE_(){var c=JE_CUR_;if(typeof closeJEModal==="function")closeJEModal();showEntryEdit(c.source,c.entryId,c.row,c.date,c.ref);}
function entryVoidFromJE_(){var c=JE_CUR_;entryVoid(c.source,c.entryId,c.row);}
function eePrefillFromJe_(source,entryId,row,date,ref){
var je=(DATA&&DATA.jeRows)||[];var legs=[],meta=null;
for(var i=0;i<je.length;i++){var r=je[i];var mt=(entryId&&(r.entryId||""))?((r.entryId||"")===String(entryId)):(r.date===date&&(r.ref||"")===(ref||""));if(!mt)continue;if(source&&String(r.source||"").toUpperCase()!==String(source).toUpperCase())continue;
var dr=parseFloat(r.debit)||0,cr=parseFloat(r.credit)||0;legs.push({a:r.account||"",s:dr>0?"DR":"CR",m:String(dr>0?dr:cr),t:r.tag||""});if(!meta)meta=r;}
if(!legs.length)return null;
return {source:source,entryId:entryId,row:row||(meta&&meta.sourceRow)||"",date:(meta&&meta.date)||"",ref:(meta&&meta.ref)||"",desc:(meta&&meta.desc)||"",status:"Posted",
party:{name:(meta&&meta.name)||"",tin:(meta&&meta.tin)||"",branch:(meta&&meta.branchCode)||"",last:"",first:"",middle:"",addr1:"",addr2:""},legs:legs,showParty:false};
}
function showEntryEdit(source,entryId,row,date,ref){
var pre=null;try{pre=eePrefillFromJe_(source,entryId,row,date,ref);}catch(e){}
if(pre){EDIT_STATE=pre;entryEditRender_();}
else{if(typeof google==="undefined"||!google.script||!google.script.run||!DATA.spreadsheetId){refreshToast_("Edit needs the live WebApp (open via the Sheet menu link).",true);return;}EDIT_STATE={loading:true,source:source,entryId:entryId,row:row};entryEditMount_("<div class=\\"je-modal-overlay\\"><div class=\\"je-modal\\" style=\\"max-width:760px\\"><div style=\\"padding:24px;color:#6B7280\\">Loading entry "+esc(entryId)+"...</div></div></div>");}
if(typeof google==="undefined"||!google.script||!google.script.run||!DATA.spreadsheetId)return;
try{google.script.run.withSuccessHandler(entryEditLoaded_).withFailureHandler(function(e){if(!EDIT_STATE||EDIT_STATE.loading){entryEditMount_("");refreshToast_("Could not load entry: "+((e&&e.message)||e),true);}}).getEntryForEdit(DATA.spreadsheetId,{source:source,entryId:entryId,row:row});}
catch(e){if(!pre){entryEditClose();refreshToast_(eeDeployMsg_(),true);}}
}
function entryEditLoaded_(r){
var sp=(r&&r.party)||{name:"",tin:"",branch:"",last:"",first:"",middle:"",addr1:"",addr2:""};
if(!r||r.__error){
// Only surface a failure if the instant payload render did NOT already happen.
if(!EDIT_STATE||EDIT_STATE.loading){entryEditClose();refreshToast_("Could not load entry: "+((r&&r.__error)||"unknown"),true);}
return;
}
if(EDIT_STATE&&!EDIT_STATE.loading){
// Instant render already happened from the payload. Only ENRICH the party sub-fields
// (last/first/middle/address) not carried by jeRows - quietly, NO re-render -> no flicker,
// and any in-progress edits are preserved. Re-render only if the Details panel is open.
var P=EDIT_STATE.party||(EDIT_STATE.party={});
if(!P.name)P.name=sp.name||"";if(!P.tin)P.tin=sp.tin||"";if(!P.branch)P.branch=sp.branch||sp.branchCode||"";
if(!P.last)P.last=sp.last||"";if(!P.first)P.first=sp.first||"";if(!P.middle)P.middle=sp.middle||"";
if(!P.addr1)P.addr1=sp.addr1||"";if(!P.addr2)P.addr2=sp.addr2||"";
if(!EDIT_STATE.row)EDIT_STATE.row=r.row||r.firstRow||"";
eePatchParty_();
return;
}
if(!EDIT_STATE)return; // user cancelled/closed before the server responded - do not re-open
EDIT_STATE={source:r.source,entryId:r.entryId,row:r.row||r.firstRow||"",date:r.date||"",ref:r.ref||"",desc:r.desc||"",status:r.status||"Posted",
party:sp,
legs:(r.legs||[]).map(function(l){return {a:l.account||"",s:String(l.side||"DR").toUpperCase(),m:(l.amount!=null?String(l.amount):""),t:l.tag||""};}),showParty:false};
if(!EDIT_STATE.legs.length)EDIT_STATE.legs=[{a:"",s:"DR",m:""},{a:"",s:"CR",m:""}];
entryEditRender_();
}
function entryEditMount_(html){var ex=document.getElementById("entryEditModal");if(!html){if(ex&&ex.parentNode)ex.parentNode.removeChild(ex);return;}if(ex){var tmp=document.createElement("div");tmp.innerHTML=html;var nm=tmp.querySelector(".je-modal"),cm=ex.querySelector(".je-modal");if(nm&&cm){cm.innerHTML=nm.innerHTML;return;}ex.innerHTML=html;return;}var d=document.createElement("div");d.id="entryEditModal";d.innerHTML=html;document.body.appendChild(d);}
function entryEditClose(e){if(e&&e.target&&e.currentTarget&&e.target!==e.currentTarget)return;var ex=document.getElementById("entryEditModal");if(ex&&ex.parentNode)ex.parentNode.removeChild(ex);EDIT_STATE=null;}function entryEditRender_(){
var s=EDIT_STATE;if(!s)return;var isGJ=String(s.source).toUpperCase()==="GJ";var showVat=eeShowVat_(s.source);
var L=NTXN_BOOKLBL[eeTypeForBook_(s.source)]||NTXN_BOOKLBL.journal;
var h="<div class=\\"je-modal-overlay\\" onclick=\\"entryEditClose(event)\\"><div class=\\"je-modal\\" onclick=\\"event.stopPropagation()\\" style=\\"max-width:780px\\">";
h+="<div class=\\"je-modal-hdr\\"><div class=\\"je-modal-title\\">"+(s.dup?"Duplicate entry":"Edit entry")+(s.entryId?" \\u00b7 "+esc(s.entryId):"")+"</div><button class=\\"je-modal-close\\" onclick=\\"entryEditClose(event)\\" title=\\"Close\\">\\u00d7</button></div>";
h+="<div class=\\"je-modal-meta\\"><span class=\\"je-source-badge\\" style=\\"background:"+L.bg+";color:"+L.fg+"\\">"+esc(L.code)+"</span> <span>"+esc(L.book)+"</span>"+(s.row?" <span>\\u00b7 Row <b>"+esc(String(s.row))+"</b></span>":"")+"</div>";
h+="<div style=\\"background:#FEF3C7;border:1px solid #F59E0B;color:#92400E;border-radius:8px;padding:9px 12px;margin:10px 0;font-size:12px;line-height:1.5\\">\\u26a0 Baka na-file na ang period na ito sa BIR. Mag-ingat sa pag-edit - maaapektuhan ang na-file nang returns (2550Q/2551Q/1701Q/1702Q) kung babaguhin ang lumang entry. Kung sigurado kang filed na, mas mabuting gumawa ng reversing entry.</div>";
h+="<div style=\\"display:grid;grid-template-columns:1fr 1fr 2fr;gap:10px;margin-bottom:10px\\">";
h+="<div><div class=\\"ee-lbl\\">Date</div><input type=\\"date\\" id=\\"eeDate\\" class=\\"ntxn-in\\" value=\\""+esc(s.date)+"\\" oninput=\\"entryEditSync_()\\"></div>";
h+="<div><div class=\\"ee-lbl\\">"+(isGJ?"Reference":"Reference / OR No")+"</div><input type=\\"text\\" id=\\"eeRef\\" class=\\"ntxn-in\\" value=\\""+esc(s.ref)+"\\" oninput=\\"entryEditSync_()\\"></div>";
h+="<div><div class=\\"ee-lbl\\">Description</div><input type=\\"text\\" id=\\"eeDesc\\" class=\\"ntxn-in\\" value=\\""+esc(s.desc)+"\\" oninput=\\"entryEditSync_()\\"></div></div>";
// legs
h+="<table class=\\"je-modal-table\\"><thead><tr><th>Account</th>"+(showVat?"<th style=\\"width:150px\\">VAT class</th>":"")+"<th class=\\"num\\" style=\\"width:120px\\">Debit</th><th class=\\"num\\" style=\\"width:120px\\">Credit</th><th style=\\"width:34px\\"></th></tr></thead><tbody>";
for(var i=0;i<s.legs.length;i++){var lg=s.legs[i];
h+="<tr><td><input type=\\"text\\" id=\\"eeA"+i+"\\" class=\\"ntxn-in ss-input\\" data-ss=\\"acct\\" value=\\""+esc(lg.a)+"\\" oninput=\\"entryEditSync_()\\" placeholder=\\"Type to search...\\" autocomplete=\\"off\\"></td>";
if(showVat)h+=eeVatCell_(i,lg,s.source);
h+="<td><input type=\\"text\\" inputmode=\\"decimal\\" id=\\"eeDr"+i+"\\" class=\\"ntxn-in amt-in\\" value=\\""+(lg.s==="DR"?esc(amtDisp_(lg.m)):"")+"\\" oninput=\\"eeJSide_(this,"+i+",'DR');entryEditSync_()\\" onfocus=\\"amtFocus_(this)\\" onblur=\\"eeRoundCell(this)\\" placeholder=\\"0.00\\"></td>";
h+="<td><input type=\\"text\\" inputmode=\\"decimal\\" id=\\"eeCr"+i+"\\" class=\\"ntxn-in amt-in\\" value=\\""+(lg.s==="CR"?esc(amtDisp_(lg.m)):"")+"\\" oninput=\\"eeJSide_(this,"+i+",'CR');entryEditSync_()\\" onfocus=\\"amtFocus_(this)\\" onblur=\\"eeRoundCell(this)\\" placeholder=\\"0.00\\"></td>";
h+="<td>"+(s.legs.length>2?"<button class=\\"ntxn-btn\\" style=\\"padding:4px 9px\\" onclick=\\"entryEditDelLeg("+i+")\\">\\u00d7</button>":"")+"</td></tr>";
}
h+="</tbody></table>";
h+="<div style=\\"margin-top:8px\\"><button class=\\"ntxn-btn\\" onclick=\\"entryEditAddLeg()\\">+ Add line</button></div>";
h+="<div id=\\"eeBal\\" style=\\"margin-top:10px;font-size:12.5px;font-weight:600\\"></div>";
if(!isGJ){
var P=s.party;
h+="<div style=\\"margin-top:8px\\"><a href=\\"javascript:void(0)\\" onclick=\\"entryEditToggleParty_()\\" style=\\"font-size:12px;color:var(--blue)\\"><span id=\\"eePartyArrow\\">"+(s.showParty?"\\u25be":"\\u25b8")+"</span> Details - name, TIN, branch, address</a></div>";
h+="<div id=\\"eeParty\\" style=\\"display:"+(s.showParty?"grid":"none")+";grid-template-columns:1fr 1fr 1fr;gap:8px;margin-top:8px\\">";
h+="<div><div class=\\"ee-lbl\\">Registered name</div><input id=\\"eeP_name\\" class=\\"ntxn-in\\" value=\\""+esc(P.name)+"\\" oninput=\\"entryEditSync_()\\"></div>";
h+="<div><div class=\\"ee-lbl\\">TIN</div><input id=\\"eeP_tin\\" class=\\"ntxn-in\\" value=\\""+esc(P.tin)+"\\" inputmode=\\"numeric\\" maxlength=\\"11\\" oninput=\\"fmtTin_(this);entryEditSync_()\\" placeholder=\\"000-000-000\\"></div>";
h+="<div><div class=\\"ee-lbl\\">Branch code</div><input id=\\"eeP_branch\\" class=\\"ntxn-in\\" value=\\""+esc(P.branch)+"\\" inputmode=\\"numeric\\" maxlength=\\"5\\" oninput=\\"fmtBranch_(this);entryEditSync_()\\" onblur=\\"padBranch_(this);entryEditSync_()\\" placeholder=\\"00000\\"></div>";
h+="<div><div class=\\"ee-lbl\\">Last name</div><input id=\\"eeP_last\\" class=\\"ntxn-in\\" value=\\""+esc(P.last)+"\\" oninput=\\"entryEditSync_()\\"></div>";
h+="<div><div class=\\"ee-lbl\\">First name</div><input id=\\"eeP_first\\" class=\\"ntxn-in\\" value=\\""+esc(P.first)+"\\" oninput=\\"entryEditSync_()\\"></div>";
h+="<div><div class=\\"ee-lbl\\">Middle name</div><input id=\\"eeP_middle\\" class=\\"ntxn-in\\" value=\\""+esc(P.middle)+"\\" oninput=\\"entryEditSync_()\\"></div>";
h+="<div style=\\"grid-column:span 2\\"><div class=\\"ee-lbl\\">Address 1</div><input id=\\"eeP_addr1\\" class=\\"ntxn-in\\" value=\\""+esc(P.addr1)+"\\" oninput=\\"entryEditSync_()\\"></div>";
h+="<div><div class=\\"ee-lbl\\">Address 2</div><input id=\\"eeP_addr2\\" class=\\"ntxn-in\\" value=\\""+esc(P.addr2)+"\\" oninput=\\"entryEditSync_()\\"></div>";
h+="</div>";
}
h+="<div class=\\"je-modal-actions\\"><button class=\\"action-btn\\" onclick=\\"entryEditClose()\\">Cancel</button>";
if(!s.dup)h+="<button id=\\"eeVoidBtn\\" class=\\"action-btn\\" style=\\"color:#B00020;border-color:#B00020\\" onclick=\\"entryVoidFromEdit_()\\">Void entry</button>";
h+="<button class=\\"action-btn action-btn-primary\\" id=\\"eeUpdateBtn\\" onclick=\\""+(s.dup?"entryEditSaveNew()":"entryEditUpdate()")+"\\">"+(s.dup?"Save as new entry":"Update entry")+"</button></div>";
h+="<span id=\\"eeFb\\" style=\\"display:block;margin-top:6px;font-size:12px\\"></span>";
h+="</div></div>";
entryEditMount_(h);
if(typeof ssInit_==="function")ssInit_();
entryEditBal_();
}
function entryEditToggleParty_(){var s=EDIT_STATE;if(!s)return;entryEditSync_();s.showParty=!s.showParty;var pp=document.getElementById("eeParty");if(pp)pp.style.display=s.showParty?"grid":"none";var ar=document.getElementById("eePartyArrow");if(ar)ar.innerHTML=s.showParty?"\\u25be":"\\u25b8";}
function eePatchParty_(){var s=EDIT_STATE;if(!s||!s.party)return;var P=s.party;function st(id,v){var el=document.getElementById(id);if(el&&!el.value&&v)el.value=v;}st("eeP_name",P.name);st("eeP_tin",P.tin);st("eeP_branch",P.branch);st("eeP_last",P.last);st("eeP_first",P.first);st("eeP_middle",P.middle);st("eeP_addr1",P.addr1);st("eeP_addr2",P.addr2);}
function eeBookKey_(source){return {CRB:"crb",CDB:"cdb",SJ:"sj",PJ:"pj"}[String(source||"").toUpperCase()]||"";}
function eeShowVat_(source){var k=eeBookKey_(source);var bm=k&&DATA.bookMeta?DATA.bookMeta[k]:null;if(!bm||!bm.vatTags)return false;if(typeof ntxnIsVatReg_==="function"&&!ntxnIsVatReg_())return false;for(var i=0;i<bm.vatTags.length;i++){if(String(bm.vatTags[i]==null?"":bm.vatTags[i]).trim())return true;}return false;}
function eeAcctColTags_(source,account){var k=eeBookKey_(source);var bm=k&&DATA.bookMeta?DATA.bookMeta[k]:null;if(!bm||!bm.headers||!bm.vatTags)return [];var a=String(account||"").trim(),seen={},out=[];for(var i=0;i<bm.headers.length;i++){if(String(bm.headers[i]==null?"":bm.headers[i]).trim()!==a)continue;var t=String(bm.vatTags[i]==null?"":bm.vatTags[i]).trim();if(!t||seen[t])continue;seen[t]=1;out.push(t);}return out;}
function eeAcctType_(account){var a=String(account||"").trim();var coa=(typeof DATA!=="undefined"&&DATA&&DATA.coa)||[];for(var i=0;i<coa.length;i++){if(String(coa[i].title||"").trim()===a)return String(coa[i].type||"").trim();}return "";}
function eeVatOpts_(source,account,curTag,side){var k=eeBookKey_(source);if(!k)return [];var cur=String(curTag||"").trim();if(/^(input tax|output tax)$/i.test(cur))return [cur];var isSales=(k==="crb"||k==="sj");var base=(typeof ntxnTagOpts_==="function")?ntxnTagOpts_(isSales).slice():(isSales?["VATable","Exempt","Zero-Rated"]:["VATable Goods Other than Capital Goods","VATable Capital Goods","VATable Services","Exempt","Zero-Rated"]);if(cur&&base.indexOf(cur)<0)base.push(cur);return base;}
function eeVatInner_(i,lg,source){var opts=eeVatOpts_(source,lg.a,lg.t,lg.s),cur=lg.t||"";if(!opts.length&&!cur)return "";var h="<select class=\\"ntxn-in\\" id=\\"eeT"+i+"\\" onchange=\\"entryEditSync_()\\"><option value=\\"\\""+(cur?"":" selected")+"></option>";var seen={};for(var o=0;o<opts.length;o++){var t=opts[o];seen[t]=1;h+="<option value=\\""+esc(t)+"\\""+(cur===t?" selected":"")+">"+esc(t)+"</option>";}if(cur&&!seen[cur])h+="<option value=\\""+esc(cur)+"\\" selected>"+esc(cur)+"</option>";return h+"</select>";}
function eeVatCell_(i,lg,source){lg._va=lg.a||"";return "<td id=\\"eeVatTd"+i+"\\">"+eeVatInner_(i,lg,source)+"</td>";}
function entryEditSync_(){
var s=EDIT_STATE;if(!s)return;
var g=function(id){var el=document.getElementById(id);return el?el.value:"";};
s.date=g("eeDate");s.ref=g("eeRef");s.desc=g("eeDesc");
for(var i=0;i<s.legs.length;i++){if(document.getElementById("eeA"+i)){s.legs[i].a=g("eeA"+i);var _dv=g("eeDr"+i),_cv=g("eeCr"+i);if(amtNum_(_dv)!==0){s.legs[i].s="DR";s.legs[i].m=_dv;}else if(amtNum_(_cv)!==0){s.legs[i].s="CR";s.legs[i].m=_cv;}else{s.legs[i].m="";}var _te=document.getElementById("eeT"+i);if(_te)s.legs[i].t=_te.value;}}
if(eeShowVat_(s.source)){for(var vk=0;vk<s.legs.length;vk++){var vlg=s.legs[vk];if((vlg.a||"")!==(vlg._va||"")){var vok=!vlg.a||(DATA.coa&&DATA.coa.some(function(a){return String(a.title).trim()===vlg.a;}));if(vok){vlg.t="";vlg._va=vlg.a||"";var vtd=document.getElementById("eeVatTd"+vk);if(vtd)vtd.innerHTML=eeVatInner_(vk,vlg,s.source);}}}}
if(s.showParty&&!(String(s.source).toUpperCase()==="GJ")){var P=s.party;P.name=g("eeP_name");P.tin=g("eeP_tin");P.branch=g("eeP_branch");P.last=g("eeP_last");P.first=g("eeP_first");P.middle=g("eeP_middle");P.addr1=g("eeP_addr1");P.addr2=g("eeP_addr2");}
entryEditBal_();
}
function entryEditBal_(){var s=EDIT_STATE;if(!s)return;var dr=0,cr=0,n=0;for(var i=0;i<s.legs.length;i++){var a=(s.legs[i].a||"").trim(),m=ntxnRound_(amtNum_(s.legs[i].m));if(!a||m===0)continue;n++;if(s.legs[i].s==="DR")dr+=m;else cr+=m;}dr=ntxnRound_(dr);cr=ntxnRound_(cr);var el=document.getElementById("eeBal");if(!el)return;if(n<2){el.style.color="#6B7280";el.textContent="Add at least two lines.";}else if(Math.abs(dr-cr)<0.005){el.style.color="#1E7E34";el.textContent="BALANCED - DR "+ntxnFmt2_(dr)+" = CR "+ntxnFmt2_(cr);}else{el.style.color="#B00020";el.textContent="Not balanced - DR "+ntxnFmt2_(dr)+" vs CR "+ntxnFmt2_(cr);}}
function eeJSide_(el,i,which){var v=(el.value||"").trim();if(v&&amtNum_(v)){var oid=which==="DR"?"eeCr"+i:"eeDr"+i;var oe=document.getElementById(oid);if(oe)oe.value="";}}
function entryEditAddLeg(){entryEditSync_();EDIT_STATE.legs.push({a:"",s:"DR",m:""});entryEditRender_();}
function entryEditDelLeg(i){entryEditSync_();if(EDIT_STATE.legs.length>2)EDIT_STATE.legs.splice(i,1);entryEditRender_();}
function entryEditUpdate(){
entryEditSync_();var s=EDIT_STATE;var fb=document.getElementById("eeFb");
function err(m){if(fb){fb.style.color="#DC2626";fb.textContent=m;}}
var legs=s.legs.filter(function(l){return (l.a||"").trim()&&ntxnRound_(amtNum_(l.m))!==0;});
if(legs.length<2)return err("Need at least two lines.");
var dr=0,cr=0;for(var i=0;i<legs.length;i++){var m=ntxnRound_(amtNum_(legs[i].m));if(legs[i].s==="DR")dr+=m;else cr+=m;}
if(Math.abs(ntxnRound_(dr-cr))>0.005)return err("Not balanced - fix the debits and credits first.");
if(typeof google==="undefined"||!google.script||!google.script.run||!DATA.spreadsheetId)return err("Update needs the live WebApp.");
var lines=legs.map(function(l){return {account:(l.a||"").trim(),side:l.s,amount:ntxnRound_(amtNum_(l.m)),vatTag:l.t||""};});
var P=s.party||{};
var payload={source:s.source,entryId:s.entryId,row:s.row,date:s.date,ref:s.ref,desc:s.desc,lines:lines,name:P.name,tin:P.tin,branchCode:P.branch,lastName:P.last,firstName:P.first,middleName:P.middle,addr1:P.addr1,addr2:P.addr2};
var btn=document.getElementById("eeUpdateBtn");if(btn)btnBusy_(btn);
if(fb){fb.style.color="#6B7280";fb.textContent="Saving changes...";}
try{google.script.run.withSuccessHandler(entryUpdated_).withFailureHandler(function(e){btnReset_(btn);err("Update failed: "+((e&&e.message)||e));}).updateTransaction(DATA.spreadsheetId,payload);}
catch(e){btnReset_(btn);err(eeDeployMsg_());}
}
function entryUpdated_(res){
var fb=document.getElementById("eeFb"),btn=document.getElementById("eeUpdateBtn");
if(!res||res.__error){btnReset_(btn);if(fb){fb.style.color="#DC2626";fb.textContent="Update failed: "+((res&&res.__error)||"unknown");}return;}
if(fb){fb.style.color="#1E7E34";fb.textContent="Updated. Refreshing...";}
entryRefreshAfter_(function(){entryEditClose();});
}
/* ── Duplicate an entry: load it, then SAVE AS NEW (new Entry ID) - for recurring entries (Meralco/payroll). ── */
function entryDupFromJE_(){var c=JE_CUR_;if(typeof closeJEModal==="function")closeJEModal();showEntryDup_(c.source,c.entryId,c.row,c.date,c.ref);}
function showEntryDup_(source,entryId,row,date,ref){
var pre=null;try{pre=eePrefillFromJe_(source,entryId,row,date,ref);}catch(e){}
if(pre){pre.dup=true;pre.entryId="";pre.ref="";EDIT_STATE=pre;entryEditRender_();}
if(typeof google==="undefined"||!google.script||!google.script.run||!DATA.spreadsheetId){if(!pre)refreshToast_("Duplicate needs the live WebApp (open via the Sheet menu link).",true);return;}
if(!pre){EDIT_STATE={loading:true,dup:true,source:source,entryId:entryId,row:row};entryEditMount_("<div class=\\"je-modal-overlay\\"><div class=\\"je-modal\\" style=\\"max-width:760px\\"><div style=\\"padding:24px;color:#6B7280\\">Loading entry "+esc(entryId)+" to duplicate...</div></div></div>");}
try{google.script.run.withSuccessHandler(entryDupLoaded_).withFailureHandler(function(e){if(!EDIT_STATE||EDIT_STATE.loading){entryEditMount_("");refreshToast_("Could not load entry: "+((e&&e.message)||e),true);}}).getEntryForEdit(DATA.spreadsheetId,{source:source,entryId:entryId,row:row});}
catch(e){if(!pre){entryEditClose();refreshToast_(eeDeployMsg_(),true);}}
}
function entryDupLoaded_(r){
if(!r||r.__error){if(!EDIT_STATE||EDIT_STATE.loading){entryEditClose();refreshToast_("Could not load entry: "+((r&&r.__error)||"unknown"),true);}return;}
var sp=(r.party)||{name:"",tin:"",branch:"",last:"",first:"",middle:"",addr1:"",addr2:""};
if(EDIT_STATE&&!EDIT_STATE.loading){var P=EDIT_STATE.party||(EDIT_STATE.party={});if(!P.name)P.name=sp.name||"";if(!P.tin)P.tin=sp.tin||"";if(!P.branch)P.branch=sp.branch||sp.branchCode||"";if(!P.last)P.last=sp.last||"";if(!P.first)P.first=sp.first||"";if(!P.middle)P.middle=sp.middle||"";if(!P.addr1)P.addr1=sp.addr1||"";if(!P.addr2)P.addr2=sp.addr2||"";eePatchParty_();return;}
if(!EDIT_STATE)return; // user cancelled/closed before the server responded - do not re-open
EDIT_STATE={dup:true,source:r.source,entryId:"",row:"",date:r.date||"",ref:"",desc:r.desc||"",status:"Posted",party:sp,
legs:(r.legs||[]).map(function(l){return {a:l.account||"",s:String(l.side||"DR").toUpperCase(),m:(l.amount!=null?String(l.amount):""),t:l.tag||""};}),showParty:false};
if(!EDIT_STATE.legs.length)EDIT_STATE.legs=[{a:"",s:"DR",m:""},{a:"",s:"CR",m:""}];
entryEditRender_();
}
function entryEditSaveNew(){
entryEditSync_();var s=EDIT_STATE;var fb=document.getElementById("eeFb");
function err(m){if(fb){fb.style.color="#DC2626";fb.textContent=m;}}
var legs=s.legs.filter(function(l){return (l.a||"").trim()&&ntxnRound_(amtNum_(l.m))!==0;});
if(legs.length<2)return err("Need at least two lines.");
var dr=0,cr=0;for(var i=0;i<legs.length;i++){var m=ntxnRound_(amtNum_(legs[i].m));if(legs[i].s==="DR")dr+=m;else cr+=m;}
if(Math.abs(ntxnRound_(dr-cr))>0.005)return err("Not balanced - fix the debits and credits first.");
if(!s.date)return err("Please set a date for the new entry.");
if(typeof google==="undefined"||!google.script||!google.script.run||!DATA.spreadsheetId)return err("Save needs the live WebApp.");
var lines=legs.map(function(l){return {account:(l.a||"").trim(),side:l.s,amount:ntxnRound_(amtNum_(l.m)),vatTag:l.t||""};});
var P=s.party||{};var isGJ=String(s.source).toUpperCase()==="GJ";
var btn=document.getElementById("eeUpdateBtn");if(btn)btnBusy_(btn);
if(fb){fb.style.color="#6B7280";fb.textContent="Saving new entry...";}
var cb=function(res){var f2=document.getElementById("eeFb"),b2=document.getElementById("eeUpdateBtn");if(!res||res.__error){btnReset_(b2);if(f2){f2.style.color="#DC2626";f2.textContent="Save failed: "+((res&&res.__error)||"unknown");}return;}if(f2){f2.style.color="#1E7E34";f2.textContent="Saved new entry "+(res.entryId||"")+". Refreshing...";}entryRefreshAfter_(function(){entryEditClose();});};
var fail=function(e){btnReset_(btn);err("Save failed: "+((e&&e.message)||e));};
try{if(isGJ){google.script.run.withSuccessHandler(cb).withFailureHandler(fail).saveJournal(DATA.spreadsheetId,{date:s.date,ref:s.ref,desc:s.desc,status:"Posted",lines:lines});}
else{google.script.run.withSuccessHandler(cb).withFailureHandler(fail).saveTransaction(DATA.spreadsheetId,{book:s.source,date:s.date,ref:s.ref,desc:s.desc,status:"Posted",lines:lines,name:P.name,tin:P.tin,branchCode:P.branch,lastName:P.last,firstName:P.first,middleName:P.middle,addr1:P.addr1,addr2:P.addr2});}}
catch(e){btnReset_(btn);err(eeDeployMsg_());}
}
function entryVoidFromEdit_(){var s=EDIT_STATE;if(!s)return;entryVoid(s.source,s.entryId,s.row);}
function entryVoid(source,entryId,row){
if(typeof google==="undefined"||!google.script||!google.script.run||!DATA.spreadsheetId){refreshToast_("Void needs the live WebApp.",true);return;}
showConfirm_("Void entry","Void entry <b>"+esc(entryId)+"</b>? It will be excluded from all reports but stays in the books as a record (you can set it back to Posted later).","Void entry",function(){
var _vb=document.getElementById("eeVoidBtn")||document.getElementById("jeVoidBtn");if(_vb)btnBusy_(_vb);
try{google.script.run.withSuccessHandler(function(res){
if(!res||res.__error){if(_vb)btnReset_(_vb);refreshToast_("Void failed: "+((res&&res.__error)||"unknown"),true);return;}
if(typeof closeJEModal==="function")closeJEModal();entryEditClose();
entryRefreshAfter_(function(){refreshToast_("Entry "+entryId+" voided.",false);});
}).withFailureHandler(function(e){if(_vb)btnReset_(_vb);refreshToast_("Void failed: "+((e&&e.message)||e),true);}).voidTransaction(DATA.spreadsheetId,{source:source,entryId:entryId,row:row,status:"Void"});}
catch(e){if(_vb)btnReset_(_vb);refreshToast_(eeDeployMsg_(),true);}
});
}
function entryRepostConfirm_(source,entryId,row){
if(!entryId)return;
if(typeof google==="undefined"||!google.script||!google.script.run||!DATA.spreadsheetId){refreshToast_("Re-post needs the live WebApp.",true);return;}
showConfirm_("Re-post entry","Re-post entry <b>"+esc(entryId)+"</b>? It will be set back to Posted and included in all reports again.","Re-post",function(){
refreshToast_("Re-posting "+entryId+"\\u2026",false);
try{google.script.run.withSuccessHandler(function(res){
if(!res||res.__error){refreshToast_("Re-post failed: "+((res&&res.__error)||"unknown"),true);return;}
entryRefreshAfter_(function(){refreshToast_("Entry "+entryId+" re-posted.",false);});
}).withFailureHandler(function(e){refreshToast_("Re-post failed: "+((e&&e.message)||e),true);}).voidTransaction(DATA.spreadsheetId,{source:source,entryId:entryId,row:row,status:"Posted"});}
catch(e){refreshToast_(eeDeployMsg_(),true);}
});
}
function entryRefreshAfter_(done){
if(typeof google!=="undefined"&&google.script&&google.script.run&&DATA.spreadsheetId){
google.script.run.withSuccessHandler(function(p){
// Reuse the canonical post-write refresh (same as the topbar Update): it re-renders
// the active REPORT tab AND re-enters CHAT mode, so save auto-updates the view in
// every mode (the old path only handled report tabs -> chat-mode edits looked stale).
try{if(typeof applyRefresh_==="function")applyRefresh_(p);
else if(p&&!p.__error){DATA=p;if(typeof buildTopBar==="function")buildTopBar();if(typeof rerenderActiveTab_==="function")rerenderActiveTab_();}
}catch(e){}
if(done)done();
}).withFailureHandler(function(){if(done)done();}).apiRefresh(DATA.spreadsheetId);
}else if(done)done();
}
/* ============ Chart of Accounts manager ============ */
var COA_MGR={accounts:[],noteGroups:[],types:[],lists:{},loaded:false,err:"",edit:null,filter:"all",collapsed:{}};
function buildCOA(){
COA_MGR.err="";
var ccx=document.getElementById("controls");if(ccx)ccx.innerHTML="";
// Instant first paint from DATA.coa (already in memory - same as every other module).
if(DATA&&DATA.coa&&DATA.coa.length){
COA_MGR.accounts=DATA.coa.map(function(a){return {code:a.code,title:a.title,type:a.type,fsGroup:a.fsGroup,normal:a.normal,active:a.active,taxLine:a.taxLine,noteGroup:a.noteGroup,ptTaxLine:a.ptTaxLine,used:null,txnCount:0};});
COA_MGR.loaded=true;
}else{COA_MGR.loaded=false;}
coaShell_();
// Background (non-blocking): usage flags + SoT dropdown lists.
if(typeof google!=="undefined"&&google.script&&google.script.run&&DATA.spreadsheetId){
google.script.run.withSuccessHandler(function(r){if(r&&!r.__error){COA_MGR.accounts=r.accounts||COA_MGR.accounts;COA_MGR.lists=r.lists||{};COA_MGR.noteGroups=r.noteGroups||[];COA_MGR.types=r.types||[];}COA_MGR.loaded=true;if(typeof ACTIVE_TAB!=="undefined"&&ACTIVE_TAB==="coa"){coaSummary_();coaBody_();}}).withFailureHandler(function(){if(typeof ACTIVE_TAB!=="undefined"&&ACTIVE_TAB==="coa")coaBody_();}).coaList(DATA.spreadsheetId);
}else if(!COA_MGR.loaded){COA_MGR.err="Chart of Accounts editing needs the live WebApp (open via the Sheet menu link).";COA_MGR.loaded=true;coaShell_();}
}
function coaShell_(){
var h="<div class=\\"report-card\\" style=\\"max-width:850px;margin:0 auto\\">";
h+="<div style=\\"padding:14px 16px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:12px;flex-wrap:wrap\\"><div style=\\"font-size:17px;font-weight:700;color:var(--navy);flex:1\\">Chart of Accounts</div>";
h+="<div style=\\"position:relative;display:inline-block\\"><svg viewBox=\\"0 0 24 24\\" width=\\"15\\" height=\\"15\\" fill=\\"none\\" stroke=\\"#9CA3AF\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" style=\\"position:absolute;left:10px;top:50%;transform:translateY(-50%);pointer-events:none\\"><circle cx=\\"11\\" cy=\\"11\\" r=\\"7\\"></circle><line x1=\\"21\\" y1=\\"21\\" x2=\\"16.65\\" y2=\\"16.65\\"></line></svg><input type=\\"text\\" id=\\"coaSearch\\" class=\\"ntxn-in\\" style=\\"width:230px;padding-left:32px\\" placeholder=\\"Search code or name...\\" oninput=\\"coaBody_()\\" autocomplete=\\"off\\"></div>";
h+="<button class=\\"ntxn-btn ntxn-save\\" style=\\"padding:7px 16px\\" onclick=\\"coaEditOpen_('')\\">+ Add account</button></div>";
h+="<div id=\\"coaSummary\\" style=\\"padding:12px 16px;border-bottom:1px solid #E5E7EB\\"></div>";
h+="<div id=\\"coaChips\\" style=\\"padding:10px 16px;border-bottom:1px solid #E5E7EB;display:flex;gap:8px;flex-wrap:wrap\\"></div>";
h+="<div id=\\"coaBody\\"></div>";
h+="<div style=\\"font-size:11px;color:#9CA3AF;padding:10px 16px\\">Accounts with transactions cannot be renamed or deleted - deactivate them instead. Changes write to the Chart_of_Accounts sheet (Google Sheets keeps version history).</div>";
h+="</div>";
document.getElementById("report").innerHTML=h;
coaSummary_();coaChips_();coaBody_();
}
function coaIsInactive_(a){return a.active&&String(a.active).toUpperCase()==="NO";}
function coaSummary_(){
var el=document.getElementById("coaSummary");if(!el)return;
var acc=COA_MGR.accounts,total=acc.length,inact=0,unused=0,usageKnown=true;
for(var i=0;i<acc.length;i++){if(coaIsInactive_(acc[i]))inact++;if(acc[i].used===false)unused++;if(acc[i].used===null||acc[i].used===undefined)usageKnown=false;}
function cell(lbl,val){return "<div class=\\"kpi-cell\\"><div class=\\"kpi-label\\">"+lbl+"</div><div class=\\"kpi-big\\">"+val+"</div></div>";}
var unusedDisp=usageKnown?String(unused):"\\u2026";
el.innerHTML="<div class=\\"kpi-grid\\" style=\\"grid-template-columns:repeat(4,1fr)\\">"+cell("Total accounts",total)+cell("Active",total-inact)+cell("Inactive",inact)+cell("Unused",unusedDisp)+"</div>";
}
function coaChips_(){
var el=document.getElementById("coaChips");if(!el)return;var f=COA_MGR.filter||"all";
var chips=[["all","All"],["active","Active"],["inactive","Inactive"],["unused","Unused"]];
var h="";for(var i=0;i<chips.length;i++){var on=f===chips[i][0];h+="<button class=\\"coa-chip"+(on?" on":"")+"\\" onclick=\\"coaSetFilter_('"+chips[i][0]+"')\\">"+chips[i][1]+"</button>";}
h+="<button class=\\"coa-chip\\" style=\\"margin-left:auto\\" onclick=\\"coaToggleAll_()\\">"+coaToggleAllLabel_()+"</button>";
el.innerHTML=h;
}
function coaTypesPresent_(){var s={},out=[];for(var i=0;i<COA_MGR.accounts.length;i++){var t=COA_MGR.accounts[i].type||"Other";if(!s[t]){s[t]=1;out.push(t);}}return out;}
function coaAnyCollapsed_(){var t=coaTypesPresent_();for(var i=0;i<t.length;i++)if(COA_MGR.collapsed[t[i]])return true;return false;}
function coaToggleAllLabel_(){return coaAnyCollapsed_()?"Expand all":"Collapse all";}
function coaToggleAll_(){if(coaAnyCollapsed_()){COA_MGR.collapsed={};}else{var t=coaTypesPresent_();COA_MGR.collapsed={};for(var i=0;i<t.length;i++)COA_MGR.collapsed[t[i]]=true;}coaChips_();coaBody_();}
function coaSetFilter_(f){COA_MGR.filter=f;coaChips_();coaBody_();}
function coaToggleGrp_(t){COA_MGR.collapsed[t]=!COA_MGR.collapsed[t];coaBody_();}
function coaBody_(){
var host=document.getElementById("coaBody");if(!host)return;
if(COA_MGR.err){host.innerHTML="<div style=\\"padding:16px;color:#B00020\\">"+esc(COA_MGR.err)+"</div>";return;}
if(!COA_MGR.loaded&&!COA_MGR.accounts.length){host.innerHTML="<div style=\\"padding:30px;color:#6B7280\\">Loading accounts...</div>";return;}
coaSummary_();
var q=((document.getElementById("coaSearch")||{}).value||"").toLowerCase();var f=COA_MGR.filter||"all";
var rows=[];for(var i=0;i<COA_MGR.accounts.length;i++){var a=COA_MGR.accounts[i];var inactive=coaIsInactive_(a);
if(f==="active"&&inactive)continue;if(f==="inactive"&&!inactive)continue;if(f==="unused"&&a.used!==false)continue;
if(q&&String(a.code).toLowerCase().indexOf(q)<0&&String(a.title).toLowerCase().indexOf(q)<0)continue;
rows.push(a);}
var order=["Asset","Liability","Equity","Revenue","Expense"];var groups={};
for(var j=0;j<rows.length;j++){var t=rows[j].type||"Other";(groups[t]=groups[t]||[]).push(rows[j]);}
var types=[];for(var o=0;o<order.length;o++)if(groups[order[o]])types.push(order[o]);
for(var gk in groups)if(order.indexOf(gk)<0)types.push(gk);
if(!types.length){host.innerHTML="<div style=\\"padding:18px;color:#9CA3AF;text-align:center\\">No accounts match.</div>";return;}
var cg="<colgroup><col style=\\"width:9%\\"><col style=\\"width:30%\\"><col style=\\"width:9%\\"><col style=\\"width:27%\\"><col style=\\"width:10%\\"><col style=\\"width:8%\\"><col style=\\"width:7%\\"></colgroup>";
var h="<table class=\\"coa-tbl\\">"+cg+"<thead><tr><th>Code</th><th>Account Title</th><th>Normal</th><th>FS Note Group</th><th>Active</th><th>Used</th><th></th></tr></thead><tbody>";
for(var ti=0;ti<types.length;ti++){var ty=types[ti];var arr=groups[ty];var collapsed=COA_MGR.collapsed[ty];
var cls=String(ty).toLowerCase();var caret=collapsed?"\\u25b8":"\\u25be";
h+="<tr class=\\"coa-grp\\" onclick=\\"coaToggleGrp_('"+esc(ty)+"')\\"><td colspan=\\"7\\">"+caret+" <span class=\\"acct-type-badge "+esc(cls)+"\\">"+esc(ty)+"</span> <span style=\\"margin-left:6px;color:#6B7280;font-weight:600\\">"+arr.length+" account"+(arr.length===1?"":"s")+"</span></td></tr>";
if(collapsed)continue;
for(var r=0;r<arr.length;r++){var a2=arr[r];var inact2=coaIsInactive_(a2);var cd=esc(String(a2.code));
var pill=inact2?"<span class=\\"coa-pill off\\">Inactive</span>":"<span class=\\"coa-pill on\\">Active</span>";
var usedCell=(a2.used===null||a2.used===undefined)?"<span style=\\"color:#C0C5CE\\">\\u2026</span>":(a2.used?"<span title=\\""+a2.txnCount+" entries\\" style=\\"color:#92600A;font-weight:600\\">Yes</span>":"<span style=\\"color:#9CA3AF\\">-</span>");
h+="<tr class=\\"coa-row\\" onclick=\\"coaEditOpen_('"+cd+"')\\""+(inact2?" style=\\"opacity:.6\\"":"")+"><td>"+esc(String(a2.code))+"</td><td title=\\""+esc(String(a2.title))+"\\">"+esc(String(a2.title))+"</td><td>"+esc(String(a2.normal||""))+"</td><td title=\\""+esc(String(a2.noteGroup||""))+"\\">"+esc(String(a2.noteGroup||""))+"</td><td>"+pill+"</td><td>"+usedCell+"</td><td style=\\"text-align:right;white-space:nowrap\\"><span class=\\"coa-edit-hint\\">Edit \\u203a</span></td></tr>";
}
}
h+="</tbody></table>";host.innerHTML=h;
}
function coaAcctByCode_(code){for(var i=0;i<COA_MGR.accounts.length;i++)if(String(COA_MGR.accounts[i].code)===String(code))return COA_MGR.accounts[i];return null;}
function coaEditOpen_(code){
var a=code?coaAcctByCode_(code):null;
COA_MGR.edit=a?{isNew:false,used:!!a.used,code:a.code,title:a.title,type:a.type||"",fsGroup:a.fsGroup||"",normal:a.normal||"Debit",active:(a.active&&String(a.active).toUpperCase()==="NO")?"No":"Yes",taxLine:a.taxLine||"",noteGroup:a.noteGroup||"",ptTaxLine:a.ptTaxLine||""}:{isNew:true,used:false,code:"",title:"",type:"",fsGroup:"",normal:"Debit",active:"Yes",taxLine:"",noteGroup:"",ptTaxLine:""};
coaEditRender_();
}
function coaEditClose_(e){if(e&&e.target&&e.currentTarget&&e.target!==e.currentTarget)return;var m=document.getElementById("coaEditModal");if(m&&m.parentNode)m.parentNode.removeChild(m);COA_MGR.edit=null;}
function coaDistinct_(key){var seen={},out=[];var c=(DATA&&DATA.coa)||[];for(var i=0;i<c.length;i++){var v=String(c[i][key]==null?"":c[i][key]).trim();if(v&&!seen[v.toLowerCase()]){seen[v.toLowerCase()]=1;out.push(v);}}return out;}
function coaSrc_(key,fallback){var base;if(COA_MGR.lists&&COA_MGR.lists[key]&&COA_MGR.lists[key].length)base=COA_MGR.lists[key].slice();else if(typeof DATA!=="undefined"&&DATA.coaLists&&DATA.coaLists[key]&&DATA.coaLists[key].length)base=DATA.coaLists[key].slice();else base=coaDistinct_(key);var seen={},out=[];function add(a){for(var i=0;i<a.length;i++){var v=String(a[i]);var k=v.toLowerCase();if(v&&!seen[k]){seen[k]=1;out.push(v);}}}add(base);add(fallback||[]);return out;}
/* COA_FIX_ / SET_FIX_ are injected here by buildHTML_ from the server REF_LISTS_ (single source). */
function coaMerge_(base,extra){var seen={},out=[];function add(a){if(!a)return;for(var i=0;i<a.length;i++){var v=String(a[i]).trim();var k=v.toLowerCase();if(v&&!seen[k]){seen[k]=1;out.push(v);}}}add(base);add(extra);return out;}
function coaUIOpts_(key){return coaMerge_(COA_FIX_[key]||[],coaSrc_(key,[]));}
function settingsSrc_(key){return coaMerge_(SET_FIX_[key]||[],coaMerge_((SET_MGR&&SET_MGR.lists&&SET_MGR.lists[key])||[],(typeof DATA!=="undefined"&&DATA.settingsLists&&DATA.settingsLists[key])||[]));}
function coaSS_(id,key,sel,onev){return "<input id=\\""+id+"\\" class=\\"ntxn-in ss-input\\" data-ss=\\"coa:"+key+"\\" value=\\""+esc(sel||"")+"\\""+(onev?" oninput=\\""+onev+"\\" onchange=\\""+onev+"\\"":"")+" placeholder=\\"Type to search...\\" autocomplete=\\"off\\">";}
function ssIn_(id,kind,sel,onev){return "<input id=\\""+id+"\\" class=\\"ntxn-in ss-input\\" data-ss=\\""+kind+"\\" value=\\""+esc(sel||"")+"\\""+(onev?" oninput=\\""+onev+"\\" onchange=\\""+onev+"\\"":"")+" placeholder=\\"Pick or type...\\" autocomplete=\\"off\\">";}
function coaOpts_(key,sel,fallback,withNone){
var src=coaSrc_(key,fallback);
var o=withNone?("<option value=\\"\\""+(!sel?" selected":"")+">(none)</option>"):"";var seen={},has=false;
for(var i=0;i<src.length;i++){var g=String(src[i]);seen[g.toLowerCase()]=1;if(sel&&g===sel)has=true;o+="<option value=\\""+esc(g)+"\\""+(sel===g?" selected":"")+">"+esc(g)+"</option>";}
if(sel&&!has&&!seen[String(sel).toLowerCase()])o+="<option value=\\""+esc(sel)+"\\" selected>"+esc(sel)+" (current)</option>";
return o;
}function coaEditRender_(){
var s=COA_MGR.edit;if(!s)return;var lock=s.used;
function sec(t){return "<div style=\\"grid-column:1/-1;font-size:11px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;color:#2E75B6;margin:6px 0 -2px\\">"+t+"</div>";}
function f(label,inner){return "<div><div class=\\"ee-lbl\\">"+label+"</div>"+inner+"</div>";}
var h="<div class=\\"je-modal-overlay\\" onclick=\\"coaEditClose_(event)\\"><div class=\\"je-modal\\" onclick=\\"event.stopPropagation()\\" style=\\"max-width:600px\\">";
h+="<div class=\\"je-modal-hdr\\"><div class=\\"je-modal-title\\">"+(s.isNew?"Add account":"Edit account"+(s.code?" \\u00b7 "+esc(s.code):""))+"</div><button class=\\"je-modal-close\\" onclick=\\"coaEditClose_(event)\\">\\u00d7</button></div>";
if(lock)h+="<div style=\\"background:#FEF3C7;border:1px solid #F59E0B;color:#92400E;border-radius:8px;padding:8px 12px;margin-bottom:12px;font-size:12px;line-height:1.5\\">This account is used in the books, so <b>Code</b> and <b>Title</b> are locked. You can still change its classification (type, groups, tax lines) and active status. To rename, create a new account or deactivate this one.</div>";
h+="<div style=\\"display:grid;grid-template-columns:1fr 1fr;gap:10px 14px\\">";
h+=sec("Identity");
h+=f("Code", "<input id=\\"coaF_code\\" class=\\"ntxn-in\\" value=\\""+esc(s.code)+"\\""+((lock||!s.isNew)?" disabled":"")+">");
h+=f("Account title","<input id=\\"coaF_title\\" class=\\"ntxn-in\\" value=\\""+esc(s.title)+"\\""+(lock?" disabled":"")+">");
h+=sec("Classification - drives the Trial Balance, Income Statement, Balance Sheet and Notes");
h+=f("Account type",ssIn_("coaF_type","coa:type",s.type,"coaTypeChanged_()"));
h+=f("Normal balance",ssIn_("coaF_normal","coa:normal",s.normal||"Debit","this.setAttribute('data-touched','1')"));
h+=f("FS group (statement face)",coaSS_("coaF_fsg","fsGroup",s.fsGroup));
h+=f("FS note group (Notes to FS)",coaSS_("coaF_note","noteGroup",s.noteGroup));
h+=f("Active",ssIn_("coaF_active","yn",s.active||"Yes"));
h+="<div></div>";
h+=sec("Tax mapping");
h+=f("Tax line (1701 / 1702)",coaSS_("coaF_tax","taxLine",s.taxLine));
h+=f("Tax line (2551Q)",coaSS_("coaF_pt","ptTaxLine",s.ptTaxLine));
h+="</div>";
h+="<div class=\\"je-modal-actions\\" style=\\"display:flex;justify-content:space-between;align-items:center;gap:10px\\"><div style=\\"display:flex;gap:8px;align-items:center\\">";
if(!s.isNew){
h+="<button class=\\"action-btn\\" style=\\"color:#92600A;border-color:#C9A227\\" onclick=\\"coaSetActive('"+esc(s.code)+"','"+(s.active==="No"?"Yes":"No")+"')\\">"+(s.active==="No"?"Activate":"Deactivate")+"</button>";
if(!s.used)h+="<button class=\\"action-btn\\" style=\\"color:#B00020;border-color:#B00020\\" onclick=\\"coaDelete('"+esc(s.code)+"')\\">Delete</button>";
else h+="<span style=\\"font-size:11px;color:#9CA3AF\\">Has transactions - deactivate instead</span>";
}
h+="</div><div style=\\"display:flex;gap:8px\\"><button class=\\"action-btn\\" onclick=\\"coaEditClose_()\\">Cancel</button><button class=\\"action-btn action-btn-primary\\" id=\\"coaSaveBtn\\" onclick=\\"coaEditSave()\\">"+(s.isNew?"Add account":"Save")+"</button></div></div>";
h+="<span id=\\"coaFb\\" style=\\"display:block;margin-top:6px;font-size:12px;color:#DC2626\\"></span></div></div>";
var ex=document.getElementById("coaEditModal");if(ex&&ex.parentNode)ex.parentNode.removeChild(ex);
var d=document.createElement("div");d.id="coaEditModal";d.innerHTML=h;document.body.appendChild(d);if(typeof ssInit_==="function")ssInit_();
}
function coaTypeChanged_(){
var t=((document.getElementById("coaF_type")||{}).value)||"";
var rec=(t==="Asset"||t==="Expense")?"Debit":((t==="Liability"||t==="Equity"||t==="Revenue")?"Credit":"");
var nb=document.getElementById("coaF_normal");
if(nb&&rec&&!nb.getAttribute("data-touched"))nb.value=rec; // auto-recommend unless user already set it manually
}
function coaEditSave(){
var s=COA_MGR.edit;if(!s)return;var fb=document.getElementById("coaFb");
function g(id){var el=document.getElementById(id);return el?String(el.value).trim():"";}
var payload={code:s.isNew?g("coaF_code"):s.code,title:g("coaF_title")||s.title,type:g("coaF_type"),fsGroup:g("coaF_fsg"),normal:g("coaF_normal"),active:g("coaF_active"),noteGroup:g("coaF_note"),taxLine:g("coaF_tax"),ptTaxLine:g("coaF_pt")};
if(!payload.code||!payload.title){if(fb)fb.textContent="Code and title are required.";return;}
var btn=document.getElementById("coaSaveBtn");if(btn)btnBusy_(btn);
var fn=s.isNew?"addAccount":"updateAccount";
function done(r){if(!r||r.__error){btnReset_(btn);if(fb)fb.textContent=(r&&r.__error)||"Save failed.";return;}coaEditClose_();buildCOA();if(typeof entryRefreshAfter_==="function")entryRefreshAfter_(null);}
try{google.script.run.withSuccessHandler(done).withFailureHandler(function(e){btnReset_(btn);if(fb)fb.textContent="Save failed: "+((e&&e.message)||e);})[fn](DATA.spreadsheetId,payload);}
catch(e){if(fb)fb.textContent=eeDeployMsg_();}
}
function coaSetActive(code,active){
var go=function(){
try{google.script.run.withSuccessHandler(function(r){if(!r||r.__error){refreshToast_((r&&r.__error)||"Could not update.",true);return;}if(typeof coaEditClose_==="function")coaEditClose_();buildCOA();if(typeof entryRefreshAfter_==="function")entryRefreshAfter_(null);}).withFailureHandler(function(e){refreshToast_("Failed: "+((e&&e.message)||e),true);}).setAccountActive(DATA.spreadsheetId,{code:code,active:active});}
catch(e){refreshToast_(eeDeployMsg_(),true);}
};
if(active==="No")showConfirm_("Deactivate account","It will be hidden from new transactions but stays in all historical reports.","Deactivate",go);
else go();
}
function coaDelete(code){
showConfirm_("Delete account","Delete account <b>"+esc(code)+"</b> permanently? This is only allowed because it has no transactions.","Delete",function(){
try{google.script.run.withSuccessHandler(function(r){if(!r||r.__error){refreshToast_((r&&r.__error)||"Could not delete.",true);return;}if(typeof coaEditClose_==="function")coaEditClose_();buildCOA();}).withFailureHandler(function(e){refreshToast_("Failed: "+((e&&e.message)||e),true);}).deleteAccount(DATA.spreadsheetId,{code:code});}
catch(e){refreshToast_(eeDeployMsg_(),true);}
});
}
/* ============ Settings manager ============ */
var SET_MGR={lists:{},loaded:false};
function buildSettingsView(){
var cc=document.getElementById("controls");if(cc)cc.innerHTML="";
var st=(DATA&&DATA.settings)?DATA.settings:{};
var L=(SET_MGR&&SET_MGR.lists&&Object.keys(SET_MGR.lists).length)?SET_MGR.lists:((typeof DATA!=="undefined"&&DATA.settingsLists)?DATA.settingsLists:{});
var isCorp=String(st.taxpayerClass||"").toUpperCase().indexOf("NON")===0;
function fld(id,label,val,type){
var opts=L[id];
if(opts&&opts.length){
if(opts.length>12){var dl="";for(var i=0;i<opts.length;i++)dl+="<option value=\\""+esc(opts[i])+"\\">";return "<div><div class=\\"ee-lbl\\">"+esc(label)+"</div><input id=\\"set_"+id+"\\" class=\\"ntxn-in\\" list=\\"setdl_"+id+"\\" value=\\""+esc(String(val==null?"":val))+"\\" placeholder=\\"Type to search...\\" autocomplete=\\"off\\"><datalist id=\\"setdl_"+id+"\\">"+dl+"</datalist></div>";}
var o="",has=false;for(var j=0;j<opts.length;j++){if(String(opts[j])===String(val))has=true;o+="<option value=\\""+esc(opts[j])+"\\""+(String(opts[j])===String(val)?" selected":"")+">"+esc(opts[j])+"</option>";}
if(val&&!has)o+="<option value=\\""+esc(val)+"\\" selected>"+esc(val)+" (current)</option>";
return "<div><div class=\\"ee-lbl\\">"+esc(label)+"</div><select id=\\"set_"+id+"\\" class=\\"ntxn-in\\">"+o+"</select></div>";
}
var _x="";if(id==="tin")_x=" inputmode=\\"numeric\\" maxlength=\\"11\\" oninput=\\"fmtTin_(this);settingsRepDefault_()\\" placeholder=\\"000-000-000\\"";else if(id==="repTin")_x=" inputmode=\\"numeric\\" maxlength=\\"11\\" oninput=\\"fmtTin_(this)\\" placeholder=\\"000-000-000\\"";else if(id==="branchCode")_x=" inputmode=\\"numeric\\" maxlength=\\"5\\" oninput=\\"fmtBranch_(this)\\" onblur=\\"padBranch_(this)\\" placeholder=\\"00000\\"";else if(id==="ownerLast"||id==="ownerFirst"||id==="ownerMiddle")_x=" oninput=\\"settingsRepDefault_()\\"";
return "<div><div class=\\"ee-lbl\\">"+esc(label)+"</div><input id=\\"set_"+id+"\\" class=\\"ntxn-in\\" type=\\""+(type||"text")+"\\" value=\\""+esc(String(val==null?"":val))+"\\""+_x+"></div>";
}
function grp(title){return "<div style=\\"grid-column:1/-1;font-size:13px;font-weight:700;color:var(--navy);margin-top:10px;border-bottom:1px solid #E5E7EB;padding-bottom:4px\\">"+title+"</div>";}
function setSS_(id,label,val){return "<div><div class=\\"ee-lbl\\">"+esc(label)+"</div><input id=\\"set_"+id+"\\" class=\\"ntxn-in ss-input\\" data-ss=\\"set:"+id+"\\" value=\\""+esc(String(val==null?"":val))+"\\" placeholder=\\"Type to search...\\" autocomplete=\\"off\\"></div>";}
var h="<div class=\\"report-card\\" style=\\"max-width:850px;margin:0 auto\\">";
h+="<div style=\\"padding:14px 16px;border-bottom:1px solid #E5E7EB\\"><div style=\\"font-size:17px;font-weight:700;color:var(--navy)\\">Settings</div><div style=\\"font-size:11.5px;color:#6B7280;margin-top:2px\\">Edits write to the Settings sheet. Some fields affect reports and tax forms - change with care.</div></div>";
h+="<div style=\\"padding:14px 16px\\"><div style=\\"display:grid;grid-template-columns:1fr 1fr;gap:10px 14px\\">";
h+=grp("Business");
h+=fld("businessName","Business / Trade name",st.businessName);
h+=fld("principalActivity","Principal activity",st.principalActivity);
h+=setSS_("category","Category / Line of business",st.category);
var _tc=st.taxpayerClass||"Individual";var _indiv=!isCorp;var _ty=_indiv?"Calendar Year":(st.taxableYear||"Calendar Year");
h+="<div><div class=\\"ee-lbl\\">Taxable year</div><select id=\\"set_taxableYear\\" class=\\"ntxn-in\\" onchange=\\"settingsYearLock_()\\""+(_indiv?" disabled":"")+"><option value=\\"Calendar Year\\""+(_ty==="Calendar Year"?" selected":"")+">Calendar Year</option><option value=\\"Fiscal Year\\""+(_ty==="Fiscal Year"?" selected":"")+">Fiscal Year</option></select>"+(_indiv?"<div style=\\"font-size:11px;color:#6B7280;margin-top:4px\\">Individual = laging Calendar Year (locked).</div>":"")+"</div>";
h+="<div><div class=\\"ee-lbl\\">Taxpayer class</div><select id=\\"set_taxpayerClass\\" class=\\"ntxn-in\\" onchange=\\"settingsYearLock_();settingsRepDefault_()\\"><option value=\\"Individual\\""+(_tc==="Individual"?" selected":"")+">Individual</option><option value=\\"Non-Individual\\""+(_tc==="Non-Individual"?" selected":"")+">Non-Individual</option></select></div>";
h+=setSS_("regType","Registration type (e.g. VAT / Non-VAT)",st.regType);
h+=grp("Registration");
h+=fld("tin","TIN",st.tin);
h+=fld("branchCode","Branch code",st.branchCode);
h+=setSS_("rdo","RDO code",st.rdo);
if(isCorp){h+=fld("registeredNameNonInd","Registered name (corporation)",st.registeredNameNonInd);}
else{h+=fld("ownerLast","Owner last name",st.ownerLast);h+=fld("ownerFirst","Owner first name",st.ownerFirst);h+=fld("ownerMiddle","Owner middle name",st.ownerMiddle);}
h+=grp("Address & contact");
h+=fld("addr1","Address line 1",st.addr1);
h+=fld("addr2","Address line 2",st.addr2);
h+=fld("zipCode","ZIP code",st.zipCode);
h+=fld("contactNumber","Contact number",st.contactNumber);
h+=fld("email","Email",st.email);
h+=grp("Authorized representative");
var _oL=(st.ownerLast||"").trim(),_oF=(st.ownerFirst||"").trim(),_oM=(st.ownerMiddle||"").trim();var _ownerNm=isCorp?"":[_oF,_oM,_oL].filter(function(x){return x;}).join(" ");
h+=fld("repName","Representative name",st.repName||_ownerNm);
h+=fld("repTitle","Representative title",st.repTitle||(_ownerNm?"Owner":""));
h+=fld("repTin","Representative TIN",st.repTin||(_ownerNm?(st.tin||""):""));
h+=fld("preparedBy","Prepared by",st.preparedBy||_ownerNm);
if(isCorp){h+=grp("Corporation details");h+=fld("dateOfIncorporation","Date of incorporation",st.dateOfIncorporation,"date");h+=fld("secRegNo","SEC registration no.",st.secRegNo);h+=fld("authorizedShares","Authorized shares",st.authorizedShares);h+=fld("parValue","Par value",st.parValue);h+=fld("issuedShares","Issued shares",st.issuedShares);}
else{h+=grp("Individual details");h+=fld("dateOfBirth","Date of birth",st.dateOfBirth,"date");h+=fld("citizenship","Citizenship",st.citizenship||"Filipino");}
h+=grp("Fiscal year & books");
var _cal=(_ty==="Calendar Year");var _fyDis=(_indiv||_cal);
h+="<div><div class=\\"ee-lbl\\">Fiscal year start month <span title=\\"Naka-lock sa January (Calendar Year). Magagamit lang kapag Non-Individual + Fiscal Year.\\" style=\\"cursor:help;color:#9CA3AF;font-weight:400\\">\\u24D8</span></div><select id=\\"set_fyStartMonth\\" class=\\"ntxn-in\\""+(_fyDis?" disabled":"")+">"+(function(){var mo=["January","February","March","April","May","June","July","August","September","October","November","December"],o="",cur=_fyDis?1:(parseInt(st.fyStartMonth)||1);for(var m=1;m<=12;m++)o+="<option value=\\""+m+"\\""+(m===cur?" selected":"")+">"+mo[m-1]+"</option>";return o;})()+"</select></div>";
h+="<div><div class=\\"ee-lbl\\">Books start date \\u26a0 (affects opening balances / Cash Flow) <span title=\\"Kailan nagsimula ang libro mo dito. Iwanang BLANGKO kung buong taon kang nagre-record (mula simula ng taxable year - hal. Jan 1). Lagyan LANG kung nag-lipat/nag-migrate ka sa gitna ng taon - ito ang petsa ng iyong opening balances. Halimbawa: lumipat ka rito noong Apr 1, 2026 (may balances ka na mula Mar 31), ilagay 04/01/2026.\\" style=\\"cursor:help;color:#9CA3AF;font-weight:400\\">\\u24D8</span></div><input id=\\"set_booksStartDate\\" class=\\"ntxn-in\\" type=\\"date\\" value=\\""+esc(String(st.booksStartDate||""))+"\\"></div>";
h+="</div>";
h+="<div style=\\"margin-top:16px;display:flex;gap:12px;align-items:center\\"><button class=\\"ntxn-btn ntxn-save\\" style=\\"padding:7px 16px\\" id=\\"setSaveBtn\\" onclick=\\"settingsSave()\\">Save settings</button><span id=\\"setFb\\" style=\\"font-size:12px\\"></span></div>";
h+="</div></div>";
document.getElementById("report").innerHTML=h;
if(typeof ssInit_==="function")ssInit_();
settingsRepDefault_();
if(!SET_MGR.loaded&&typeof google!=="undefined"&&google.script&&google.script.run&&DATA.spreadsheetId){
SET_MGR.loaded=true;
google.script.run.withSuccessHandler(function(r){if(r&&!r.__error){SET_MGR.lists=r.lists||{};if(typeof ACTIVE_TAB!=="undefined"&&ACTIVE_TAB==="settings")buildSettingsView();}}).withFailureHandler(function(){}).settingsLists(DATA.spreadsheetId);
}
}
function settingsSave(){
var fb=document.getElementById("setFb");function g(id){var el=document.getElementById("set_"+id);return el?String(el.value):undefined;}
var fields=["businessName","principalActivity","category","taxableYear","taxpayerClass","regType","tin","branchCode","rdo","registeredNameNonInd","ownerLast","ownerFirst","ownerMiddle","addr1","addr2","zipCode","contactNumber","email","repName","repTitle","repTin","preparedBy","dateOfIncorporation","secRegNo","authorizedShares","parValue","issuedShares","dateOfBirth","citizenship","fyStartMonth","booksStartDate"];
var payload={};for(var i=0;i<fields.length;i++){var v=g(fields[i]);if(v!==undefined)payload[fields[i]]=v;}
var oldB=(DATA.settings&&DATA.settings.booksStartDate)||"";
var doSave=function(){
var btn=document.getElementById("setSaveBtn");if(btn)btnBusy_(btn);if(fb){fb.style.color="#6B7280";fb.textContent="Saving...";}
try{google.script.run.withSuccessHandler(function(r){if(!r||r.__error){btnReset_(btn);if(fb){fb.style.color="#DC2626";fb.textContent=(r&&r.__error)||"Save failed.";}return;}if(fb){fb.style.color="#1E7E34";fb.textContent="Saved. Refreshing...";}entryRefreshAfter_(function(){if(ACTIVE_TAB==="settings")buildSettingsView();});}).withFailureHandler(function(e){btnReset_(btn);if(fb){fb.style.color="#DC2626";fb.textContent="Save failed: "+((e&&e.message)||e);}}).updateSettings(DATA.spreadsheetId,payload);}
catch(e){btnReset_(btn);if(fb){fb.style.color="#DC2626";fb.textContent=eeDeployMsg_();}}
};
if(payload.booksStartDate!==undefined&&payload.booksStartDate!==oldB)showConfirm_("Change Books Start Date","Changing the Books Start Date affects opening balances and the Cash Flow statement. Continue?","Continue",doSave);
else doSave();
}
/* ============ Transactions Register (leg-level, all books) ============ */
var TXNREG_STATE={book:"all",q:"",from:"",to:"",page:1,pageSize:100,voided:false};
function buildTxnReg(){var cc=document.getElementById("controls");if(cc)cc.innerHTML="";txnRegShell_();}
function txnRegLegs_(){
var src=TXNREG_STATE.voided?((DATA&&DATA.voidedRows)||[]):((typeof jeView_==="function")?jeView_():((DATA&&DATA.jeRows)||[]));
var book=TXNREG_STATE.book,q=(TXNREG_STATE.q||"").toLowerCase(),from=TXNREG_STATE.from,to=TXNREG_STATE.to;
var out=[];
for(var i=0;i<src.length;i++){var j=src[i];
if(book!=="all"&&String(j.source||"").toUpperCase()!==book)continue;
if(from&&String(j.date||"")<from)continue;
if(to&&String(j.date||"")>to)continue;
if(q){var hay=(String(j.ref||"")+" "+String(j.account||"")+" "+String(j.desc||"")+" "+String(j.name||"")+" "+String(j.entryId||"")+" "+String(j.debit||"")+" "+String(j.credit||"")).toLowerCase();if(hay.indexOf(q)<0)continue;}
out.push(j);
}
out.sort(function(a,b){var da=String(a.date||""),db=String(b.date||"");if(da!==db)return da<db?1:-1;var ea=String(a.entryId||""),eb=String(b.entryId||"");if(ea!==eb)return ea<eb?1:-1;return 0;});
return out;
}
function txnRegShell_(){
var h="<div class=\\"report-card\\" style=\\"max-width:980px;margin:0 auto\\">";
h+="<div style=\\"padding:14px 16px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:10px;flex-wrap:wrap\\"><div style=\\"font-size:17px;font-weight:700;color:var(--navy);flex:1\\">Transactions</div>";
h+="<div style=\\"position:relative;display:inline-block\\"><svg viewBox=\\"0 0 24 24\\" width=\\"15\\" height=\\"15\\" fill=\\"none\\" stroke=\\"#9CA3AF\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" style=\\"position:absolute;left:10px;top:50%;transform:translateY(-50%);pointer-events:none\\"><circle cx=\\"11\\" cy=\\"11\\" r=\\"7\\"></circle><line x1=\\"21\\" y1=\\"21\\" x2=\\"16.65\\" y2=\\"16.65\\"></line></svg><input type=\\"text\\" id=\\"txnSearch\\" class=\\"ntxn-in\\" style=\\"width:230px;padding-left:32px\\" placeholder=\\"Search ref, account, party...\\" value=\\""+esc(TXNREG_STATE.q)+"\\" oninput=\\"txnRegSearch_()\\" autocomplete=\\"off\\"></div>";
h+="<input type=\\"date\\" id=\\"txnFrom\\" class=\\"ntxn-in\\" style=\\"width:148px\\" value=\\""+esc(TXNREG_STATE.from)+"\\" onchange=\\"txnRegDate_()\\" title=\\"From\\">";
h+="<input type=\\"date\\" id=\\"txnTo\\" class=\\"ntxn-in\\" style=\\"width:148px\\" value=\\""+esc(TXNREG_STATE.to)+"\\" onchange=\\"txnRegDate_()\\" title=\\"To\\">";
h+="</div>";
h+="<div id=\\"txnSummary\\" style=\\"padding:12px 16px;border-bottom:1px solid #E5E7EB\\"></div>";
h+="<div id=\\"txnChips\\" style=\\"padding:10px 16px;border-bottom:1px solid #E5E7EB;display:flex;gap:8px;flex-wrap:wrap\\"></div>";
h+="<div id=\\"txnBody\\"></div>";
h+="<div style=\\"font-size:11px;color:#9CA3AF;padding:8px 16px 12px\\">Click any line to open its full journal entry (and edit or void it).</div>";
h+="</div>";
document.getElementById("report").innerHTML=h;
txnRegSummary_();txnRegChips_();txnRegBody_();
}
function txnRegSummary_(){
var el=document.getElementById("txnSummary");if(!el)return;
var legs=txnRegLegs_();var ent={},dr=0,cr=0;
for(var i=0;i<legs.length;i++){if(legs[i].entryId)ent[legs[i].entryId]=1;dr+=legs[i].debit||0;cr+=legs[i].credit||0;}
function cell(lbl,val){return "<div class=\\"kpi-cell\\"><div class=\\"kpi-label\\">"+lbl+"</div><div class=\\"kpi-big\\">"+val+"</div></div>";}
el.innerHTML="<div class=\\"kpi-grid\\" style=\\"grid-template-columns:repeat(4,1fr)\\">"+cell("Transactions",Object.keys(ent).length)+cell("Lines",legs.length)+cell("Total Debit",fmt(dr))+cell("Total Credit",fmt(cr))+"</div>";
}
function txnRegChips_(){
var el=document.getElementById("txnChips");if(!el)return;var f=TXNREG_STATE.book;
var chips=[["all","All"],["CRB","CRB"],["CDB","CDB"],["SJ","SJ"],["PJ","PJ"],["GJ","GJ"]];
var vr=(DATA&&DATA.voidedRows)||[];var vset={};for(var vi=0;vi<vr.length;vi++){if(vr[vi].entryId)vset[vr[vi].entryId]=1;}var vc=Object.keys(vset).length;
var h="";for(var i=0;i<chips.length;i++){h+="<button class=\\"coa-chip"+(f===chips[i][0]?" on":"")+"\\" onclick=\\"txnRegSetBook_('"+chips[i][0]+"')\\">"+chips[i][1]+"</button>";}
h+="<button class=\\"coa-chip\\" style=\\"margin-left:auto;"+(TXNREG_STATE.voided?"background:#B00020;border-color:#B00020;color:#fff":"color:#B00020;border-color:#FBCFE8")+"\\" onclick=\\"txnRegToggleVoided_()\\">"+(TXNREG_STATE.voided?"\\u2715 Hide voided":"Show voided"+(vc?" ("+vc+")":""))+"</button>";
el.innerHTML=h;
}
function txnRegToggleVoided_(){TXNREG_STATE.voided=!TXNREG_STATE.voided;TXNREG_STATE.page=1;txnRegChips_();txnRegSummary_();txnRegBody_();}
function txnRegSetBook_(b){TXNREG_STATE.book=b;TXNREG_STATE.page=1;txnRegChips_();txnRegSummary_();txnRegBody_();}
function txnRegSearch_(){var el=document.getElementById("txnSearch");TXNREG_STATE.q=el?el.value:"";TXNREG_STATE.page=1;txnRegSummary_();txnRegBody_();}
function txnRegDate_(){var f=document.getElementById("txnFrom"),t=document.getElementById("txnTo");TXNREG_STATE.from=f?f.value:"";TXNREG_STATE.to=t?t.value:"";TXNREG_STATE.page=1;txnRegSummary_();txnRegBody_();}
function txnRegPage_(d){TXNREG_STATE.page=(TXNREG_STATE.page||1)+d;txnRegBody_();}
function txnRegBody_(){
var host=document.getElementById("txnBody");if(!host)return;
var legs=txnRegLegs_();
if(!legs.length){host.innerHTML="<div style=\\"padding:24px;color:#9CA3AF;text-align:center\\">"+(TXNREG_STATE.voided?"No voided transactions.":"No transactions match.")+"</div>";return;}
var ps=TXNREG_STATE.pageSize,totalPages=Math.ceil(legs.length/ps),pg=TXNREG_STATE.page||1;
if(pg>totalPages)pg=totalPages;if(pg<1)pg=1;TXNREG_STATE.page=pg;
var startIdx=(pg-1)*ps,slice=legs.slice(startIdx,startIdx+ps);
var cg="<colgroup><col style=\\"width:9%\\"><col style=\\"width:7%\\"><col style=\\"width:9%\\"><col style=\\"width:18%\\"><col style=\\"width:22%\\"><col style=\\"width:15%\\"><col style=\\"width:10%\\"><col style=\\"width:10%\\"></colgroup>";
var h=(TXNREG_STATE.voided?"<div style=\\"padding:8px 16px;font-size:11.5px;color:#B00020;background:#FEF2F2;border-bottom:1px solid #FEE2E2\\">Voided entries are excluded from all reports & tax forms but kept as records (no gaps in the books). Click an entry to <b>re-post</b> it (set back to Posted).</div>":"")+"<table class=\\"coa-tbl\\">"+cg+"<thead><tr><th>Date</th><th>Book</th><th>Ref</th><th>Account</th><th>Description</th><th>Party</th><th class=\\"txn-num\\">Debit</th><th class=\\"txn-num\\">Credit</th></tr></thead><tbody>";
var lastEid=null;
for(var i=0;i<slice.length;i++){var j=slice[i];var eid=String(j.entryId||"");var sep=(i>0&&eid!==lastEid);lastEid=eid;
var badge="<span class=\\"je-source-badge\\" style=\\""+sourceBadgeColor(j.source)+"\\">"+esc(String(j.source||""))+"</span>";
if(TXNREG_STATE.voided)badge+=" <span class=\\"je-source-badge\\" style=\\"background:#FEE2E2;color:#B00020\\">VOIDED</span>";
var dr=(j.debit>0)?fmt(j.debit):"";var cr=(j.credit>0)?fmt(j.credit):"";
var click=TXNREG_STATE.voided?("entryRepostConfirm_('"+escJS(j.source||"")+"','"+escJS(j.entryId||"")+"',"+(parseInt(j.sourceRow,10)||0)+")"):("showJEDetail('"+escJS(j.date||"")+"','"+escJS(j.ref||"")+"','"+escJS(j.entryId||"")+"')");
var rstyle=TXNREG_STATE.voided?" style=\\"opacity:.6\\"":"";
h+="<tr class=\\"coa-row"+(sep?" txn-sep":"")+"\\""+rstyle+" onclick=\\""+click+"\\"><td>"+esc(String(j.date||""))+"</td><td>"+badge+"</td><td>"+esc(String(j.ref||""))+"</td><td title=\\""+esc(String(j.account||""))+"\\">"+esc(String(j.account||""))+"</td><td title=\\""+esc(String(j.desc||""))+"\\">"+esc(String(j.desc||""))+"</td><td>"+esc(String(j.name||""))+"</td><td class=\\"txn-num\\">"+dr+"</td><td class=\\"txn-num\\">"+cr+"</td></tr>";
}
h+="</tbody></table>";
var fromN=startIdx+1,toN=startIdx+slice.length;
h+="<div style=\\"padding:10px 16px;display:flex;align-items:center;gap:12px;font-size:12px;color:#6B7280;border-top:1px solid #F3F4F6\\"><span>Showing "+fromN+"-"+toN+" of "+legs.length+" lines</span>";
if(totalPages>1){h+="<span style=\\"margin-left:auto\\"></span><button class=\\"ntxn-btn\\" style=\\"padding:4px 10px\\""+(pg<=1?" disabled":"")+" onclick=\\"txnRegPage_(-1)\\">Prev</button><span>Page "+pg+" / "+totalPages+"</span><button class=\\"ntxn-btn\\" style=\\"padding:4px 10px\\""+(pg>=totalPages?" disabled":"")+" onclick=\\"txnRegPage_(1)\\">Next</button>";}
h+="</div>";
host.innerHTML=h;
}
function ntxnBookHeader_(s){var sec=ntxnSectionOf_(s.type);var baseType=sec?sec.types[0]:s.type;var L=NTXN_BOOKLBL[baseType]||NTXN_BOOKLBL.receive;var h="<div class=\\"ntxn-card\\"><div style=\\"display:flex;align-items:center;gap:12px\\"><div class=\\"ntxn-menu-ico\\" style=\\"background:"+L.bg+";color:"+L.fg+"\\">"+L.code+"</div><div style=\\"flex:1;min-width:0\\"><div style=\\"font-size:14px;font-weight:700;color:var(--navy)\\">"+L.book+"</div><div style=\\"font-size:11.5px;color:#6B7280\\">"+L.cue+"</div></div><button class=\\"ntxn-btn\\" onclick=\\"ntxnMenuToggle()\\">Change</button></div>";if(sec&&sec.types.length>1){var subL=sec.key==="invoice"?["New invoice","Receive payment"]:["New bill","Pay bill"];h+="<div class=\\"ntxn-seg\\" style=\\"margin-top:10px\\">";sec.types.forEach(function(t,idx){h+="<button class=\\""+(s.type===t?"on":"")+"\\" style=\\"font-size:12px;padding:6px 12px\\" onclick=\\"ntxnSetType('"+t+"')\\">"+subL[idx]+"</button>";});h+="</div>";}h+="</div>";return h;}
function ntxnLines4_(){return [{a:"",t:"",m:""},{a:"",t:"",m:""},{a:"",t:"",m:""},{a:"",t:"",m:""}];}
function ntxnInit_(){if(NEWTXN_STATE)return;NEWTXN_STATE={type:"receive",date:"",ref:"",desc:"",cash:"",cashLines:[{a:"",m:""}],vatMode:"inc",wh:"",whRef:"",charge:"",chargeAcct:"",payBal:0,party:{name:"",tin:"",branch:"",last:"",first:"",middle:"",addr1:"",addr2:"",kind:"noni"},showParty:false,lines:ntxnLines4_(),jlines:[{a:"",dr:"",cr:""},{a:"",dr:"",cr:""},{a:"",dr:"",cr:""},{a:"",dr:"",cr:""}],open:[],openLoaded:false,openErr:"",payAmt:"",paySel:-1};}
function ntxnIsVatReg_(){var rt=String((DATA.settings&&(DATA.settings.regType||DATA.settings.registrationType))||"").toUpperCase();return rt.indexOf("VAT")>=0&&rt.indexOf("NON")<0;}
function ntxnAcctByRe_(re){for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;if(re.test(String(a.title)))return String(a.title).trim();}return "";}
function ntxnVatAcct_(kind){return ntxnAcctByRe_(kind==="output"?/output vat/i:/input vat/i);}
function ntxnWhAcct_(sales){return sales?ntxnAcctByRe_(/cwt receivable|creditable.*withh/i):ntxnAcctByRe_(/ewt payable|expanded.*withh|withholding tax.*payable/i);}
function ntxnControlAcct_(name){return ntxnAcctByRe_(name==="Accounts Receivable"?/accounts receivable/i:/accounts payable/i)||name;}
function ntxnCashList_(){var out=[];for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;if(classifyForCF(a)==="Cash")out.push(String(a.title).trim());}return out;}
function ntxnNoteIsCash_(ng){var t=String(ng||"").toLowerCase().replace(/^[0-9.]+\\s*-\\s*/,"").replace(/&/g," and ").replace(/\\s+/g," ").trim();return t.indexOf("cash")>=0&&t.indexOf("equivalent")>=0;}
function ntxnCashAccts_(){var out=[];for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];if(a.active&&String(a.active).toUpperCase()==="NO")continue;if(ntxnNoteIsCash_(a.noteGroup))out.push(String(a.title).trim());}if(!out.length)out=ntxnCashList_();return out;}function ntxnContactSide_(t){return (t==="spend"||t==="payap"||t==="bill")?"supplier":"customer";}
function ntxnContactList_(side){var all=(DATA.contacts||[]);var nameCount={};for(var i=0;i<all.length;i++){var n=String(all[i].name||"").toUpperCase();if(n)nameCount[n]=(nameCount[n]||0)+1;}var list=[];for(var j=0;j<all.length;j++){var c=all[j];var nm=String(c.name||"").trim();if(!nm)continue;var dup=nameCount[nm.toUpperCase()]>1;var val=(dup&&c.tin)?(nm+" - TIN "+c.tin):nm;var role=(c.side==="supplier")?"Supplier":(c.side==="both")?"Customer/Supplier":"Customer";var lbl=role+(c.tin?" - "+c.tin:"");list.push({value:val,label:lbl,c:c,side:c.side});}list.sort(function(a,b){var am=(a.side===side||a.side==="both")?0:1,bm=(b.side===side||b.side==="both")?0:1;if(am!==bm)return am-bm;return 0;});return list;}function ntxnPickContact(){ntxnSync();var el=document.getElementById("ntxnP_pick");if(!el)return;var v=String(el.value||"").trim();var list=ntxnContactList_(ntxnContactSide_(NEWTXN_STATE.type));var f=null;for(var i=0;i<list.length;i++){if(list[i].value===v){f=list[i].c;break;}}if(!f){for(var j=0;j<list.length;j++){if(String(list[j].c.name||"").toUpperCase()===v.toUpperCase()){f=list[j].c;break;}}}var P=NEWTXN_STATE.party;if(f){P.name=f.regName||f.name||v;P.tin=f.tin||"";P.branch=f.branch||"";P.last=f.last||"";P.first=f.first||"";P.middle=f.middle||"";P.addr1=f.addr1||"";P.addr2=f.addr2||"";P.kind=(P.last||P.first)?"indiv":"noni";}else{P.name=v;}renderNewTxn();}
function ntxnCashTotal_(){var s=NEWTXN_STATE,cfg=NTXN_T[s.type];if(cfg.pay)return ntxnRound_(parseFloat(s.payAmt)||0);var net=0,vatAmt=0,vr=ntxnIsVatReg_();var inc=(s.vatMode!=="ex");for(var k=0;k<s.lines.length;k++){var ln=s.lines[k];var ac=(ln.a||"").trim(),m=ntxnRound_(parseFloat(ln.m)||0);if(!ac||m===0)continue;var tag=vr?(ln.t||""):"";var vatable=vr&&ntxnTagVatable_(tag);if(vatable){if(inc){var nn=ntxnRound_(m/1.12);net+=nn;vatAmt+=ntxnRound_(m-nn);}else{net+=m;vatAmt+=ntxnRound_(m*0.12);}}else{net+=m;}}var wh=ntxnRound_(parseFloat(s.wh)||0);return ntxnRound_(ntxnRound_(net)+ntxnRound_(vatAmt)-wh);}
function ntxnAddCashLine(){ntxnSync();NEWTXN_STATE.cashLines.push({a:"",m:""});renderNewTxn();}
function ntxnDelCashLine(i){ntxnSync();if(NEWTXN_STATE.cashLines.length>1)NEWTXN_STATE.cashLines.splice(i,1);renderNewTxn();}
function ntxnCashSection_(s,cfg){var csSide=cfg.cash||(cfg.pay==="ar"?"DR":(cfg.pay==="ap"?"CR":"DR"));var sideLbl=csSide==="DR"?"Cash/Bank received in":"Cash/Bank paid from";var lines=s.cashLines||[{a:"",m:""}];var showAmt=(lines.length>1)||!!cfg.pay;var h="<div><span class=\\"ntxn-lbl\\">"+sideLbl+(lines.length>1?" (split)":"")+"</span>";for(var i=0;i<lines.length;i++){h+="<div style=\\"display:flex;gap:8px;align-items:center;margin-bottom:6px\\"><input type=\\"text\\" class=\\"ntxn-in ss-input\\" data-ss=\\"cash\\" id=\\"ntxnCash"+i+"\\" value=\\""+esc(lines[i].a||"")+"\\" oninput=\\"ntxnSync()\\" placeholder=\\"Search bank/cash...\\" autocomplete=\\"off\\" style=\\"flex:2\\">";if(showAmt){h+="<input type=\\"text\\" inputmode=\\"decimal\\" class=\\"ntxn-in amt-in\\" id=\\"ntxnCashM"+i+"\\" value=\\""+esc(amtDisp_(lines[i].m||""))+"\\" oninput=\\"ntxnSync()\\" onfocus=\\"amtFocus_(this)\\" onblur=\\"ntxnRoundCell(this)\\" placeholder=\\"0.00\\" style=\\"flex:1\\">";}if(lines.length>1){h+="<button class=\\"ntxn-btn\\" onclick=\\"ntxnDelCashLine("+i+")\\" style=\\"padding:6px 10px\\">x</button>";}h+="</div>";}h+="<button class=\\"ntxn-btn\\" onclick=\\"ntxnAddCashLine()\\">+ Split into another bank</button>";if(lines.length>1&&!cfg.pay){var tot=ntxnCashTotal_();h+="<span style=\\"font-size:11.5px;color:#6B7280;margin-left:10px\\">Split must total "+ntxnFmt2_(tot)+"</span>";}h+="</div>";return h;}
function ntxnTagOpts_(sales){return sales?["VATable","Exempt","Zero-Rated"]:["VATable Goods Other than Capital Goods","VATable Capital Goods","VATable Services","Exempt","Zero-Rated"];}
function ntxnTagVatable_(t){return /^VAT/i.test(String(t||""));}function ntxnColExists_(book,account,side,tag){var bk=String(book||"").toLowerCase();var bm=DATA.bookMeta&&DATA.bookMeta[bk];if(!bm||!bm.headers)return true;for(var i=0;i<bm.headers.length;i++){if(String(bm.headers[i]).trim()===account&&String((bm.drCr||[])[i]||"").trim().toUpperCase()===side&&(!tag||String((bm.vatTags||[])[i]||"").trim()===tag))return true;}return false;}
function ntxnRound_(n){return Math.round((n||0)*100)/100;}
function ntxnFmt2_(n){var x=Number(n||0);if(!isFinite(x))x=0;return x.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});}
function ntxnRoundCell(el){amtBlur_(el);ntxnSync();}
function eeRoundCell(el){amtBlur_(el);entryEditSync_();}
function ntxnComputeRaw_(){
var s=NEWTXN_STATE, cfg=NTXN_T[s.type];
if(cfg.journal){var out=[],dr=0,cr=0,n=0;for(var i=0;i<s.jlines.length;i++){var jl=s.jlines[i];var a=(jl.a||"").trim(),jdr=ntxnRound_(parseFloat(jl.dr)||0),jcr=ntxnRound_(parseFloat(jl.cr)||0);if(!a)continue;if(jdr!==0){out.push({account:a,side:"DR",amount:jdr});dr+=jdr;n++;}else if(jcr!==0){out.push({account:a,side:"CR",amount:jcr});cr+=jcr;n++;}}return {book:"GJ",journal:true,lines:out,dr:ntxnRound_(dr),cr:ntxnRound_(cr),n:n};}
if(cfg.pay){var collect=cfg.pay==="ar";var ctrl=ntxnControlAcct_(collect?"Accounts Receivable":"Accounts Payable");var cashSide=collect?"DR":"CR";var dedSide=collect?"DR":"CR";var pcls=(s.cashLines||[]).filter(function(x){return (x.a||"").trim();});var cashLegs=[],cashSum=0;for(var pci=0;pci<pcls.length;pci++){var pcm=ntxnRound_(parseFloat(pcls[pci].m)||0);if(pcm!==0){cashLegs.push({account:pcls[pci].a.trim(),side:cashSide,amount:pcm});cashSum+=pcm;}}cashSum=ntxnRound_(cashSum);var whAmt=ntxnRound_(parseFloat(s.wh)||0),whAcct=ntxnWhAcct_(collect);var chAcct=(s.chargeAcct||"").trim(),chAmt=chAcct?ntxnRound_(parseFloat(s.charge)||0):0;var settled=ntxnRound_(cashSum+whAmt+chAmt);var out2=[];if(collect){for(var z1=0;z1<cashLegs.length;z1++)out2.push(cashLegs[z1]);if(whAmt>0&&whAcct)out2.push({account:whAcct,side:dedSide,amount:whAmt});if(chAmt>0&&chAcct)out2.push({account:chAcct,side:dedSide,amount:chAmt});if(settled>0)out2.push({account:ctrl,side:"CR",amount:settled});}else{if(settled>0)out2.push({account:ctrl,side:"DR",amount:settled});for(var z2=0;z2<cashLegs.length;z2++)out2.push(cashLegs[z2]);if(whAmt>0&&whAcct)out2.push({account:whAcct,side:dedSide,amount:whAmt});if(chAmt>0&&chAcct)out2.push({account:chAcct,side:dedSide,amount:chAmt});}var pdr=0,pcr=0;for(var z3=0;z3<out2.length;z3++){if(out2[z3].side==="DR")pdr+=out2[z3].amount;else pcr+=out2[z3].amount;}return {book:cfg.book,pay:cfg.pay,lines:out2,dr:ntxnRound_(pdr),cr:ntxnRound_(pcr),amt:settled,ctrl:ctrl,settled:settled,cashSum:cashSum,whAmt:whAmt,chAmt:chAmt,whAcct:whAcct,chAcct:chAcct,balance:ntxnRound_(parseFloat(s.payBal)||0),chargeAmtEntered:ntxnRound_(parseFloat(s.charge)||0)};}
// receive/spend/invoice/bill
var lns=[],net=0,vatAmt=0;var vr=ntxnIsVatReg_();var inc=(s.vatMode!=="ex");
for(var k=0;k<s.lines.length;k++){var ln=s.lines[k];var ac=(ln.a||"").trim(),m2=ntxnRound_(parseFloat(ln.m)||0);if(!ac||m2===0)continue;var tag=vr?(ln.t||""):"";var vatable=vr&&ntxnTagVatable_(tag);var lnNet,lnVat;if(vatable){if(inc){lnNet=ntxnRound_(m2/1.12);lnVat=ntxnRound_(m2-lnNet);}else{lnNet=m2;lnVat=ntxnRound_(m2*0.12);}}else{lnNet=m2;lnVat=0;}lns.push({account:ac,tag:tag,amount:lnNet});net+=lnNet;vatAmt+=lnVat;}
net=ntxnRound_(net);vatAmt=ntxnRound_(vatAmt);
var wh=ntxnRound_(parseFloat(s.wh)||0);
var lineSide=cfg.line, vatAcct=ntxnVatAcct_(cfg.vat), whAcct=ntxnWhAcct_(cfg.sales);
var gross=ntxnRound_(net+vatAmt);
var controlAmt=ntxnRound_(gross-wh); // cash (receive/spend) or AR/AP (invoice/bill)
var out3=[];
var controlAcct = cfg.control?ntxnControlAcct_(cfg.control):((s.cashLines&&s.cashLines[0]&&s.cashLines[0].a)||s.cash||"");
if(cfg.control){if(controlAcct&&controlAmt!==0)out3.push({account:controlAcct,side:cfg.controlSide,amount:controlAmt});}
else{var rcls=(s.cashLines||[]).filter(function(x){return (x.a||"").trim();});if(rcls.length<=1){var rca=rcls.length?rcls[0].a.trim():(s.cash||"");if(rca&&controlAmt!==0)out3.push({account:rca,side:cfg.cash,amount:controlAmt});}else{for(var rci=0;rci<rcls.length;rci++){var rcm=ntxnRound_(parseFloat(rcls[rci].m)||0);if(rcm!==0)out3.push({account:rcls[rci].a.trim(),side:cfg.cash,amount:rcm});}}}
if(wh>0&&whAcct){var whSide=cfg.sales?"DR":"CR";out3.push({account:whAcct,side:whSide,amount:wh});}
for(var j=0;j<lns.length;j++)out3.push({account:lns[j].account,side:lineSide,amount:lns[j].amount,vatTag:lns[j].tag});
if(vatAmt>0&&vatAcct)out3.push({account:vatAcct,side:lineSide,amount:vatAmt,vatTag:(cfg.vat==="output"?"Output Tax":"Input Tax")});
var dr3=0,cr3=0;for(var x=0;x<out3.length;x++){if(out3[x].side==="DR")dr3+=out3[x].amount;else cr3+=out3[x].amount;}
return {book:cfg.book,sales:cfg.sales,lines:out3,dr:ntxnRound_(dr3),cr:ntxnRound_(cr3),net:net,vatAmt:vatAmt,wh:wh,whAcct:whAcct,gross:gross,nLines:lns.length,controlAcct:controlAcct};
}
function ntxnCompute_(){var r=ntxnComputeRaw_();if(r&&r.lines){var dr=0,cr=0;for(var i=0;i<r.lines.length;i++){var L=r.lines[i];if(L.amount<0){L.side=(L.side==="DR")?"CR":"DR";L.amount=ntxnRound_(-L.amount);}if(L.side==="DR")dr+=L.amount;else cr+=L.amount;}r.dr=ntxnRound_(dr);r.cr=ntxnRound_(cr);}return r;}
function fmtTin_(el){var d=(el.value||"").replace(/[^0-9]/g,"").slice(0,9);var p=[];if(d.length>0)p.push(d.slice(0,3));if(d.length>3)p.push(d.slice(3,6));if(d.length>6)p.push(d.slice(6,9));el.value=p.join("-");}
function fmtBranch_(el){el.value=(el.value||"").replace(/[^0-9]/g,"").slice(0,5);}
function padBranch_(el){var d=(el.value||"").replace(/[^0-9]/g,"").slice(0,5);el.value=d?("00000"+d).slice(-5):"";}
function ntxnFmtTin(el){fmtTin_(el);ntxnSync();}
function ntxnFmtBranch(el){fmtBranch_(el);ntxnSync();}
function ntxnPadBranch(el){padBranch_(el);ntxnSync();}
function ntxnSetType(t){ntxnInit_();NEWTXN_STATE.type=t;NEWTXN_STATE.lines=ntxnLines4_();NEWTXN_STATE.jlines=[{a:"",dr:"",cr:""},{a:"",dr:"",cr:""},{a:"",dr:"",cr:""},{a:"",dr:"",cr:""}];NEWTXN_STATE.wh="";NEWTXN_STATE.whRef="";NEWTXN_STATE.charge="";NEWTXN_STATE.chargeAcct="";NEWTXN_STATE.payBal=0;NEWTXN_STATE.payAmt="";NEWTXN_STATE.paySel=-1;NEWTXN_STATE.cashLines=[{a:"",m:""}];NEWTXN_STATE.party={name:"",tin:"",branch:"",last:"",first:"",middle:"",addr1:"",addr2:"",kind:"noni"};ntxnSeedCash_();NEWTXN_STATE.open=[];NEWTXN_STATE.openLoaded=false;NEWTXN_STATE.showParty=false;if(NTXN_T[t].pay)ntxnLoadOpen_();renderNewTxn();}
function ntxnSetVatMode(m){NEWTXN_STATE.vatMode=m;renderNewTxn();}
function ntxnComposeName_(){var P=NEWTXN_STATE.party;var ln=(P.last||"").trim().toUpperCase(),fn=(P.first||"").trim().toUpperCase(),mn=(P.middle||"").trim().toUpperCase();return (ln+((fn||mn)?", ":"")+fn+(mn?" "+mn:"")).trim();}
function ntxnNameParts(){ntxnSync();var nm=ntxnComposeName_();var pv=document.getElementById("ntxnP_compose");if(pv)pv.textContent=nm?("Saved as: "+nm):"";var pk=document.getElementById("ntxnP_pick");if(pk)pk.value=nm;}
function ntxnSetPartyKind(k){ntxnSync();var P=NEWTXN_STATE.party;P.kind=k;if(k==="noni"){P.last="";P.first="";P.middle="";}var w=document.getElementById("ntxnPartyWrap");if(w){w.innerHTML=ntxnPartyPanel_(NEWTXN_STATE,NTXN_T[NEWTXN_STATE.type]);}else{renderNewTxn();}}
function statShare_(base,vatableNet,totalNet){
base=base||{};var m=base.method||"whole";
if(m==="perhead"){var dn=parseFloat(base.diners)||1,sn=parseFloat(base.seniors)||0;return dn?(totalNet*sn/dn):0;}
if(m==="memc"){return (parseFloat(base.memcNet)||0)*(parseFloat(base.seniors)||0);}
if(m==="specific")return parseFloat(base.specNet)||0;
return vatableNet;
}
function resiboDerive_(p){
var inc=(p.mode!=="ex"), vr=!!p.vatReg, rnd=function(n){return Math.round((n||0)*100)/100;};
var salesAcct=p.salesAccount||"Sales";
var vatableNet=0,exemptNet=0,zeroNet=0,vatRaw=0;
var lines=p.lines||[];
for(var i=0;i<lines.length;i++){
var L=lines[i],amt=rnd((parseFloat(L.qty)||0)*(parseFloat(L.price)||0));if(!amt)continue;
var vc=L.vatClass||"VATable",net,lvat;
if(vr&&/^VAT/i.test(vc)){if(inc){net=rnd(amt/1.12);lvat=rnd(amt-net);}else{net=amt;lvat=rnd(amt*0.12);}vatableNet+=net;vatRaw+=lvat;}
else if(vc==="Zero-Rated"||vc==="Zero"){zeroNet+=amt;}
else {exemptNet+=amt;}
}
vatableNet=rnd(vatableNet);exemptNet=rnd(exemptNet);zeroNet=rnd(zeroNet);vatRaw=rnd(vatRaw);
var totalNet=rnd(vatableNet+exemptNet+zeroNet);
var disc=p.discount||{type:"none"};
var discount=0,discAcct="",outVat=vatRaw,dispVatable=vatableNet,dispExempt=exemptNet,shareNet=0,salesCredit=totalNet;
if(disc.type==="regular"){
var D=(disc.mode==="rate")?rnd(vatableNet*(parseFloat(disc.val)||0)/100):rnd(parseFloat(disc.val)||0);if(D>vatableNet)D=vatableNet;
discount=D;dispVatable=rnd(vatableNet-D);outVat=rnd(dispVatable*0.12);salesCredit=rnd(totalNet-D); // net method: sales reduced
} else if(disc.type==="statutory"){
var rate=parseFloat(disc.rate)||0;
shareNet=rnd(statShare_(disc.base,vatableNet,totalNet));if(shareNet>vatableNet)shareNet=vatableNet;
var raw=rnd(shareNet*rate/100);if(disc.cap&&raw>disc.cap)raw=disc.cap;
discount=rnd(raw);discAcct=disc.acct||"Statutory Discount Expense";
if(disc.vatTreat==="exempt"){outVat=rnd((vatableNet-shareNet)*0.12);dispVatable=rnd(vatableNet-shareNet);dispExempt=rnd(exemptNet+shareNet);}
else {outVat=rnd((vatableNet-discount)*0.12);}
}
outVat=rnd(outVat);
var legs=[],dr=0,cr=0;
function add(a,s,t,m){m=rnd(m);if(!a||!m)return;legs.push({account:a,side:s,vatTag:t,amount:m});if(s==="DR")dr+=m;else cr+=m;}
if(dispVatable>0)add(salesAcct,"CR",vr?"VATable":"",dispVatable);
if(dispExempt>0)add(salesAcct,"CR",vr?"Exempt":"",dispExempt);
if(zeroNet>0)add(salesAcct,"CR",vr?"Zero-Rated":"",zeroNet);
if(outVat>0)add("Output VAT","CR","Output Tax",outVat);
if(discount>0&&disc.type==="statutory")add(discAcct,"DR","",discount);
var saleCr=rnd(dispVatable+dispExempt+zeroNet+outVat);
var cwt=(p.whtMode==="rate")?rnd(totalNet*(parseFloat(p.whtVal)||0)/100):rnd(parseFloat(p.whtVal)||0);if(cwt>totalNet)cwt=totalNet;
var fees=p.fees||[],feeSum=0;for(var f=0;f<fees.length;f++)feeSum+=rnd(parseFloat(fees[f].amount)||0);feeSum=rnd(feeSum);
var due=rnd(saleCr-(disc.type==="statutory"?discount:0)-cwt);
var charge=(p.sale==="charge"),drMain=charge?"Accounts Receivable":(p.cashAccount||"Cash in Bank");
if(charge){add(drMain,"DR","",due);if(cwt>0)add("Creditable Withholding Tax","DR","",cwt);}
else {var netCash=rnd(due-feeSum);add(drMain,"DR","",netCash);for(var g=0;g<fees.length;g++){var fa=rnd(parseFloat(fees[g].amount)||0);if(fa>0)add(fees[g].account||"Charges","DR","",fa);}if(cwt>0)add("Creditable Withholding Tax","DR","",cwt);}
dr=rnd(dr);cr=rnd(cr);
return {legs:legs,dr:dr,cr:cr,totalNet:totalNet,vat:outVat,vatRaw:vatRaw,discount:rnd(discount),discAcct:discAcct,shareNet:rnd(shareNet),cwt:cwt,feeSum:feeSum,due:due,dispVatable:rnd(dispVatable),dispExempt:rnd(dispExempt),zeroNet:zeroNet,balanced:Math.abs(dr-cr)<0.005,book:charge?"SJ":"CRB"};
}
function gastosDerive_(p){
var inc=(p.mode!=="ex"), vr=!!p.vatReg, rnd=function(n){return Math.round((n||0)*100)/100;};
var exAcct=p.expenseAccount||"Purchases";
var TAGMAP={Goods:"VATable Goods Other than Capital Goods",Capital:"VATable Capital Goods",Services:"VATable Services",Exempt:"Exempt",Zero:"Zero-Rated"};
var byTag={},inputVat=0,totalNet=0;
var lines=p.lines||[];
for(var i=0;i<lines.length;i++){
var L=lines[i],amt=rnd((parseFloat(L.qty)||0)*(parseFloat(L.price)||0));if(!amt)continue;
var vc=L.vatClass||"Goods",vatable=vr&&(vc==="Goods"||vc==="Capital"||vc==="Services"),net,ivat;
if(vatable){if(inc){net=rnd(amt/1.12);ivat=rnd(amt-net);}else{net=amt;ivat=rnd(amt*0.12);}inputVat+=ivat;}
else {net=amt;ivat=0;}
var tg=vr?(TAGMAP[vc]||""):"";byTag[tg]=rnd((byTag[tg]||0)+net);totalNet+=net;
}
inputVat=rnd(inputVat);totalNet=rnd(totalNet);
var gross=rnd(totalNet+inputVat);
var ewt=(p.whtMode==="rate")?rnd(totalNet*(parseFloat(p.whtVal)||0)/100):rnd(parseFloat(p.whtVal)||0);if(ewt>gross)ewt=gross;
var payable=rnd(gross-ewt),charge=(p.sale==="charge"),crMain=charge?"Accounts Payable":(p.cashAccount||"Cash in Bank");
var legs=[],dr=0,cr=0;
function add(a,s,t,m){m=rnd(m);if(!a||!m)return;legs.push({account:a,side:s,vatTag:t,amount:m});if(s==="DR")dr+=m;else cr+=m;}
for(var k in byTag)add(exAcct,"DR",k,byTag[k]);
if(inputVat>0)add("Input VAT","DR","Input Tax",inputVat);
add(crMain,"CR","",payable);
if(ewt>0)add("EWT Payable","CR","",ewt);
dr=rnd(dr);cr=rnd(cr);
return {legs:legs,dr:dr,cr:cr,totalNet:totalNet,inputVat:inputVat,gross:gross,ewt:ewt,payable:payable,balanced:Math.abs(dr-cr)<0.005,book:charge?"PJ":"CDB"};
}
function ntxnToggleParty(){ntxnSync();var s=NEWTXN_STATE;s.showParty=!s.showParty;var w=document.getElementById("ntxnPartyWrap");if(w){w.style.display=s.showParty?"block":"none";var a=document.getElementById("ntxnPartyArrow");if(a)a.innerHTML=s.showParty?String.fromCharCode(9662):String.fromCharCode(9656);}else{renderNewTxn();}}
function settingsYearLock_(){var tc=document.getElementById("set_taxpayerClass"),ty=document.getElementById("set_taxableYear"),fy=document.getElementById("set_fyStartMonth");if(!ty)return;var indiv=tc?(String(tc.value||"").toUpperCase().indexOf("NON")!==0):true;if(indiv){ty.value="Calendar Year";ty.disabled=true;}else{ty.disabled=false;}var cal=/calendar/i.test(String(ty.value||""));if(fy){if(indiv||cal){fy.value="1";fy.disabled=true;}else{fy.disabled=false;}}}
function settingsRepDefault_(){var tcE=document.getElementById("set_taxpayerClass");if(tcE&&String(tcE.value||"").toUpperCase().indexOf("NON")===0)return;function gv(id){var e=document.getElementById(id);return e?String(e.value||"").trim():"";}var nm=[gv("set_ownerFirst"),gv("set_ownerMiddle"),gv("set_ownerLast")].filter(function(x){return x;}).join(" ");var tin=gv("set_tin");function setIfBlank(id,val){var e=document.getElementById(id);if(e&&val&&!String(e.value||"").trim())e.value=val;}if(nm){setIfBlank("set_repName",nm);setIfBlank("set_repTitle","Owner");setIfBlank("set_preparedBy",nm);}if(tin)setIfBlank("set_repTin",tin);}
function ntxnAddLine(){ntxnSync();NEWTXN_STATE.lines.push({a:"",t:"",m:""});renderNewTxn();}
function ntxnAddJLine(){ntxnSync();NEWTXN_STATE.jlines.push({a:"",dr:"",cr:""});renderNewTxn();}
function ntxnJSide_(el,i,which){var v=(el.value||"").trim();if(v&&parseFloat(v.replace(/,/g,""))){var oid=which==="dr"?"ntxnJCr"+i:"ntxnJDr"+i;var oe=document.getElementById(oid);if(oe)oe.value="";}}
function ntxnSync(){
if(!NEWTXN_STATE)return;
var g=function(id){var e=document.getElementById(id);return e?e.value:"";};
if(document.getElementById("ntxnDate"))NEWTXN_STATE.date=g("ntxnDate");
if(document.getElementById("ntxnRef"))NEWTXN_STATE.ref=g("ntxnRef");
if(document.getElementById("ntxnDesc"))NEWTXN_STATE.desc=g("ntxnDesc");
if(document.getElementById("ntxnCash"))NEWTXN_STATE.cash=g("ntxnCash");
if(NEWTXN_STATE.cashLines){for(var ci=0;ci<NEWTXN_STATE.cashLines.length;ci++){var ce=document.getElementById("ntxnCash"+ci);if(ce)NEWTXN_STATE.cashLines[ci].a=ce.value;var cm=document.getElementById("ntxnCashM"+ci);if(cm)NEWTXN_STATE.cashLines[ci].m=cm.value.replace(/,/g,"");}}
if(document.getElementById("ntxnWh"))NEWTXN_STATE.wh=g("ntxnWh").replace(/,/g,"");
if(document.getElementById("ntxnPayAmt"))NEWTXN_STATE.payAmt=g("ntxnPayAmt").replace(/,/g,"");
if(document.getElementById("ntxnWhRef"))NEWTXN_STATE.whRef=g("ntxnWhRef");
if(document.getElementById("ntxnCharge"))NEWTXN_STATE.charge=g("ntxnCharge").replace(/,/g,"");
if(document.getElementById("ntxnChargeAcct"))NEWTXN_STATE.chargeAcct=g("ntxnChargeAcct");
var P=NEWTXN_STATE.party;["tin","branch","last","first","middle","addr1","addr2"].forEach(function(f){var e=document.getElementById("ntxnP_"+f);if(e)P[f]=e.value;});var pn0=document.getElementById("ntxnP_name");if(pn0)P.name=pn0.value;var pick0=document.getElementById("ntxnP_pick");if(pick0)P.name=pick0.value;if(P.kind==="indiv")P.name=ntxnComposeName_();
for(var i=0;i<NEWTXN_STATE.lines.length;i++){var ea=document.getElementById("ntxnA"+i),et=document.getElementById("ntxnT"+i),em=document.getElementById("ntxnM"+i);if(ea)NEWTXN_STATE.lines[i].a=ea.value;if(et)NEWTXN_STATE.lines[i].t=et.value;if(em)NEWTXN_STATE.lines[i].m=em.value.replace(/,/g,"");}
for(var j=0;j<NEWTXN_STATE.jlines.length;j++){var ja=document.getElementById("ntxnJA"+j),jdr=document.getElementById("ntxnJDr"+j),jcr=document.getElementById("ntxnJCr"+j);if(ja)NEWTXN_STATE.jlines[j].a=ja.value;if(jdr)NEWTXN_STATE.jlines[j].dr=jdr.value.replace(/,/g,"");if(jcr)NEWTXN_STATE.jlines[j].cr=jcr.value.replace(/,/g,"");}
ntxnUpdatePreview_();
for(var b=0;b<NEWTXN_STATE.lines.length;b++){var el=document.getElementById("ntxnA"+b);if(el){var vv=(el.value||"").trim();var ok=!vv||DATA.coa.some(function(a){return String(a.title).trim()===vv;});el.style.borderColor=ok?"":"#C9A227";}}
}
function ntxnSeedCash_(){var s=NEWTXN_STATE;if(!s.cashLines||!s.cashLines.length)s.cashLines=[{a:"",m:""}];if(!s.cashLines[0].a){var bk=NTXN_T[s.type].book;var last=(bk==="CRB"||bk==="CDB")?(NTXN_LASTCASH[bk]||""):"";var cl=ntxnCashAccts_();s.cashLines[0].a=last||cl[0]||"";}}
function buildNewTxn(){
ntxnInit_();
if(!NEWTXN_STATE.date){var ds=DATA.jeRows.map(function(e){return e.date;}).filter(function(d){return d&&d.length>=10;}).sort();NEWTXN_STATE.date=ds[ds.length-1]||(new Date().toISOString().substring(0,10));}
ntxnSeedCash_();
if(NTXN_T[NEWTXN_STATE.type].pay&&!NEWTXN_STATE.openLoaded)ntxnLoadOpen_();
document.getElementById("controls").innerHTML="";
renderNewTxn();
}
function ntxnStyle_(){
var h="<style>";
h+=".ntxn-wrap{max-width:var(--report-width);margin:0 auto;}";
h+=".ntxn-card{background:#fff;border:1px solid #E5E7EB;border-radius:10px;padding:14px 16px;margin-bottom:12px;}";
h+=".ntxn-step{font-size:11px;font-weight:700;color:var(--navy);letter-spacing:.4px;text-transform:uppercase;border-bottom:2px solid var(--gold);padding-bottom:5px;margin-bottom:10px;}";
h+=".ntxn-seg{display:flex;flex-wrap:wrap;gap:6px;}";
h+=".ntxn-seg button{padding:8px 14px;border:1px solid #D0D0D0;border-radius:8px;background:#fff;color:var(--navy);font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;}";
h+=".ntxn-seg button:hover:not(.on){border-color:var(--gold);}";
h+=".ntxn-seg button.on{background:var(--navy);color:#fff;border-color:var(--navy);}";
h+=".ntxn-lbl{font-size:10px;font-weight:700;color:#999;letter-spacing:.5px;text-transform:uppercase;display:block;margin-bottom:3px;}";
h+=".ntxn-in{padding:6px 9px;border:1px solid #D0D0D0;border-radius:6px;font-size:13px;color:var(--navy);font-family:inherit;box-sizing:border-box;width:100%;}";
h+=".ntxn-in:hover{border-color:var(--gold);}";
h+=".ntxn-in:focus{border-color:var(--gold);outline:none;box-shadow:0 0 0 2px rgba(201,162,39,.22);}";
h+="input.ntxn-in[type=number]{text-align:right;font-variant-numeric:tabular-nums;}";
h+=".ntxn-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;}";
h+=".ntxn-grid2{display:grid;grid-template-columns:1fr 1fr;gap:10px;}";
h+=".ntxn-tbl{width:100%;border-collapse:collapse;font-size:13px;margin-top:4px;}";
h+=".ntxn-tbl th{text-align:left;font-size:10px;color:#6B7280;letter-spacing:.4px;padding:4px 6px;border-bottom:2px solid #E5E7EB;text-transform:uppercase;}";
h+=".ntxn-tbl td{padding:4px 6px;border-bottom:1px solid #F0F1F3;}";
h+=".ntxn-btn{padding:6px 14px;border:1px solid #D0D0D0;border-radius:6px;background:#fff;color:var(--navy);font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit;}";
h+=".ntxn-btn:hover:not(:disabled){border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.28);}";
h+=".ntxn-btn:active:not(:disabled){transform:scale(.98);}";
h+=".ntxn-save{background:var(--navy);color:#fff;border-color:var(--navy);}";
h+=".ntxn-save:hover{background:#152844;border-color:var(--gold);box-shadow:0 0 0 2px rgba(201,162,39,.45);}.ntxn-save:disabled{opacity:.5;cursor:not-allowed;}";
h+=".ntxn-prev table{width:100%;table-layout:fixed;border-collapse:collapse;font-size:12.5px;}";
h+=".ntxn-prev td{padding:4px 8px;border-bottom:1px solid #F0F1F3;}.ntxn-prev .num{text-align:right;font-variant-numeric:tabular-nums;}";
h+=".ntxn-bal{margin-top:8px;font-size:13px;font-weight:700;padding:8px 12px;border-radius:8px;border:1px solid #E5E7EB;}";
h+=".ntxn-ok{color:#1E7E34;border-color:#1E7E34;}.ntxn-no{color:#92600A;border-color:#C9A227;}";
h+=".ntxn-warn{font-size:11.5px;color:#92600A;background:#FFF8E6;border:1px solid #C9A227;border-radius:6px;padding:6px 10px;margin-top:8px;}";
h+=".ntxn-fb{font-size:12.5px;margin-top:8px;min-height:16px;}";
h+=".ntxn-open{width:100%;border-collapse:collapse;font-size:12.5px;}";
h+=".ntxn-open th{text-align:left;font-size:10px;color:#6B7280;padding:5px 8px;border-bottom:2px solid #E5E7EB;text-transform:uppercase;}";
h+=".ntxn-open td{padding:6px 8px;border-bottom:1px solid #F0F1F3;cursor:pointer;}";
h+=".ntxn-open tr.on td{background:#FFF8E6;}";
h+="</style>";
return h;
}
function renderNewTxn(){
var s=NEWTXN_STATE, cfg=NTXN_T[s.type], vatReg=ntxnIsVatReg_();
var h="";
h+=ntxnStyle_();
h+="<div class=\\"ntxn-wrap\\">";
// Book header (book is chosen from the "+ New" topbar menu); Invoice/Bill keep the New | payment sub-tab
h+=ntxnBookHeader_(s);
if(cfg.pay){h+=ntxnRenderPay_(s,cfg);}else if(cfg.journal){h+=ntxnRenderJournal_(s);}else{h+=ntxnRenderStd_(s,cfg,vatReg);}
// preview + save
h+="<div class=\\"ntxn-card\\"><div class=\\"ntxn-step\\">"+(cfg.journal?"Review & save":"Step 3 - Review and save")+"</div><div id=\\"ntxnPreview\\" class=\\"ntxn-prev\\""+(cfg.journal?" style=\\"display:none\\"":"")+"></div>";
h+="<div style=\\"margin-top:12px;display:flex;gap:12px;align-items:center;flex-wrap:wrap\\"><button class=\\"ntxn-btn\\" onclick=\\"ntxnClear()\\">Clear</button><button class=\\"ntxn-btn ntxn-save\\" id=\\"ntxnSaveBtn\\" onclick=\\"ntxnSave()\\">Save to Books</button><span id=\\"ntxnFb\\" class=\\"ntxn-fb\\"></span></div></div>";
h+="</div>";
document.getElementById("report").innerHTML=h;
ssInit_();
ntxnUpdatePreview_();
}
function ntxnPartyPanel_(s,cfg){
var P=s.party,req=(s.type==="bill");
if(!P.kind)P.kind="noni";
var indiv=(P.kind==="indiv");
var actS="background:var(--navy);color:#fff;",inaS="background:#fff;color:#475569;";
var h="<div class=\\"ntxn-card\\" style=\\"background:#FAFBFC\\"><div style=\\"font-size:11px;color:#6B7280;margin-bottom:8px\\">Auto-filled when you pick a contact above. For a new contact, fill these so it is searchable next time."+(req?" TIN is required for a Bill (SLP).":"")+"</div>";
h+="<div style=\\"display:flex;align-items:center;gap:8px;margin-bottom:8px;font-size:12.5px;color:var(--navy)\\">Customer type:<span style=\\"display:inline-flex;border:1px solid #CBD5E1;border-radius:6px;overflow:hidden\\"><button type=\\"button\\" onclick=\\"ntxnSetPartyKind('noni')\\" style=\\"font-size:12px;padding:5px 10px;border:none;cursor:pointer;"+(!indiv?actS:inaS)+"\\">Non-Individual</button><button type=\\"button\\" onclick=\\"ntxnSetPartyKind('indiv')\\" style=\\"font-size:12px;padding:5px 10px;border:none;border-left:1px solid #CBD5E1;cursor:pointer;"+(indiv?actS:inaS)+"\\">Individual</button></span></div>";
h+="<div class=\\"ntxn-grid\\">";
h+="<div><span class=\\"ntxn-lbl\\">TIN"+(req?" *":"")+"</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnP_tin\\" value=\\""+esc(P.tin)+"\\" inputmode=\\"numeric\\" maxlength=\\"11\\" oninput=\\"ntxnFmtTin(this)\\" placeholder=\\"000-000-000\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">Branch Code</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnP_branch\\" value=\\""+esc(P.branch)+"\\" inputmode=\\"numeric\\" maxlength=\\"5\\" oninput=\\"ntxnFmtBranch(this)\\" onblur=\\"ntxnPadBranch(this)\\" placeholder=\\"00000\\"></div>";
h+="<div></div>";
h+="</div>";
if(indiv){
h+="<div class=\\"ntxn-grid\\" style=\\"margin-top:8px\\">";
h+="<div><span class=\\"ntxn-lbl\\">Last Name</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnP_last\\" value=\\""+esc(P.last)+"\\" oninput=\\"ntxnNameParts()\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">First Name</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnP_first\\" value=\\""+esc(P.first)+"\\" oninput=\\"ntxnNameParts()\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">Middle Name</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnP_middle\\" value=\\""+esc(P.middle)+"\\" oninput=\\"ntxnNameParts()\\"></div>";
h+="</div>";
h+="<div id=\\"ntxnP_compose\\" style=\\"font-size:11.5px;color:var(--blue);margin-top:6px\\">"+(ntxnComposeName_()?("Saved as: "+esc(ntxnComposeName_())):"")+"</div>";
} else {
h+="<div style=\\"font-size:11.5px;color:#6B7280;margin-top:6px\\">Registered Name = ang pangalan sa \\"Received from / Customer\\" sa itaas.</div>";
}
h+="<div class=\\"ntxn-grid2\\" style=\\"margin-top:8px\\">";
h+="<div><span class=\\"ntxn-lbl\\">Address line 1</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnP_addr1\\" value=\\""+esc(P.addr1)+"\\" oninput=\\"ntxnSync()\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">Address line 2</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnP_addr2\\" value=\\""+esc(P.addr2)+"\\" oninput=\\"ntxnSync()\\"></div>";
h+="</div></div>";
return h;
}
function ntxnRenderStd_(s,cfg,vatReg){
var h="<div class=\\"ntxn-card\\"><div class=\\"ntxn-step\\">Step 1 - Details</div><div class=\\"ntxn-grid\\">";
h+="<div><span class=\\"ntxn-lbl\\">Date</span><input type=\\"date\\" class=\\"ntxn-in\\" id=\\"ntxnDate\\" value=\\""+esc(s.date)+"\\" onchange=\\"ntxnSync()\\"></div>";
var refLbl=cfg.control?(s.type==="invoice"?"Invoice No":"Bill No"):"Reference / OR No";
var refPh=cfg.control?(s.type==="invoice"?"INV-0001":"BILL-0001"):"e.g. OR-001234";
h+="<div><span class=\\"ntxn-lbl\\">"+refLbl+(cfg.control?"":" (optional)")+"</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnRef\\" value=\\""+esc(s.ref)+"\\" oninput=\\"ntxnSync()\\" placeholder=\\""+refPh+"\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">Description (optional)</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnDesc\\" value=\\""+esc(s.desc)+"\\" oninput=\\"ntxnSync()\\" placeholder=\\"What is this for?\\"></div>";
h+="</div>";
h+="<div class=\\"ntxn-grid2\\" style=\\"margin-top:8px\\">";
h+="<div><span class=\\"ntxn-lbl\\">"+cfg.partyLbl+(cfg.control?"":" (optional)")+"</span><input type=\\"text\\" class=\\"ntxn-in ss-input\\" data-ss=\\""+(ntxnContactSide_(s.type)==="customer"?"cust":"supp")+"\\" id=\\"ntxnP_pick\\" value=\\""+esc(s.party.name)+"\\" onchange=\\"ntxnPickContact()\\" oninput=\\"ntxnSync()\\" placeholder=\\"Type a name to search...\\" autocomplete=\\"off\\"></div>";
h+="<div>"+(cfg.control?"":ntxnCashSection_(s,cfg))+"</div>";
h+="</div>";
h+="<div style=\\"margin-top:8px\\"><span style=\\"font-size:12px;color:var(--blue);cursor:pointer\\" onclick=\\"ntxnToggleParty()\\"><span id=\\"ntxnPartyArrow\\">"+(s.showParty?String.fromCharCode(9662):String.fromCharCode(9656))+"</span> Details - TIN, branch, address"+(cfg.control?(s.type==="bill"?" (TIN required)":" (for SLS)"):"")+"</span></div>";
h+="</div>";
h+="<div id=\\"ntxnPartyWrap\\" style=\\"display:"+(s.showParty?"block":"none")+"\\">"+ntxnPartyPanel_(s,cfg)+"</div>";
// lines
h+="<div class=\\"ntxn-card\\"><div class=\\"ntxn-step\\">Step 2 - "+(cfg.sales?"Income / accounts (credit)":"Expense / accounts (debit)")+"</div>";
h+="<table class=\\"ntxn-tbl\\" style=\\"table-layout:fixed\\">"+(vatReg?"<colgroup><col style=\\"width:4%\\"><col style=\\"width:46%\\"><col style=\\"width:26%\\"><col style=\\"width:24%\\"></colgroup>":"<colgroup><col style=\\"width:4%\\"><col style=\\"width:72%\\"><col style=\\"width:24%\\"></colgroup>")+"<thead><tr><th>#</th><th>Account</th>"+(vatReg?"<th>VAT classification</th>":"")+"<th style=\\"white-space:nowrap\\">Amount"+(vatReg?(s.vatMode!=="ex"?" (total)":" (before VAT)"):"")+"</th></tr></thead><tbody>";
var tagOpts=ntxnTagOpts_(cfg.sales);
for(var i=0;i<s.lines.length;i++){
h+="<tr><td>"+(i+1)+"</td><td><input type=\\"text\\" class=\\"ntxn-in ss-input\\" data-ss=\\"acctnc\\" id=\\"ntxnA"+i+"\\" value=\\""+esc(s.lines[i].a)+"\\" oninput=\\"ntxnSync()\\" placeholder=\\"Type to search...\\" autocomplete=\\"off\\"></td>";
if(vatReg){h+="<td><select class=\\"ntxn-in\\" id=\\"ntxnT"+i+"\\" onchange=\\"ntxnSync()\\"><option value=\\"\\">(none)</option>";for(var t=0;t<tagOpts.length;t++)h+="<option value=\\""+esc(tagOpts[t])+"\\""+(s.lines[i].t===tagOpts[t]?" selected":"")+">"+esc(tagOpts[t])+"</option>";h+="</select></td>";}
h+="<td><input type=\\"text\\" inputmode=\\"decimal\\" class=\\"ntxn-in amt-in\\" id=\\"ntxnM"+i+"\\" value=\\""+esc(amtDisp_(s.lines[i].m))+"\\" oninput=\\"ntxnSync()\\" onfocus=\\"amtFocus_(this)\\" onblur=\\"ntxnRoundCell(this)\\" placeholder=\\"0.00\\"></td></tr>";
}
h+="</tbody></table>";
h+="<div style=\\"margin-top:8px;display:flex;gap:14px;align-items:center;flex-wrap:wrap\\"><button class=\\"ntxn-btn\\" onclick=\\"ntxnAddLine()\\">+ Add line</button>";
if(vatReg){var inc=(s.vatMode!=="ex");var actS="background:var(--navy);color:#fff;",inaS="background:#fff;color:#475569;";h+="<span style=\\"display:inline-flex;align-items:center;gap:8px;font-size:12.5px;color:var(--navy)\\">The amount I entered is:<span style=\\"display:inline-flex;border:1px solid #CBD5E1;border-radius:6px;overflow:hidden\\"><button type=\\"button\\" onclick=\\"ntxnSetVatMode('inc')\\" style=\\"font-size:12px;padding:5px 10px;border:none;cursor:pointer;"+(inc?actS:inaS)+"\\">Total (VAT included)</button><button type=\\"button\\" onclick=\\"ntxnSetVatMode('ex')\\" style=\\"font-size:12px;padding:5px 10px;border:none;border-left:1px solid #CBD5E1;cursor:pointer;"+(inc?inaS:actS)+"\\">Before VAT</button></span></span>";}
var whLbl=cfg.sales?"CWT withheld by customer (Form 2307)":"EWT you withheld";
h+="<span style=\\"display:inline-flex;gap:6px;align-items:center;font-size:12px;color:var(--navy)\\">"+whLbl+": <input type=\\"text\\" inputmode=\\"decimal\\" class=\\"ntxn-in amt-in\\" id=\\"ntxnWh\\" value=\\""+esc(amtDisp_(s.wh))+"\\" oninput=\\"ntxnSync()\\" onfocus=\\"amtFocus_(this)\\" onblur=\\"ntxnRoundCell(this)\\" style=\\"width:110px\\" placeholder=\\"0.00\\"></span>";
h+="</div></div>";
return h;
}
function ntxnRenderJournal_(s){
var h="<div class=\\"ntxn-card\\"><div class=\\"ntxn-step\\">Step 1 - Details</div><div class=\\"ntxn-grid\\">";
h+="<div><span class=\\"ntxn-lbl\\">Date</span><input type=\\"date\\" class=\\"ntxn-in\\" id=\\"ntxnDate\\" value=\\""+esc(s.date)+"\\" onchange=\\"ntxnSync()\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">Reference No (optional)</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnRef\\" value=\\""+esc(s.ref)+"\\" oninput=\\"ntxnSync()\\" placeholder=\\"e.g. JE-ADJ\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">Description (optional)</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnDesc\\" value=\\""+esc(s.desc)+"\\" oninput=\\"ntxnSync()\\" placeholder=\\"What is this entry for?\\"></div>";
h+="</div></div>";
h+="<div class=\\"ntxn-card\\"><div class=\\"ntxn-step\\">Step 2 - Journal lines</div>";
h+="<table class=\\"ntxn-tbl\\" style=\\"table-layout:fixed\\"><colgroup><col style=\\"width:4%\\"><col style=\\"width:52%\\"><col style=\\"width:22%\\"><col style=\\"width:22%\\"></colgroup><thead><tr><th>#</th><th>Account</th><th class=\\"num\\">Debit</th><th class=\\"num\\">Credit</th></tr></thead><tbody>";
for(var i=0;i<s.jlines.length;i++){
h+="<tr><td>"+(i+1)+"</td><td><input type=\\"text\\" class=\\"ntxn-in ss-input\\" data-ss=\\"acct\\" id=\\"ntxnJA"+i+"\\" value=\\""+esc(s.jlines[i].a)+"\\" oninput=\\"ntxnSync()\\" placeholder=\\"Type to search...\\" autocomplete=\\"off\\"></td>";
h+="<td><input type=\\"text\\" inputmode=\\"decimal\\" class=\\"ntxn-in amt-in\\" id=\\"ntxnJDr"+i+"\\" value=\\""+esc(amtDisp_(s.jlines[i].dr))+"\\" oninput=\\"ntxnJSide_(this,"+i+",'dr');ntxnSync()\\" onfocus=\\"amtFocus_(this)\\" onblur=\\"ntxnRoundCell(this)\\" placeholder=\\"0.00\\"></td>";
h+="<td><input type=\\"text\\" inputmode=\\"decimal\\" class=\\"ntxn-in amt-in\\" id=\\"ntxnJCr"+i+"\\" value=\\""+esc(amtDisp_(s.jlines[i].cr))+"\\" oninput=\\"ntxnJSide_(this,"+i+",'cr');ntxnSync()\\" onfocus=\\"amtFocus_(this)\\" onblur=\\"ntxnRoundCell(this)\\" placeholder=\\"0.00\\"></td></tr>";
}
h+="</tbody></table>";
h+="<div style=\\"margin-top:8px;display:flex;gap:14px;align-items:center;flex-wrap:wrap\\"><button class=\\"ntxn-btn\\" onclick=\\"ntxnAddJLine()\\">+ Add line</button><span id=\\"ntxnJBal\\" style=\\"font-size:12.5px;font-weight:600\\"></span></div></div>";
return h;
}
function ntxnRenderPay_(s,cfg){
var collect=(cfg.pay==="ar");
var h="<div class=\\"ntxn-card\\"><div class=\\"ntxn-step\\">Step 1 - "+(collect?"Pick the invoice to collect":"Pick the bill to pay")+"</div>";
if(!s.openLoaded){h+="<div style=\\"color:#6B7280;font-size:12.5px\\">Loading open "+(collect?"invoices":"bills")+"...</div>";}
else if(s.openErr){h+="<div class=\\"ntxn-warn\\">"+esc(s.openErr)+"</div>";}
else if(!s.open.length){h+="<div style=\\"color:#1E7E34;font-size:12.5px\\">No open "+(collect?"invoices":"bills")+" - everything is settled.</div>";}
else{
h+="<table class=\\"ntxn-open\\"><thead><tr><th>Date</th><th>Reference</th><th>"+(collect?"Customer":"Supplier")+"</th><th style=\\"text-align:right\\">Original</th><th style=\\"text-align:right\\">Balance</th><th style=\\"text-align:right\\">Age</th></tr></thead><tbody>";
for(var i=0;i<s.open.length;i++){var o=s.open[i];var age=ntxnAge_(o.date);h+="<tr class=\\""+(s.paySel===i?"on":"")+"\\" onclick=\\"ntxnPickOpen("+i+")\\"><td>"+esc(o.date)+"</td><td>"+esc(o.ref)+"</td><td>"+esc(o.party)+"</td><td class=\\"num\\" style=\\"text-align:right\\">"+ntxnFmt2_(o.original)+"</td><td class=\\"num\\" style=\\"text-align:right;font-weight:700\\">"+ntxnFmt2_(o.balance)+"</td><td class=\\"num\\" style=\\"text-align:right;color:"+(age>60?"#B00020":"#6B7280")+"\\">"+age+"d</td></tr>";}
h+="</tbody></table>";
}
h+="</div>";
if(s.paySel>=0&&s.open[s.paySel]){
var o2=s.open[s.paySel],whAcct=ntxnWhAcct_(collect);
h+="<div class=\\"ntxn-card\\"><div class=\\"ntxn-step\\">Step 2 - "+(collect?"Receive payment":"Make payment")+"</div>";
h+="<div style=\\"font-size:12px;color:var(--navy);margin-bottom:10px\\">Settling <b>"+esc(o2.party)+"</b> · Invoice "+esc(o2.ref)+" · Balance <b>"+ntxnFmt2_(o2.balance)+"</b></div>";
h+="<div class=\\"ntxn-grid\\">";
h+="<div><span class=\\"ntxn-lbl\\">Date</span><input type=\\"date\\" class=\\"ntxn-in\\" id=\\"ntxnDate\\" value=\\""+esc(s.date)+"\\" onchange=\\"ntxnSync()\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">"+(collect?"Collection Receipt / OR No":"Reference / Voucher No")+"</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnRef\\" value=\\""+esc(s.ref)+"\\" oninput=\\"ntxnSync()\\" placeholder=\\""+(collect?"e.g. CR-0001":"e.g. CV-0001")+"\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">Description (optional)</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnDesc\\" value=\\""+esc(s.desc||"")+"\\" oninput=\\"ntxnSync()\\" placeholder=\\""+(collect?"e.g. Collection of "+esc(o2.ref):"e.g. Payment of "+esc(o2.ref))+"\\"></div>";
h+="</div>";
h+="<div style=\\"margin-top:8px\\">"+ntxnCashSection_(s,cfg)+"</div>";
var whLbl=collect?"CWT withheld by customer (Form 2307)":"EWT you withheld";
h+="<div class=\\"ntxn-grid\\" style=\\"margin-top:10px\\">";
h+="<div><span class=\\"ntxn-lbl\\">"+whLbl+"</span><input type=\\"text\\" inputmode=\\"decimal\\" class=\\"ntxn-in amt-in\\" id=\\"ntxnWh\\" value=\\""+esc(amtDisp_(s.wh))+"\\" oninput=\\"ntxnSync()\\" onfocus=\\"amtFocus_(this)\\" onblur=\\"ntxnRoundCell(this)\\" placeholder=\\"0.00\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">"+(collect?"CWT account":"EWT account")+"</span><input type=\\"text\\" class=\\"ntxn-in\\" value=\\""+esc(whAcct||"(no CWT/EWT account in COA)")+"\\" readonly style=\\"background:#F5F7FA;color:#6B7280\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">Form 2307 ref (optional)</span><input type=\\"text\\" class=\\"ntxn-in\\" id=\\"ntxnWhRef\\" value=\\""+esc(s.whRef||"")+"\\" oninput=\\"ntxnSync()\\"></div>";
h+="</div>";
h+="<div class=\\"ntxn-grid2\\" style=\\"margin-top:8px\\">";
h+="<div><span class=\\"ntxn-lbl\\">Other charge / deduction account (optional)</span><input type=\\"text\\" class=\\"ntxn-in ss-input\\" data-ss=\\"acctnc\\" id=\\"ntxnChargeAcct\\" value=\\""+esc(s.chargeAcct||"")+"\\" oninput=\\"ntxnSync()\\" placeholder=\\"e.g. Bank Charges\\" autocomplete=\\"off\\"></div>";
h+="<div><span class=\\"ntxn-lbl\\">Charge amount</span><input type=\\"text\\" inputmode=\\"decimal\\" class=\\"ntxn-in amt-in\\" id=\\"ntxnCharge\\" value=\\""+esc(amtDisp_(s.charge||""))+"\\" oninput=\\"ntxnSync()\\" onfocus=\\"amtFocus_(this)\\" onblur=\\"ntxnRoundCell(this)\\" placeholder=\\"0.00\\"></div>";
h+="</div>";
h+="<div style=\\"margin-top:8px\\"><span style=\\"font-size:12px;color:var(--blue);cursor:pointer\\" onclick=\\"ntxnToggleParty()\\"><span id=\\"ntxnPartyArrow\\">"+(s.showParty?String.fromCharCode(9662):String.fromCharCode(9656))+"</span> "+(collect?"Customer details - TIN, branch, registered name (for SLS)":"Supplier details - TIN, branch, name (for SLP)")+"</span></div>";
h+="</div>";
h+="<div id=\\"ntxnPartyWrap\\" style=\\"display:"+(s.showParty?"block":"none")+"\\">"+ntxnPartyPanel_(s,cfg)+"</div>";
}
return h;
}
function ntxnFindContact_(name,side){var list=ntxnContactList_(side);var v=String(name||"").trim().toUpperCase();for(var i=0;i<list.length;i++){if(String(list[i].c.name||"").trim().toUpperCase()===v)return list[i].c;}for(var j=0;j<list.length;j++){if(String(list[j].value||"").trim().toUpperCase()===v)return list[j].c;}return null;}
function ntxnPickOpen(i){ntxnSync();var o=NEWTXN_STATE.open[i];if(!o)return;NEWTXN_STATE.paySel=i;NEWTXN_STATE.ref="";NEWTXN_STATE.desc="";NEWTXN_STATE.payInvRef=o.ref;NEWTXN_STATE.payBal=o.balance;var side=ntxnContactSide_(NEWTXN_STATE.type);var f=ntxnFindContact_(o.party,side);var P=NEWTXN_STATE.party;if(f){P.name=f.regName||f.name||o.party;P.tin=f.tin||"";P.branch=f.branch||"";P.last=f.last||"";P.first=f.first||"";P.middle=f.middle||"";P.addr1=f.addr1||"";P.addr2=f.addr2||"";P.kind=(P.last||P.first)?"indiv":"noni";}else{P.name=o.party;P.tin="";P.branch="";P.last="";P.first="";P.middle="";P.addr1="";P.addr2="";P.kind="noni";}var bk=NTXN_T[NEWTXN_STATE.type].book;var last=NTXN_LASTCASH[bk]||(ntxnCashAccts_()[0]||"");NEWTXN_STATE.cashLines=[{a:last,m:String(o.balance)}];NEWTXN_STATE.wh="";NEWTXN_STATE.whRef="";NEWTXN_STATE.charge="";NEWTXN_STATE.chargeAcct="";renderNewTxn();}
function ntxnOpenItems_(kind){
// Open invoices/bills with their REMAINING balance. Collections/payments are applied FIFO
// (oldest charge first) PER PARTY - matching the Subsidiary Ledger. (Old code netted by exact
// reference, so a collection "CR-F03" never reduced its invoice "INV-J04" -> invoice stayed full.)
var re=(kind==="ap")?/^accounts payable/i:/^accounts receivable/i;var je=(DATA&&DATA.jeRows)||[];
var parties={};
for(var i=0;i<je.length;i++){var r=je[i];if(!re.test(String(r.account||"").trim()))continue;
var pn=String(r.name||"").trim()||"(No Name)";var g=parties[pn];if(!g)g=parties[pn]={charges:[],pay:0};
var dr=parseFloat(r.debit)||0,cr=parseFloat(r.credit)||0;var chg=(kind==="ap")?(cr-dr):(dr-cr);
if(chg>0.005)g.charges.push({ref:String(r.ref||"")||("#"+(r.entryId||"")),date:r.date||"",amt:Math.round(chg*100)/100});
else if(chg<-0.005)g.pay+=-chg;}
var items=[];
for(var p in parties){if(!parties.hasOwnProperty(p))continue;var g=parties[p];
g.charges.sort(function(a,b){if(a.date!==b.date)return a.date<b.date?-1:1;return (a.ref||"")<(b.ref||"")?-1:1;});
var pay=g.pay;
for(var c=0;c<g.charges.length;c++){var ch=g.charges[c];var bal=ch.amt;
if(pay>0.005){var ap=Math.min(bal,pay);bal=Math.round((bal-ap)*100)/100;pay=Math.round((pay-ap)*100)/100;}
if(bal>0.005)items.push({ref:ch.ref,party:(p==="(No Name)"?"":p),date:ch.date,original:ch.amt,balance:bal});}}
items.sort(function(a,b){return (a.date<b.date?-1:(a.date>b.date?1:0));});
return items;
}
function ntxnLoadOpen_(){
var s=NEWTXN_STATE,cfg=NTXN_T[s.type];if(!cfg.pay)return;
s.openErr="";
try{s.open=ntxnOpenItems_(cfg.pay);s.openLoaded=true;if(s.type===(cfg.pay==="ar"?"payar":"payap"))renderNewTxn();return;}catch(e){}
s.openLoaded=false;
if(typeof google==="undefined"||!google.script||!google.script.run||!DATA.spreadsheetId){s.openLoaded=true;s.openErr="Open items need the live WebApp + a saved Spreadsheet ID (Generate Report once).";return;}
google.script.run.withSuccessHandler(function(r){if(!r||r.__error){NEWTXN_STATE.openErr=(r&&r.__error)||"Could not load open items.";NEWTXN_STATE.open=[];}else{NEWTXN_STATE.open=r.items||[];}NEWTXN_STATE.openLoaded=true;if(NEWTXN_STATE.type===(cfg.pay==="ar"?"payar":"payap"))renderNewTxn();}).withFailureHandler(function(e){NEWTXN_STATE.openErr=(e&&e.message)||"Could not load open items.";NEWTXN_STATE.openLoaded=true;renderNewTxn();}).openItems(DATA.spreadsheetId,cfg.pay);
}
function ntxnUpdatePreview_(){
var c=ntxnCompute_(),prev=document.getElementById("ntxnPreview"),btn=document.getElementById("ntxnSaveBtn");if(!prev)return;
var h="<table><colgroup><col style=\\"width:42%\\"><col style=\\"width:20%\\"><col style=\\"width:19%\\"><col style=\\"width:19%\\"></colgroup><thead><tr><td style=\\"font-size:10px;color:#6B7280\\">ACCOUNT</td><td style=\\"font-size:10px;color:#6B7280\\">VAT</td><td class=\\"num\\" style=\\"font-size:10px;color:#6B7280\\">DEBIT</td><td class=\\"num\\" style=\\"font-size:10px;color:#6B7280\\">CREDIT</td></tr></thead><tbody>";
if(!c.lines.length)h+="<tr><td colspan=\\"4\\" style=\\"color:#6B7280\\">Fill in the entry above to see the preview.</td></tr>";
var creates=[];
for(var j=0;j<c.lines.length;j++){var l=c.lines[j];h+="<tr><td>"+esc(l.account)+"</td><td style=\\"font-size:11px;color:#6B7280\\">"+esc(l.vatTag||"")+"</td><td class=\\"num\\">"+(l.side==="DR"?ntxnFmt2_(l.amount):"-")+"</td><td class=\\"num\\">"+(l.side==="CR"?ntxnFmt2_(l.amount):"-")+"</td></tr>";
if(!c.journal&&!ntxnColExists_(c.book,l.account,l.side,l.vatTag||""))creates.push(l.account+" ("+l.side+(l.vatTag?", "+l.vatTag:"")+")");}
var __rendered=c.lines.length?c.lines.length:1;for(var __p=__rendered;__p<6;__p++){h+="<tr class=\\"ntxn-pad\\"><td> </td><td></td><td class=\\"num\\"></td><td class=\\"num\\"></td></tr>";}
h+="</tbody></table>";
prev.innerHTML=h;
var oldBal=document.getElementById("ntxnBal");if(oldBal)oldBal.parentNode.removeChild(oldBal);
var oldW=document.getElementById("ntxnCreateW");if(oldW)oldW.parentNode.removeChild(oldW);
var dr=ntxnRound_(c.dr),cr=ntxnRound_(c.cr);
var ok=(c.lines.length>=2&&dr>0&&Math.abs(dr-cr)<0.005);
var bookLbl={CRB:"Cash Receipts Book",CDB:"Cash Disbursements Book",SJ:"Sales Journal",PJ:"Purchase Journal",GJ:"General Journal"}[c.book]||c.book;
var msg=ok?("Balanced - will record in "+c.book+" ("+bookLbl+"): DR "+ntxnFmt2_(dr)+" = CR "+ntxnFmt2_(cr)+(c.vatAmt>0?" (incl. "+ntxnFmt2_(c.vatAmt)+" VAT)":"")):("Incomplete - "+(dr===0?"enter amounts":(Math.abs(dr-cr)>=0.005?"not balanced (DR "+ntxnFmt2_(dr)+" vs CR "+ntxnFmt2_(cr)+")":"add at least two lines")));
var whMiss=(!c.journal&&!c.pay&&(c.wh||0)>0&&!c.whAcct);if(whMiss){ok=false;msg="Add a 'Creditable Withholding Tax / CWT Receivable' account to your Chart of Accounts first - needed to record the Form 2307 withholding.";}
if(c.pay){var over=c.settled>c.balance+0.005;var chargeNoAcct=(c.chargeAmtEntered>0&&!c.chAcct);ok=(c.settled>0&&Math.abs(dr-cr)<0.005&&!over&&!chargeNoAcct);if(over)msg="Cannot settle more than the open balance ("+ntxnFmt2_(c.balance)+").";else if(chargeNoAcct)msg="Pick an account for the other charge / deduction.";else if(c.settled<=0)msg="Enter the cash"+(c.pay==="ar"?" received":" paid")+" and any withholding.";else msg="Settling "+ntxnFmt2_(c.settled)+" of "+ntxnFmt2_(c.balance)+" - records in "+c.book+" ("+bookLbl+")"+(c.settled<c.balance-0.005?" - partial, "+ntxnFmt2_(c.balance-c.settled)+" stays open":"")+". Cash "+ntxnFmt2_(c.cashSum)+(c.whAmt>0?" + WHT "+ntxnFmt2_(c.whAmt):"")+(c.chAmt>0?" + charge "+ntxnFmt2_(c.chAmt):"");}
if(!c.journal)prev.insertAdjacentHTML("afterend","<div id=\\"ntxnBal\\" class=\\"ntxn-bal "+(ok?"ntxn-ok":"ntxn-no")+"\\">"+msg+"</div>");
if(creates.length){document.getElementById("ntxnBal").insertAdjacentHTML("afterend","<div id=\\"ntxnCreateW\\" class=\\"ntxn-warn\\">New column(s) will be auto-created in "+c.book+": "+esc(creates.join(", "))+". (header + DR/CR + VAT tag)</div>");}
var jb=document.getElementById("ntxnJBal");if(jb){if(c.journal){var jok=(c.n>=2&&Math.abs(c.dr-c.cr)<0.005&&c.dr>0);jb.innerHTML=jok?("<span style=\\"color:#0F6E56\\">Balanced - DR "+ntxnFmt2_(c.dr)+" = CR "+ntxnFmt2_(c.cr)+"</span>"):("<span style=\\"color:#B45309\\">"+((c.dr===0&&c.cr===0)?"Enter debits and credits":"Not balanced - DR "+ntxnFmt2_(c.dr)+" vs CR "+ntxnFmt2_(c.cr))+"</span>");}else jb.innerHTML="";}
if(btn)btn.disabled=!ok;
}
function ntxnClear(){var t=NEWTXN_STATE.type,d=NEWTXN_STATE.date,op=NEWTXN_STATE.open,ol=NEWTXN_STATE.openLoaded;NEWTXN_STATE=null;ntxnInit_();NEWTXN_STATE.type=t;NEWTXN_STATE.date=d;NEWTXN_STATE.open=op;NEWTXN_STATE.openLoaded=ol;NEWTXN_STATE.showParty=false;ntxnSeedCash_();renderNewTxn();}
function ntxnSave(){
ntxnSync();
var s=NEWTXN_STATE,cfg=NTXN_T[s.type],c=ntxnCompute_(),fb=document.getElementById("ntxnFb"),btn=document.getElementById("ntxnSaveBtn");
function err(m){if(fb){fb.style.color="#DC2626";fb.textContent=m;}}
if(typeof google==="undefined"||!google.script||!google.script.run)return err("Saving needs the live WebApp (open via the Sheet menu link).");
if(!DATA.spreadsheetId)return err("Save unavailable - re-deploy the launcher and Generate Report once.");
if(c.lines.length<2||ntxnRound_(c.dr)<=0||Math.abs(c.dr-c.cr)>=0.005)return err("Complete a balanced entry first.");
if(s.type==="bill"&&!String(s.party.tin||"").trim())return err("Supplier TIN is required for a Bill (SLP).");
if(cfg.pay){if(c.settled>c.balance+0.005)return err("Cannot settle more than the open balance ("+ntxnFmt2_(c.balance)+").");if(c.chargeAmtEntered>0&&!c.chAcct)return err("Pick an account for the other charge / deduction.");}
if((c.book==="CRB"||c.book==="CDB")&&s.cashLines&&s.cashLines[0]&&String(s.cashLines[0].a||"").trim())NTXN_LASTCASH[c.book]=String(s.cashLines[0].a).trim();
if(btn)btnBusy_(btn);
if(fb){fb.style.color="#6B7280";fb.textContent="Writing to the books...";}
if(cfg.journal){
google.script.run.withSuccessHandler(ntxnSaved_).withFailureHandler(ntxnErr_).saveJournal(DATA.spreadsheetId,{date:s.date,ref:s.ref,desc:s.desc,status:"Posted",lines:c.lines});
}else{
var P=s.party;
var payDesc=cfg.pay?((String(s.desc||"").trim()||(cfg.pay==="ar"?"Collection":"Payment"))+(String(s.whRef||"").trim()?" - 2307: "+String(s.whRef).trim():"")):s.desc;
var payload={book:c.book,date:s.date,ref:s.ref,desc:payDesc,status:"Posted",lines:c.lines,name:P.name,tin:P.tin,branchCode:P.branch,lastName:P.last,firstName:P.first,middleName:P.middle,addr1:P.addr1,addr2:P.addr2};
google.script.run.withSuccessHandler(ntxnSaved_).withFailureHandler(ntxnErr_).saveTransaction(DATA.spreadsheetId,payload);
}
}
function ntxnSaved_(res){
var fb=document.getElementById("ntxnFb"),btn=document.getElementById("ntxnSaveBtn");
if(!res||res.__error){btnReset_(btn);if(fb){fb.style.color="#DC2626";fb.textContent="Save failed: "+((res&&res.__error)||"unknown");}return;}
var note=(res.created&&res.created.length)?(" (+"+res.created.length+" new column"+(res.created.length>1?"s":"")+")"):"";
if(typeof refreshToast_==="function")refreshToast_("Saved "+(res.entryId||"")+" to "+res.book+note+" \\u2713",false);
var keepType=NEWTXN_STATE.type;
var _si={book:res.book||"",entryId:res.entryId||"",row:res.row||0};
ntxnClear();
var fb2=document.getElementById("ntxnFb");
if(fb2&&_si.entryId){var bk={CRB:"Cash Receipts Book",CDB:"Cash Disbursements Book",SJ:"Sales Journal",PJ:"Purchase Journal",GJ:"General Journal"}[_si.book]||_si.book;fb2.style.color="#0F6E56";fb2.innerHTML="<div style=\\"background:#E1F5EE;border:1px solid #9FE1CB;border-radius:8px;padding:8px 12px\\"><b>Saved "+esc(_si.entryId)+"</b> to "+esc(bk)+". <a onclick=\\"showEntryEdit('"+_si.book+"','"+_si.entryId+"',"+_si.row+")\\" style=\\"color:var(--blue);cursor:pointer;text-decoration:underline;margin-left:6px\\">View / Edit this entry</a><div style=\\"font-size:11px;color:#6B7280;margin-top:4px\\">Makikita mo rin ito sa <b>"+esc(bk)+"</b>, o sa <b>Reports → General Ledger / Trial Balance</b> (drill-down sa amount) - i-date filter lang.</div></div>";}
if(typeof google!=="undefined"&&google.script&&google.script.run&&DATA.spreadsheetId){
google.script.run.withSuccessHandler(function(p){if(p&&!p.__error){DATA=p;if(typeof runAnomalyDaemon==="function"){try{runAnomalyDaemon();}catch(e){}}buildTopBar();if(NTXN_T[keepType].pay){NEWTXN_STATE.openLoaded=false;ntxnLoadOpen_();}}}).apiRefresh(DATA.spreadsheetId);
}
}
function ntxnErr_(e){var fb=document.getElementById("ntxnFb"),btn=document.getElementById("ntxnSaveBtn");btnReset_(btn);if(fb){fb.style.color="#DC2626";fb.textContent="Save failed: "+((e&&e.message)||e||"unknown")+" - make sure the WebApp is deployed with access to the Sheet.";}}
`; }
function getSubLedgerJS_() { return `
var SUBL_STATE={view:"customer",mode:"ledger",from:"",to:"",asOf:"",search:"",expanded:{}};
function sublTypeMap_(){var m={};for(var i=0;i<DATA.coa.length;i++){var a=DATA.coa[i];m[String(a.title).trim()]=String(a.type).trim();}return m;}
function sublTradeAcct_(view){var want=view==="customer"?"Accounts Receivable":"Accounts Payable";for(var i=0;i<DATA.coa.length;i++){if(String(DATA.coa[i].title).trim()===want)return want;}return want;}
function sublCashAcct_(){for(var i=0;i<DATA.coa.length;i++){var t=String(DATA.coa[i].title).trim();if(/^cash in bank$/i.test(t))return t;}return "Cash in Bank";}
function sublDates_(){var ds=DATA.jeRows.map(function(e){return e.date;}).filter(function(d){return d&&d.length>=10;}).sort();return {min:ds[0]||"2026-01-01",max:ds[ds.length-1]||"2026-12-31"};}
function sublFmt_(n){if(!n||Math.abs(n)<0.005)return "-";var v=Math.abs(n).toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});return n<0?"("+v+")":v;}
/* Per-party data within [from,to]: A/R or A/P movement rows + activity totals */
function sublData_(){
var view=SUBL_STATE.view, from=SUBL_STATE.from, to=SUBL_STATE.to;
var tradeAcct=sublTradeAcct_(view), cashAcct=sublCashAcct_(), tm=sublTypeMap_();
var cust=(view==="customer");
var parties={}, order=[];
function P(name){var k=name||"(No Name)";if(!parties[k]){parties[k]={name:k,rows:[],sales:0,collect:0,purch:0,pay:0};order.push(k);}return parties[k];}
// Pass 1: ONLY the trade control account (A/R or A/P) creates a party. This keeps the subsidiary
// ledger to true trade parties - cash-only customers, suppliers, and the owner (no A/R / A/P
// balance) no longer appear as empty rows in the wrong view (A/R best practice; Xero/QBO pattern).
for(var i=0;i<DATA.jeRows.length;i++){
var je=DATA.jeRows[i];if(!je.date||je.date<from||je.date>to)continue;
if(je.account===tradeAcct){var nm=(je.name||"(No Name)").trim()||"(No Name)";P(nm).rows.push(je);}
}
// Pass 2: activity memo (sales/collections or purchases/payments) for EXISTING trade parties only.
for(var i2=0;i2<DATA.jeRows.length;i2++){
var je2=DATA.jeRows[i2];if(!je2.date||je2.date<from||je2.date>to)continue;
var nm2=(je2.name||"(No Name)").trim()||"(No Name)";if(!parties[nm2])continue;
var t=tm[je2.account]||"";
if(cust){
if(t==="Revenue")parties[nm2].sales+=(je2.credit||0)-(je2.debit||0);
if(je2.account===cashAcct)parties[nm2].collect+=(je2.debit||0);
}else{
if(t==="Expense"||/inventory|equipment|property/i.test(je2.account))parties[nm2].purch+=(je2.debit||0)-(je2.credit||0);
if(je2.account===cashAcct)parties[nm2].pay+=(je2.credit||0);
}
}
var out=[];
for(var o=0;o<order.length;o++){var p=parties[order[o]];
p.rows.sort(function(a,b){if(a.date!==b.date)return a.date<b.date?-1:1;return (a.ref||"")<(b.ref||"")?-1:1;});
var bal=0,totDr=0,totCr=0;
for(var r=0;r<p.rows.length;r++){var x=p.rows[r];bal+=(x.debit||0)-(x.credit||0);totDr+=x.debit||0;totCr+=x.credit||0;}
p.totDr=totDr;p.totCr=totCr;p.ending=cust?bal:-bal;
out.push(p);
}
out.sort(function(a,b){return b.ending-a.ending;});
return {parties:out,tradeAcct:tradeAcct};
}
/* Open-item FIFO aging as of SUBL_STATE.asOf */
function sublAging_(){
var view=SUBL_STATE.view, asOf=SUBL_STATE.asOf, tradeAcct=sublTradeAcct_(view), cust=(view==="customer");
var parties={}, order=[];
for(var i=0;i<DATA.jeRows.length;i++){var je=DATA.jeRows[i];if(je.account!==tradeAcct)continue;if(!je.date||je.date>asOf)continue;
var nm=(je.name||"(No Name)").trim()||"(No Name)";if(!parties[nm]){parties[nm]=[];order.push(nm);}parties[nm].push(je);}
function days(d){var a=new Date(asOf+"T00:00:00"),b=new Date(d+"T00:00:00");return Math.floor((a-b)/86400000);}
var rows=[];
for(var o=0;o<order.length;o++){var nm=order[o];var es=parties[nm].slice();
es.sort(function(a,b){if(a.date!==b.date)return a.date<b.date?-1:1;return (a.ref||"")<(b.ref||"")?-1:1;});
var queue=[],credits=0;
for(var e=0;e<es.length;e++){var x=es[e];var charge=cust?((x.debit||0)-(x.credit||0)):((x.credit||0)-(x.debit||0));
if(charge>0.005){queue.push({date:x.date,ref:x.ref,amt:charge});}
else if(charge<-0.005){credits+=-charge;}
}
// apply credits FIFO to oldest open charges
for(var q=0;q<queue.length&&credits>0.005;q++){var ap=Math.min(queue[q].amt,credits);queue[q].amt-=ap;credits-=ap;}
var b={cur:0,b1:0,b2:0,b3:0,tot:0};
for(var q2=0;q2<queue.length;q2++){if(queue[q2].amt<0.005)continue;var d=days(queue[q2].date);var amt=queue[q2].amt;b.tot+=amt;
if(d<=30)b.cur+=amt;else if(d<=60)b.b1+=amt;else if(d<=90)b.b2+=amt;else b.b3+=amt;}
var unapplied=credits;
if(b.tot>0.005||unapplied>0.005)rows.push({name:nm,cur:b.cur,b1:b.b1,b2:b.b2,b3:b.b3,tot:b.tot,adv:unapplied});
}
rows.sort(function(a,b){return b.tot-a.tot;});
return rows;
}
function setSUBLView(v){SUBL_STATE.view=v;SUBL_STATE.expanded={};buildSUBL();}
function setSUBLMode(m){SUBL_STATE.mode=m;SUBL_STATE.expanded={};buildSUBL();}
function setSUBLFrom(v){SUBL_STATE.from=v;renderSUBL();}
function setSUBLTo(v){SUBL_STATE.to=v;renderSUBL();}
function setSUBLAsOf(v){SUBL_STATE.asOf=v;renderSUBL();}
function setSUBLSearch(v){SUBL_STATE.search=(v||"").toLowerCase();renderSUBL();}
function toggleSUBLParty(id){SUBL_STATE.expanded[id]=!SUBL_STATE.expanded[id];renderSUBL();}
function buildSUBL(){
if(!SUBL_STATE.from){var d=sublDates_();SUBL_STATE.from=d.min;SUBL_STATE.to=d.max;SUBL_STATE.asOf=d.max;}
buildSUBLControls();renderSUBL();
}
function buildSUBLControls(){
var s=SUBL_STATE;
var h="<div class=\\"ctrl-bar\\">";
h+="<div class=\\"ctrl-group\\"><label>View</label><select onchange=\\"setSUBLView(this.value)\\"><option value=\\"customer\\""+(s.view==="customer"?" selected":"")+">Customers (A/R)</option><option value=\\"supplier\\""+(s.view==="supplier"?" selected":"")+">Suppliers (A/P)</option></select></div>";
h+="<div class=\\"ctrl-group\\"><label>Report</label><select onchange=\\"setSUBLMode(this.value)\\"><option value=\\"ledger\\""+(s.mode==="ledger"?" selected":"")+">Subsidiary Ledger</option><option value=\\"aging\\""+(s.mode==="aging"?" selected":"")+">Aging</option></select></div>";
if(s.mode==="ledger"){
h+="<div class=\\"ctrl-group\\"><label>From</label><input type=\\"date\\" value=\\""+s.from+"\\" onchange=\\"setSUBLFrom(this.value)\\"></div>";
h+="<div class=\\"ctrl-group\\"><label>To</label><input type=\\"date\\" value=\\""+s.to+"\\" onchange=\\"setSUBLTo(this.value)\\"></div>";
}else{
h+="<div class=\\"ctrl-group\\"><label>As of</label><input type=\\"date\\" value=\\""+s.asOf+"\\" onchange=\\"setSUBLAsOf(this.value)\\"></div>";
}
h+="<div class=\\"ctrl-group\\"><label>Search</label><input type=\\"text\\" placeholder=\\"Party name...\\" oninput=\\"setSUBLSearch(this.value)\\"></div>";
h+="</div>";
document.getElementById("controls").innerHTML=h;
}
function renderSUBL(){if(SUBL_STATE.mode==="aging")renderSUBLAging();else renderSUBLedger();}
function renderSUBLedger(){
var s=SUBL_STATE, cust=(s.view==="customer"), d=sublData_(), meta=getReportMetadata();
var fromLong=formatDateLong(s.from),toLong=formatDateLong(s.to);
var h="<div class=\\"report-block\\"><div class=\\"report-card\\">";
h+="<div class=\\"report-hdr\\"><div class=\\"report-biz\\">"+esc(meta.businessName)+"</div><div class=\\"report-title\\">"+(cust?"Customer":"Supplier")+" Subsidiary Ledger</div><div class=\\"report-date\\">For the period "+fromLong+" to "+toLong+"</div><div class=\\"report-currency\\">(Amounts in Philippine Peso)</div></div>";
h+="<table class=\\"cf-table\\" style=\\"width:100%\\"><thead><tr><th class=\\"cf-descr\\">"+(cust?"Customer":"Supplier")+"</th><th class=\\"cf-amount-h\\">Beginning</th><th class=\\"cf-amount-h\\">"+(cust?"Billings":"Purchases")+"</th><th class=\\"cf-amount-h\\">"+(cust?"Collections":"Payments")+"</th><th class=\\"cf-amount-h\\">Ending "+(cust?"A/R":"A/P")+"</th></tr></thead><tbody>";
var tDr=0,tCr=0,tEnd=0;
for(var i=0;i<d.parties.length;i++){var p=d.parties[i];
if(s.search&&p.name.toLowerCase().indexOf(s.search)<0)continue;
var noname=(p.name==="(No Name)");
var charges=cust?p.totDr:p.totCr, settle=cust?p.totCr:p.totDr;
tDr+=charges;tCr+=settle;tEnd+=p.ending;
var id="sl"+i;var open=!!s.expanded[id];
h+="<tr style=\\"cursor:pointer\\" onclick=\\"toggleSUBLParty('"+id+"')\\"><td class=\\"cf-line\\"><b>"+esc(p.name)+"</b>"+(noname?" <span style=\\"font-size:10px;color:#9C6F00;border:1px solid #9C6F00;border-radius:4px;padding:0 4px\\">UNTAGGED</span>":"")+"</td><td class=\\"cf-amount\\">-</td><td class=\\"cf-amount\\">"+sublFmt_(charges)+"</td><td class=\\"cf-amount\\">"+sublFmt_(settle)+"</td><td class=\\"cf-amount\\" style=\\"font-weight:700\\">"+sublFmt_(p.ending)+"</td></tr>";
if(open){
h+="<tr><td colspan=\\"5\\" style=\\"padding:0 0 8px 0;background:#FAFBFC\\">";
h+="<table style=\\"width:100%;border-collapse:collapse;font-size:12px\\"><thead><tr><th style=\\"text-align:left;padding:5px 10px;color:#6B7280;font-size:10px\\">Date</th><th style=\\"text-align:left;padding:5px 10px;color:#6B7280;font-size:10px\\">Ref</th><th style=\\"text-align:left;padding:5px 10px;color:#6B7280;font-size:10px\\">Particulars</th><th style=\\"text-align:right;padding:5px 10px;color:#6B7280;font-size:10px\\">"+(cust?"Charge":"Payment")+"</th><th style=\\"text-align:right;padding:5px 10px;color:#6B7280;font-size:10px\\">"+(cust?"Collection":"Purchase")+"</th><th style=\\"text-align:right;padding:5px 10px;color:#6B7280;font-size:10px\\">Balance</th></tr></thead><tbody>";
h+="<tr><td colspan=\\"5\\" style=\\"padding:4px 10px;font-style:italic;color:#6B7280\\">Beginning balance</td><td style=\\"text-align:right;padding:4px 10px;color:#6B7280\\">"+sublFmt_(0)+"</td></tr>";
var bal=0;
for(var r=0;r<p.rows.length;r++){var x=p.rows[r];bal+=(x.debit||0)-(x.credit||0);var shown=cust?bal:-bal;
var dv=cust?(x.debit||0):(x.credit||0), cv=cust?(x.credit||0):(x.debit||0);
h+="<tr><td style=\\"padding:4px 10px\\">"+fmtGLDate(x.date)+"</td><td style=\\"padding:4px 10px\\">"+esc(x.ref||"")+"</td><td style=\\"padding:4px 10px\\">"+esc(x.desc||"")+"</td><td style=\\"text-align:right;padding:4px 10px\\" class=\\"gl-amt-clickable\\" onclick=\\"event.stopPropagation();showJEDetail('"+x.date+"','"+escJS(x.ref||"")+"')\\">"+sublFmt_(dv)+"</td><td style=\\"text-align:right;padding:4px 10px\\" class=\\"gl-amt-clickable\\" onclick=\\"event.stopPropagation();showJEDetail('"+x.date+"','"+escJS(x.ref||"")+"')\\">"+sublFmt_(cv)+"</td><td style=\\"text-align:right;padding:4px 10px\\">"+sublFmt_(shown)+"</td></tr>";
}
h+="<tr style=\\"border-top:2px solid #1F3864;font-weight:700\\"><td colspan=\\"3\\" style=\\"padding:5px 10px\\">Ending balance</td><td style=\\"text-align:right;padding:5px 10px\\">"+sublFmt_(charges)+"</td><td style=\\"text-align:right;padding:5px 10px\\">"+sublFmt_(settle)+"</td><td style=\\"text-align:right;padding:5px 10px\\">"+sublFmt_(p.ending)+"</td></tr>";
h+="</tbody></table>";
// activity summary
if(cust)h+="<div style=\\"padding:8px 12px;font-size:12px;color:#374151;background:#F8FAFF;border-top:1px solid #E5E7EB\\"><b>Activity:</b> Total Sales (cash + credit): <b>"+sublFmt_(p.sales||0)+"</b> · Total Collection (cash received): <b>"+sublFmt_(p.collect||0)+"</b></div>";
else h+="<div style=\\"padding:8px 12px;font-size:12px;color:#374151;background:#F8FAFF;border-top:1px solid #E5E7EB\\"><b>Activity:</b> Total Purchases/Expenses (cash + credit): <b>"+sublFmt_(p.purch||0)+"</b> · Total Payments (cash paid): <b>"+sublFmt_(p.pay||0)+"</b></div>";
h+="</td></tr>";
}
}
h+="<tr style=\\"border-top:2px solid #1F3864;font-weight:700;background:#F8FAFF\\"><td class=\\"cf-line\\">TOTAL</td><td class=\\"cf-amount\\">-</td><td class=\\"cf-amount\\">"+sublFmt_(tDr)+"</td><td class=\\"cf-amount\\">"+sublFmt_(tCr)+"</td><td class=\\"cf-amount\\">"+sublFmt_(tEnd)+"</td></tr>";
h+="</tbody></table>";
h+="<div style=\\"font-size:11px;color:#6B7280;margin-top:8px\\">Click a "+(cust?"customer":"supplier")+" to see the running ledger and activity totals. Untagged collections/payments appear under (No Name) - tag the party name in the books for accurate per-party balances. Click an amount to open its journal entry.</div>";
h+="</div></div>";
document.getElementById("report").innerHTML=h;
}
function renderSUBLAging(){
var s=SUBL_STATE, cust=(s.view==="customer"), rows=sublAging_(), meta=getReportMetadata();
var h="<div class=\\"report-block\\"><div class=\\"report-card\\">";
h+="<div class=\\"report-hdr\\"><div class=\\"report-biz\\">"+esc(meta.businessName)+"</div><div class=\\"report-title\\">Aging of "+(cust?"Receivables":"Payables")+"</div><div class=\\"report-date\\">As of "+formatDateLong(s.asOf)+"</div><div class=\\"report-currency\\">(Amounts in Philippine Peso)</div></div>";
h+="<table class=\\"cf-table\\" style=\\"width:100%\\"><thead><tr><th class=\\"cf-descr\\">"+(cust?"Customer":"Supplier")+"</th><th class=\\"cf-amount-h\\">Current (1-30)</th><th class=\\"cf-amount-h\\">31-60</th><th class=\\"cf-amount-h\\">61-90</th><th class=\\"cf-amount-h\\">Over 90</th><th class=\\"cf-amount-h\\">Total</th></tr></thead><tbody>";
var t={cur:0,b1:0,b2:0,b3:0,tot:0};
for(var i=0;i<rows.length;i++){var r=rows[i];if(s.search&&r.name.toLowerCase().indexOf(s.search)<0)continue;
var noname=(r.name==="(No Name)");
t.cur+=r.cur;t.b1+=r.b1;t.b2+=r.b2;t.b3+=r.b3;t.tot+=r.tot;
h+="<tr><td class=\\"cf-line\\"><b>"+esc(r.name)+"</b>"+(noname?" <span style=\\"font-size:10px;color:#9C6F00;border:1px solid #9C6F00;border-radius:4px;padding:0 4px\\">UNTAGGED</span>":"")+(r.adv>0.005?" <span style=\\"font-size:10px;color:#1E7E34\\">(advance "+sublFmt_(r.adv)+")</span>":"")+"</td><td class=\\"cf-amount\\">"+sublFmt_(r.cur)+"</td><td class=\\"cf-amount\\">"+sublFmt_(r.b1)+"</td><td class=\\"cf-amount\\">"+sublFmt_(r.b2)+"</td><td class=\\"cf-amount\\">"+sublFmt_(r.b3)+"</td><td class=\\"cf-amount\\" style=\\"font-weight:700\\">"+sublFmt_(r.tot)+"</td></tr>";
}
h+="<tr style=\\"border-top:2px solid #1F3864;font-weight:700;background:#F8FAFF\\"><td class=\\"cf-line\\">TOTAL</td><td class=\\"cf-amount\\">"+sublFmt_(t.cur)+"</td><td class=\\"cf-amount\\">"+sublFmt_(t.b1)+"</td><td class=\\"cf-amount\\">"+sublFmt_(t.b2)+"</td><td class=\\"cf-amount\\">"+sublFmt_(t.b3)+"</td><td class=\\"cf-amount\\">"+sublFmt_(t.tot)+"</td></tr>";
h+="</tbody></table>";
h+="<div style=\\"font-size:11px;color:#6B7280;margin-top:8px\\">Open-item FIFO aging by transaction date (walang due-date/terms tracking). Collections/payments applied oldest-first. Untagged movements appear under (No Name) - tag the party for accurate aging.</div>";
h+="</div></div>";
document.getElementById("report").innerHTML=h;
}
`; }
function getSalesJS_() { return `
/* ════════ SALES-SIDE VAT VIEWS: SSJ (book) + SLS (RELIEF) + DAT ════════ */
/* ── Shared VAT period control bar + custom confirm modal ── */
var VAT_MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];
function vatQuarterMonths_(year,q){var base=(q-1)*3;var out=[];for(var i=1;i<=3;i++){var m=base+i;out.push({m:m,key:year+"-"+(m<10?"0"+m:""+m),name:VAT_MONTHS[m-1]});}return out;}
function vatActiveMonths_(st){var qm=vatQuarterMonths_(st.year,st.quarter);var out=[];for(var i=0;i<3;i++){if(st.checked[i])out.push(qm[i].key);}return out;}
function vatPeriodBar_(st,prefix){
var qm=vatQuarterMonths_(st.year,st.quarter);
var h="<div class=\\"ctrl-group\\"><label>Year</label><input type=\\"number\\" value=\\""+st.year+"\\" onchange=\\""+prefix+"SetYear_(this.value)\\" style=\\"width:84px\\"></div>";
h+="<div class=\\"ctrl-group\\"><label>Quarter</label><select onchange=\\""+prefix+"SetQuarter_(this.value)\\">";
var qn=["1st Quarter","2nd Quarter","3rd Quarter","4th Quarter"];
for(var q=1;q<=4;q++)h+="<option value=\\""+q+"\\""+(st.quarter===q?" selected":"")+">"+qn[q-1]+"</option>";
h+="</select></div>";
h+="<div class=\\"ctrl-group\\"><label>Months</label><div style=\\"display:flex;gap:12px;align-items:center;\\">";
for(var i=0;i<3;i++)h+="<label style=\\"font-weight:400;display:flex;gap:5px;align-items:center;cursor:pointer;font-size:13px;\\"><input type=\\"checkbox\\""+(st.checked[i]?" checked":"")+" onchange=\\""+prefix+"ToggleMonth_("+i+")\\">"+qm[i].name+"</label>";
h+="</div></div>";
return h;
}
function showConfirm_(title,msg,confirmLabel,onYes){
var old=document.getElementById("vatConfirmOverlay");if(old&&old.parentNode)old.parentNode.removeChild(old);
var ov=document.createElement("div");ov.id="vatConfirmOverlay";
ov.setAttribute("style","position:fixed;inset:0;background:rgba(15,23,42,0.45);display:flex;align-items:center;justify-content:center;z-index:9999;");
var box="<div style=\\"background:#fff;border-radius:12px;max-width:440px;width:90%;box-shadow:0 20px 50px rgba(0,0,0,0.28);overflow:hidden;\\">";
box+="<div style=\\"background:#1F3864;color:#fff;padding:14px 18px;font-size:15px;font-weight:600;\\">"+esc(title)+"</div>";
box+="<div style=\\"padding:18px;font-size:13px;color:#374151;line-height:1.5;\\">"+msg+"</div>";
box+="<div style=\\"display:flex;justify-content:flex-end;gap:10px;padding:0 18px 18px;\\"><button id=\\"vatCfgCancel\\" class=\\"ec-btn\\" style=\\"padding:8px 16px\\">Cancel</button><button id=\\"vatCfgYes\\" class=\\"ec-btn ec-btn-primary\\" style=\\"padding:8px 16px\\">"+esc(confirmLabel||"Confirm")+"</button></div></div>";
ov.innerHTML=box;document.body.appendChild(ov);
function close(){if(ov&&ov.parentNode)ov.parentNode.removeChild(ov);}
document.getElementById("vatCfgCancel").onclick=close;
ov.onclick=function(e){if(e.target===ov)close();};
document.getElementById("vatCfgYes").onclick=function(){close();onYes();};
}
var SLS_STATE={view:"report",year:0,quarter:1,checked:[true,true,true]};
function salesVat_(){ return (typeof DATA!=="undefined" && DATA && DATA.salesVat) ? DATA.salesVat : {ssjRows:[],slsRows:[],vatSummary:{},validation:{errors:[],warnings:[]}}; }
/* RELIEF amount: always 2dp with thousands sep, "0.00" for zero, (x) for negative */
function reliefAmt_(n){var x=n||0;var v=Math.abs(x).toFixed(2);v=v.replace(/\\B(?=(\\d{3})+(?!\\d))/g,",");return x<0?"("+v+")":v;}
/* Sanitize entered text for SLS report + DAT: strip non-ASCII/quote/period/comma/pipe, collapse spaces, UPPERCASE */
function slsSan_(s){return String(s==null?"":s).replace(/[^\\x20-\\x7E]/g,"").replace(/[".,|]/g,"").replace(/\\s+/g," ").trim().toUpperCase();}
function salesValNotice_(v){
if(!v) return "";
var nE=(v.errors||[]).length, nW=(v.warnings||[]).length;
if(!nE && !nW) return "";
var sev=nE?"red":"amber";
var msg=(nE?nE+" error"+(nE===1?"":"s"):"")+(nE&&nW?" \\u00b7 ":"")+(nW?nW+" warning"+(nW===1?"":"s"):"");
return "<div class=\\"alert-card "+(sev==="red"?"red":"")+"\\" style=\\"max-width:850px;margin:0 auto 10px\\"><div class=\\"alert-icon\\">"+(nE?"\\uD83D\\uDD34":"\\u26A0\\uFE0F")+"</div><div class=\\"alert-body\\"><div class=\\"alert-title\\">Sales validation: "+msg+"</div><div class=\\"alert-text\\">Open the Sales Validation view for details.</div></div></div>";
}
/* ── Subsidiary Sales Journal: BIR loose-leaf book format (Mo/Day = actual date) ── */
function renderSubsidiarySalesBook(from,to){
var sv=salesVat_();
var all=(sv.ssjRows||[]).filter(function(s){return s.date&&s.date>=from&&s.date<=to;});
all.sort(function(a,b){if(a.date!==b.date)return a.date<b.date?-1:1;return (a.ref||"")<(b.ref||"")?-1:1;});
var title="SUBSIDIARY SALES JOURNAL";
var h="<div class=\\"book-print\\">";
if(BOOK_STATE.showCover){h+=buildBookCover(title,from,to,"01");h+="<div class=\\"book-page-break\\"></div>";}
h+=buildBookHeader(title,from,to,"");
h+="<table class=\\"bk-tbl\\"><thead><tr>";
h+="<th>Ln</th><th>Mo</th><th>Day</th><th>Invoice/Ref</th><th>Customer / Address</th><th>TIN</th><th>Description</th><th>Exempt</th><th>Zero-Rated</th><th>Taxable (Net)</th><th>Output VAT</th><th>Total</th>";
h+="</tr></thead><tbody>";
if(!all.length){h+="<tr class=\\"bk-empty\\"><td colspan=\\"12\\">No sales-reportable transactions for the selected period.</td></tr>";}
var ln=1,prevYr="",prevMo="",prevDay="";
var tEx=0,tZe=0,tVa=0,tOv=0,tTo=0;
for(var i=0;i<all.length;i++){
var s=all[i];
var dt=(s.date||"").substring(0,10);
var yr=dt.substring(0,4),mo=parseInt(dt.substring(5,7))||0,day=parseInt(dt.substring(8,10))||0;
if(yr!==prevYr&&yr){h+="<tr class=\\"bk-yr-row\\"><td class=\\"bk-ln\\">"+(ln++)+"</td><td class=\\"bk-mo\\">"+yr+"</td><td class=\\"bk-day\\"></td><td class=\\"bk-particulars\\"></td><td class=\\"bk-particulars\\"></td><td class=\\"bk-pr\\"></td><td class=\\"bk-particulars\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-num\\"></td></tr>";prevYr=yr;prevMo="";prevDay="";}
var moStr="",dayStr="";
if(mo!==parseInt(prevMo)){moStr=bkMonthAbbr(mo);prevMo=mo;}
if(day!==parseInt(prevDay)||moStr){dayStr=day;prevDay=day;}
tEx+=s.exempt;tZe+=s.zero;tVa+=s.vatable;tOv+=s.outputVAT;tTo+=s.totalInvoice;
// Construct customer name: regName (Non-Indiv) or Last, First Middle (Indiv) or fallback to name
var custName;
if(s.regName){custName=s.regName;}
else if(s.lastName||s.firstName||s.middleName){var fnMn=[s.firstName||"",s.middleName||""].filter(function(x){return x;}).join(" ");custName=(s.lastName||"")+(s.lastName&&fnMn?", ":"")+fnMn;}
else{custName=s.name||"";}
var addr=(s.addr1||"")+(s.addr2?((s.addr1?", ":"")+s.addr2):"");
var custCell="<div>"+esc(custName)+"</div>"+(addr?"<div style=\\"font-size:10px;color:#6B7280;\\">"+esc(addr)+"</div>":"");
h+="<tr><td class=\\"bk-ln\\">"+(ln++)+"</td>";
h+="<td class=\\"bk-mo\\">"+moStr+"</td><td class=\\"bk-day\\">"+dayStr+"</td>";
h+="<td class=\\"bk-particulars\\">"+esc(s.ref)+"</td>";
h+="<td class=\\"bk-particulars\\">"+custCell+"</td>";
h+="<td class=\\"bk-pr\\">"+esc(s.tin)+"</td>";
h+="<td class=\\"bk-particulars\\" style=\\"font-size:10px;\\">"+esc(s.desc||"")+"</td>";
h+="<td class=\\"bk-num\\">"+bkFmt(s.exempt)+"</td><td class=\\"bk-num\\">"+bkFmt(s.zero)+"</td><td class=\\"bk-num\\">"+bkFmt(s.vatable)+"</td><td class=\\"bk-num\\">"+bkFmt(s.outputVAT)+"</td><td class=\\"bk-num\\">"+bkFmt(s.totalInvoice)+"</td></tr>";
}
if(all.length){
h+="<tr class=\\"bk-totals\\"><td colspan=\\"7\\" class=\\"bk-particulars\\" style=\\"text-align:right;padding-right:12px;\\">TOTAL</td>";
h+="<td class=\\"bk-num\\">"+bkFmt(tEx)+"</td><td class=\\"bk-num\\">"+bkFmt(tZe)+"</td><td class=\\"bk-num\\">"+bkFmt(tVa)+"</td><td class=\\"bk-num\\">"+bkFmt(tOv)+"</td><td class=\\"bk-num\\">"+bkFmt(tTo)+"</td></tr>";
}
h+="</tbody></table>";
if(BOOK_STATE.showSworn){h+="<div class=\\"book-page-break\\"></div>";h+=buildBookSworn(title,from,to,null);}
h+="</div>";
return h;
}
/* ── SLS owner block from Settings ── */
function slsOwner_(){
var s=DATA.settings||{};
var digits=String(s.tin||"").replace(/[^0-9]/g,"");
var fyStart=parseInt(s.fyStartMonth)||1;
var bc=String(s.branchCode||"").replace(/[^0-9]/g,"");
var branch4=bc?bc.slice(-4):"0000";
var cls=String(s.taxpayerClass||"").trim().toLowerCase();
var indiv=(cls.indexOf("non")===-1); // "individual" or blank => individual; "non-individual" => false
var last=indiv?String(s.ownerLast||""):"";
var first=indiv?String(s.ownerFirst||""):"";
var middle=indiv?String(s.ownerMiddle||""):"";
var regName=indiv?"":(String(s.registeredNameNonInd||"")||String(s.businessName||""));
var tradeName=String(s.businessName||"");
var regNameDisplay=indiv?(last+(first||middle?", "+[first,middle].filter(function(x){return x;}).join(" "):"")):regName;
var tinDisplay=(digits.length>=9)?(digits.substring(0,3)+"-"+digits.substring(3,6)+"-"+digits.substring(6,9)):String(s.tin||"");
return {
tin9:digits.substring(0,9),
tinDisplay:tinDisplay,
branch:branch4,
regName:regName,
regNameDisplay:regNameDisplay,
tradeName:tradeName,
last:last, first:first, middle:middle,
taxpayerClass:String(s.taxpayerClass||""),
address:String(s.addr1||s.address||"").trim(),
address2:String(s.addr2||"").trim(),
rdo:String(s.rdo||""),
rdoCode:(function(){var m=String(s.rdo||"").match(/(\\d{2,3}[A-Za-z]?)/);if(!m)return "";var c=m[1];if(/^\\d+$/.test(c)){while(c.length<3)c="0"+c;}return c;})(),
fyEndMonth:(fyStart===1?12:(fyStart-1))
};
}
function slsMonthName_(tm){if(!tm||tm.length<7)return"";var p=tm.split("-");var m=["January","February","March","April","May","June","July","August","September","October","November","December"];return m[parseInt(p[1])-1]+", "+p[0];}
function slsLastDay_(tm){if(!tm||tm.length<7)return"";var p=tm.split("-");var y=parseInt(p[0]),mo=parseInt(p[1]);var d=new Date(y,mo,0).getDate();var pad=function(n){return n<10?"0"+n:""+n;};return pad(mo)+"/"+pad(d)+"/"+y;}
/* ── Group SSJ rows by tax month ── */
function slsRowsByMonth_(ssjRows){
var map={},order=[];
for(var i=0;i<ssjRows.length;i++){var s=ssjRows[i];var tm=s.taxMonth||(s.date||"").substring(0,7);if(!map[tm]){map[tm]=[];order.push(tm);}map[tm].push(s);}
order.sort();
return {map:map,order:order};
}
/* ── SLS RELIEF report (per-transaction, per taxable month) ── */
function renderSlsReport_(){
var sv=salesVat_();var rows=sv.ssjRows||[];var o=slsOwner_();
var today=new Date();var mm=["January","February","March","April","May","June","July","August","September","October","November","December"];
var rptDate=today.getDate()+" "+mm[today.getMonth()]+" "+today.getFullYear();
var byM=slsRowsByMonth_(rows);
var h=salesValNotice_(sv.validation);
if(!rows.length){h+="<div class=\\"report-block\\"><div class=\\"is-empty-row\\" style=\\"padding:20px;text-align:center;color:#6B7280;\\">No sales-reportable transactions to list.</div></div>";return h;}
var active=vatActiveMonths_(SLS_STATE);var shown=0;
for(var mi=0;mi<byM.order.length;mi++){
var tm=byM.order[mi];if(active.indexOf(tm)===-1)continue;shown++;var list=byM.map[tm];
h+="<div class=\\"report-block\\" style=\\"max-width:980px;margin:0 auto 18px;background:#fff;padding:18px 20px;\\">";
h+="<div style=\\"font-size:12px;color:#111827;line-height:1.5;\\">";
h+="Bureau of Internal Revenue<br>Reconciliation of Listings for Enforcement System<br>Summary List of Sales<br>Report Date: "+esc(rptDate)+"</div>";
h+="<div style=\\"display:flex;justify-content:space-between;margin-top:12px;font-size:12px;color:#111827;\\">";
h+="<div><div><b>T.I.N. of Owner:</b> "+esc(o.tinDisplay)+"</div><div><b>Registered Name:</b> "+esc(o.regNameDisplay)+"</div><div><b>Trade Name:</b> "+esc(o.tradeName)+"</div></div>";
h+="<div style=\\"text-align:left;\\"><div><b>Owners Address:</b> "+esc(slsSan_(o.address))+"</div><div>"+esc(slsSan_(o.address2))+"</div></div>";
h+="</div>";
h+="<div style=\\"margin-top:10px;font-size:12px;\\"><b>Taxable Month:</b> <i>"+esc(slsMonthName_(tm))+"</i></div>";
h+="<table style=\\"width:100%;border-collapse:collapse;margin-top:8px;font-size:11px;\\">";
h+="<thead><tr style=\\"border-top:1px solid #111827;border-bottom:1px solid #111827;\\">";
var cols=["T.I.N.","Registered Name","Total Sales","Zero Rated","Exempt","Gross Taxable","Taxable Net","Output Tax"];
for(var ci=0;ci<cols.length;ci++){var num=ci>=2;h+="<th style=\\"padding:5px 8px;text-align:"+(num?"right":"left")+";font-weight:700;\\">"+cols[ci]+"</th>";}
h+="</tr></thead><tbody>";
var tTs=0,tZe=0,tEx=0,tGt=0,tTn=0,tOt=0;
for(var ri=0;ri<list.length;ri++){
var s=list[ri];
var totalSales=s.exempt+s.zero+s.vatable;
tTs+=totalSales;tZe+=s.zero;tEx+=s.exempt;tGt+=s.grossTaxable;tTn+=s.vatable;tOt+=s.outputVAT;
h+="<tr><td style=\\"padding:3px 8px;\\">"+esc(s.tin||"000-000-001")+"</td>";
h+="<td style=\\"padding:3px 8px;\\">"+esc(slsSan_(s.regName||s.name))+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;\\">"+reliefAmt_(totalSales)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;\\">"+reliefAmt_(s.zero)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;\\">"+reliefAmt_(s.exempt)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;\\">"+reliefAmt_(s.grossTaxable)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;\\">"+reliefAmt_(s.vatable)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;\\">"+reliefAmt_(s.outputVAT)+"</td></tr>";
}
h+="<tr style=\\"border-top:1px solid #111827;border-bottom:1px solid #111827;font-weight:700;\\">";
h+="<td colspan=\\"2\\" style=\\"padding:5px 8px;\\">Total for the Taxable Month</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tTs)+"</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tZe)+"</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tEx)+"</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tGt)+"</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tTn)+"</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tOt)+"</td></tr>";
h+="</tbody></table></div>";
}
if(!shown)h+="<div class=\\"report-block\\"><div style=\\"padding:18px;text-align:center;color:#6B7280;\\">No sales-reportable transactions for the selected period.</div></div>";
return h;
}
/* ── Build SLS DAT (RELIEF H/D records; per customer per month; BIR-correct split) ── */
function buildSlsDatFiles_(){
var sv=salesVat_();var rows=sv.ssjRows||[];var o=slsOwner_();
var active=vatActiveMonths_(SLS_STATE);
function q(s){return "\\""+slsSan_(s)+"\\"";}
function numH(n){return (n||0).toFixed(2);}
function numD(n){return (!n||Math.abs(n)<0.005)?"0":(n).toFixed(2);}
var byM=slsRowsByMonth_(rows);var files=[];
for(var mi=0;mi<byM.order.length;mi++){
var tm=byM.order[mi];
if(active.indexOf(tm)===-1)continue;
var list=byM.map[tm];var period=slsLastDay_(tm);
var hEx=0,hZe=0,hTax=0,hOv=0;var buckets={},border=[];
for(var ri=0;ri<list.length;ri++){
var s=list[ri];
hEx+=s.exempt;hZe+=s.zero;hTax+=s.vatable;hOv+=s.outputVAT;
var tin9=s.normalizedTin||"";var key,name,ctin;
if(tin9 && tin9!=="000000000"){key=tin9;name=s.regName||s.name||"";ctin=tin9;}
else{key="VARIOUS";name="VARIOUS CUSTOMERS";ctin="000000001";}
if(!buckets[key]){buckets[key]={ctin:ctin,name:name,last:s.lastName||"",first:s.firstName||"",middle:s.middleName||"",ex:0,ze:0,tax:0,ov:0};border.push(key);}
var b=buckets[key];b.ex+=s.exempt;b.ze+=s.zero;b.tax+=s.vatable;b.ov+=s.outputVAT;
}
var lines=[];
lines.push(["H","S",q(o.tin9),q(o.branch),q(o.regName),q(o.last),q(o.first),q(o.middle),q(o.address),q(o.address2),numH(hEx),numH(hZe),numH(hTax),numH(hOv),o.rdoCode,period,String(o.fyEndMonth)].join(","));
for(var bi=0;bi<border.length;bi++){var bk=buckets[border[bi]];var isIndiv=!!(bk.last||bk.first||bk.middle);lines.push(["D","S",q(bk.ctin),q(isIndiv?"":bk.name),isIndiv?q(bk.last):"",isIndiv?q(bk.first):"",isIndiv?q(bk.middle):"","","",numD(bk.ex),numD(bk.ze),numD(bk.tax),numD(bk.ov),o.tin9,period].join(","));}
var pp=tm.split("-");
files.push({tm:tm,filename:(o.tin9||"SLS")+"S"+pp[1]+pp[0]+".DAT",content:lines.join("\\n")});
}
return files;
}
function renderSlsDatView_(){
var files=buildSlsDatFiles_();
var h="<div class=\\"report-block\\" style=\\"max-width:980px;margin:0 auto;\\">";
if(!files.length){h+="<div style=\\"padding:18px;text-align:center;color:#6B7280;\\">No sales-reportable rows for the selected period.</div></div>";return h;}
h+="<div style=\\"display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;\\"><div style=\\"font-size:12px;color:#6B7280;\\">RELIEF SLS DAT - one file per taxable month (summarized per customer).</div>";
if(files.length>1)h+="<button class=\\"ctrl-btn\\" onclick=\\"btnFlash_(this,'Downloaded!');slsDownloadAll_()\\">Download All ("+files.length+")</button>";
h+="</div>";
for(var i=0;i<files.length;i++){var f=files[i];
h+="<div style=\\"border:1px solid #E2E8F0;border-radius:8px;margin-bottom:14px;overflow:hidden;\\">";
h+="<div style=\\"display:flex;justify-content:space-between;align-items:center;background:#F8FAFC;border-bottom:1px solid #E2E8F0;padding:8px 12px;\\"><span style=\\"font-family:Consolas,Monaco,monospace;font-size:12px;font-weight:600;color:#1F3864;\\">"+esc(f.filename)+"</span><button class=\\"ctrl-btn\\" onclick=\\"btnFlash_(this,'Downloaded!');slsDownloadOne_('"+f.tm+"')\\">Download .DAT</button></div>";
h+="<pre style=\\"margin:0;padding:14px;font-family:Consolas,Monaco,monospace;font-size:12px;white-space:pre-wrap;word-break:break-all;\\">"+esc(f.content)+"</pre></div>";
}
h+="</div>";return h;
}
function downloadText_(filename,text){var blob=new Blob([text],{type:"text/plain;charset=utf-8"});var url=URL.createObjectURL(blob);var a=document.createElement("a");a.href=url;a.download=filename;document.body.appendChild(a);a.click();document.body.removeChild(a);setTimeout(function(){URL.revokeObjectURL(url);},1000);}
function slsDownloadOne_(tm){var files=buildSlsDatFiles_();for(var i=0;i<files.length;i++){if(files[i].tm===tm){downloadText_(files[i].filename,files[i].content);return;}}}
function slsDownloadAll_(){
var files=buildSlsDatFiles_();if(!files.length)return;
if(files.length===1){downloadText_(files[0].filename,files[0].content);return;}
var list="";for(var i=0;i<files.length;i++)list+="<div style=\\"font-family:Consolas,Monaco,monospace;color:#1F3864;\\">"+esc(files[i].filename)+"</div>";
showConfirm_("Download all SLS DAT files","Ida-download ang <b>"+files.length+"</b> na .DAT file (isa kada buwan):<div style=\\"margin-top:8px;\\">"+list+"</div>","Download "+files.length+" files",function(){files.forEach(function(f,idx){setTimeout(function(){downloadText_(f.filename,f.content);},idx*500);});});
}
function slsSetYear_(v){var y=parseInt(v);if(y>=2000&&y<=2100){SLS_STATE.year=y;SLS_STATE.checked=[true,true,true];}renderSLS();}
function slsSetQuarter_(v){SLS_STATE.quarter=parseInt(v)||1;SLS_STATE.checked=[true,true,true];renderSLS();}
function slsToggleMonth_(i){SLS_STATE.checked[i]=!SLS_STATE.checked[i];renderSLS();}
function slsLazyInit_(){
if(SLS_STATE.year)return;
var sv=salesVat_();var rows=sv.ssjRows||[];var tm="";
for(var i=0;i<rows.length;i++){var t=rows[i].taxMonth||(rows[i].date||"").substring(0,7);if(t&&(!tm||t<tm))tm=t;}
if(tm&&tm.length>=7){var p=tm.split("-");SLS_STATE.year=parseInt(p[0]);SLS_STATE.quarter=Math.floor((parseInt(p[1])-1)/3)+1;}
else{var d=new Date();SLS_STATE.year=d.getFullYear();SLS_STATE.quarter=Math.floor(d.getMonth()/3)+1;}
}
function setSlsView_(v){SLS_STATE.view=v;renderSLS();}
function renderSLS(){
slsLazyInit_();
var c="<div class=\\"ctrl-bar\\">";
c+="<div class=\\"ctrl-group\\"><label>View</label><div style=\\"display:flex;gap:6px;\\">";
c+="<button class=\\"ctrl-btn"+(SLS_STATE.view==="report"?" on":"")+"\\" onclick=\\"setSlsView_('report')\\">SLS Report</button>";
c+="<button class=\\"ctrl-btn"+(SLS_STATE.view==="dat"?" on":"")+"\\" onclick=\\"setSlsView_('dat')\\">DAT File</button>";
c+="<button class=\\"ctrl-btn"+(SLS_STATE.view==="val"?" on":"")+"\\" onclick=\\"setSlsView_('val')\\">Validation</button>";
c+="</div></div>";
c+=vatPeriodBar_(SLS_STATE,"sls");
c+="</div>";
document.getElementById("controls").innerHTML=c;
var h;
if(SLS_STATE.view==="report")h=renderSlsReport_();
else if(SLS_STATE.view==="dat")h=renderSlsDatView_();
else h=renderSalesValidation_();
document.getElementById("report").innerHTML=h;
}
/* ── Sales Validation (returns HTML; shown as 3rd toggle inside SLS) ── */
function renderSalesValidation_(){
var sv=salesVat_(); var v=sv.validation||{errors:[],warnings:[]};
var h="<div class=\\"report-block\\">";
if((!v.errors||!v.errors.length)&&(!v.warnings||!v.warnings.length)){
h+="<div class=\\"alert-card green\\"><div class=\\"alert-icon\\">\\u2705</div><div class=\\"alert-body\\"><div class=\\"alert-title\\">All clear</div><div class=\\"alert-text\\">No errors or warnings on the sales-side books.</div></div></div>";
}else{
var e=v.errors||[],w=v.warnings||[];
for(var i=0;i<e.length;i++)h+="<div class=\\"alert-card red\\" style=\\"margin-bottom:8px\\"><div class=\\"alert-icon\\">\\uD83D\\uDD34</div><div class=\\"alert-body\\"><div class=\\"alert-title\\">Error</div><div class=\\"alert-text\\">"+esc(e[i].msg||"")+"</div></div></div>";
for(var j=0;j<w.length;j++)h+="<div class=\\"alert-card\\" style=\\"margin-bottom:8px\\"><div class=\\"alert-icon\\">\\u26A0\\uFE0F</div><div class=\\"alert-body\\"><div class=\\"alert-title\\">Warning</div><div class=\\"alert-text\\">"+esc(w[j].msg||"")+"</div></div></div>";
}
h+="</div>";return h;
}
/* ════════ PURCHASE-SIDE VAT VIEWS: SPJ (book) + SLP (RELIEF) + DAT ════════ */
var SLP_STATE={view:"report",year:0,quarter:1,checked:[true,true,true]};
function purchaseVat_(){ return (typeof DATA!=="undefined" && DATA && DATA.purchaseVat) ? DATA.purchaseVat : {spjRows:[],slpRows:[],vatSummary:{},validation:{errors:[],warnings:[]}}; }
function purchaseValNotice_(v){
if(!v) return "";
var nE=(v.errors||[]).length, nW=(v.warnings||[]).length;
if(!nE && !nW) return "";
var sev=nE?"red":"amber";
var msg=(nE?nE+" error"+(nE===1?"":"s"):"")+(nE&&nW?" \\u00b7 ":"")+(nW?nW+" warning"+(nW===1?"":"s"):"");
return "<div class=\\"alert-card "+(sev==="red"?"red":"")+"\\" style=\\"max-width:850px;margin:0 auto 10px\\"><div class=\\"alert-icon\\">"+(nE?"\\uD83D\\uDD34":"\\u26A0\\uFE0F")+"</div><div class=\\"alert-body\\"><div class=\\"alert-title\\">Purchase validation: "+msg+"</div><div class=\\"alert-text\\">Open the Purchase Validation view for details.</div></div></div>";
}
/* ── Subsidiary Purchase Journal: BIR loose-leaf book format (Mo/Day = actual date) ── */
function renderSubsidiaryPurchaseBook(from,to){
var pv=purchaseVat_();
var all=(pv.spjRows||[]).filter(function(s){return s.date&&s.date>=from&&s.date<=to;});
all.sort(function(a,b){if(a.date!==b.date)return a.date<b.date?-1:1;return (a.ref||"")<(b.ref||"")?-1:1;});
var title="SUBSIDIARY PURCHASE JOURNAL";
var h="<div class=\\"book-print\\">";
if(BOOK_STATE.showCover){h+=buildBookCover(title,from,to,"01");h+="<div class=\\"book-page-break\\"></div>";}
h+=buildBookHeader(title,from,to,"");
h+="<table class=\\"bk-tbl\\"><thead><tr>";
h+="<th>Ln</th><th>Mo</th><th>Day</th><th>Invoice/Ref</th><th>Supplier / Address</th><th>TIN</th><th>Description</th><th>Exempt</th><th>Zero-Rated</th><th>Taxable (Net)</th><th>Input Tax</th><th>Total</th>";
h+="</tr></thead><tbody>";
if(!all.length){h+="<tr class=\\"bk-empty\\"><td colspan=\\"12\\">No purchase-reportable transactions for the selected period.</td></tr>";}
var ln=1,prevYr="",prevMo="",prevDay="";
var tEx=0,tZe=0,tNet=0,tIn=0,tTo=0;
for(var i=0;i<all.length;i++){
var s=all[i];
var dt=(s.date||"").substring(0,10);
var yr=dt.substring(0,4),mo=parseInt(dt.substring(5,7))||0,day=parseInt(dt.substring(8,10))||0;
if(yr!==prevYr&&yr){h+="<tr class=\\"bk-yr-row\\"><td class=\\"bk-ln\\">"+(ln++)+"</td><td class=\\"bk-mo\\">"+yr+"</td><td class=\\"bk-day\\"></td><td class=\\"bk-particulars\\"></td><td class=\\"bk-particulars\\"></td><td class=\\"bk-pr\\"></td><td class=\\"bk-particulars\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-num\\"></td><td class=\\"bk-num\\"></td></tr>";prevYr=yr;prevMo="";prevDay="";}
var moStr="",dayStr="";
if(mo!==parseInt(prevMo)){moStr=bkMonthAbbr(mo);prevMo=mo;}
if(day!==parseInt(prevDay)||moStr){dayStr=day;prevDay=day;}
tEx+=s.exempt;tZe+=s.zero;tNet+=s.taxableNet;tIn+=s.inputTax;tTo+=s.totalInvoice;
// Construct supplier name: regName (Non-Indiv) or Last, First Middle (Indiv) or fallback
var supName;
if(s.regName){supName=s.regName;}
else if(s.lastName||s.firstName||s.middleName){var fnMn=[s.firstName||"",s.middleName||""].filter(function(x){return x;}).join(" ");supName=(s.lastName||"")+(s.lastName&&fnMn?", ":"")+fnMn;}
else{supName=s.name||"";}
var addr=(s.addr1||"")+(s.addr2?((s.addr1?", ":"")+s.addr2):"");
var supCell="<div>"+esc(supName)+"</div>"+(addr?"<div style=\\"font-size:10px;color:#6B7280;\\">"+esc(addr)+"</div>":"");
h+="<tr><td class=\\"bk-ln\\">"+(ln++)+"</td>";
h+="<td class=\\"bk-mo\\">"+moStr+"</td><td class=\\"bk-day\\">"+dayStr+"</td>";
h+="<td class=\\"bk-particulars\\">"+esc(s.ref)+"</td>";
h+="<td class=\\"bk-particulars\\">"+supCell+"</td>";
h+="<td class=\\"bk-pr\\">"+esc(s.tin)+"</td>";
h+="<td class=\\"bk-particulars\\" style=\\"font-size:10px;\\">"+esc(s.desc||"")+"</td>";
h+="<td class=\\"bk-num\\">"+bkFmt(s.exempt)+"</td><td class=\\"bk-num\\">"+bkFmt(s.zero)+"</td><td class=\\"bk-num\\">"+bkFmt(s.taxableNet)+"</td><td class=\\"bk-num\\">"+bkFmt(s.inputTax)+"</td><td class=\\"bk-num\\">"+bkFmt(s.totalInvoice)+"</td></tr>";
}
if(all.length){
h+="<tr class=\\"bk-totals\\"><td colspan=\\"7\\" class=\\"bk-particulars\\" style=\\"text-align:right;padding-right:12px;\\">TOTAL</td>";
h+="<td class=\\"bk-num\\">"+bkFmt(tEx)+"</td><td class=\\"bk-num\\">"+bkFmt(tZe)+"</td><td class=\\"bk-num\\">"+bkFmt(tNet)+"</td><td class=\\"bk-num\\">"+bkFmt(tIn)+"</td><td class=\\"bk-num\\">"+bkFmt(tTo)+"</td></tr>";
}
h+="</tbody></table>";
if(BOOK_STATE.showSworn){h+="<div class=\\"book-page-break\\"></div>";h+=buildBookSworn(title,from,to,null);}
h+="</div>";
return h;
}
/* ── SLP RELIEF report (per-transaction, per taxable month) ── */
function renderSlpReport_(){
var pv=purchaseVat_();var rows=pv.spjRows||[];var o=slsOwner_();
var today=new Date();var mm=["January","February","March","April","May","June","July","August","September","October","November","December"];
var rptDate=today.getDate()+" "+mm[today.getMonth()]+" "+today.getFullYear();
var byM=slsRowsByMonth_(rows);
var h=purchaseValNotice_(pv.validation);
if(!rows.length){h+="<div class=\\"report-block\\"><div class=\\"is-empty-row\\" style=\\"padding:20px;text-align:center;color:#6B7280;\\">No purchase-reportable transactions to list.</div></div>";return h;}
var active=vatActiveMonths_(SLP_STATE);var shown=0;
for(var mi=0;mi<byM.order.length;mi++){
var tm=byM.order[mi];if(active.indexOf(tm)===-1)continue;shown++;var list=byM.map[tm];
h+="<div class=\\"report-block\\" style=\\"max-width:1040px;margin:0 auto 18px;background:#fff;padding:18px 20px;\\">";
h+="<div style=\\"font-size:12px;color:#111827;line-height:1.5;\\">";
h+="Bureau of Internal Revenue<br>Reconciliation of Listings for Enforcement System<br>Summary List of Purchases<br>Report Date: "+esc(rptDate)+"</div>";
h+="<div style=\\"display:flex;justify-content:space-between;margin-top:12px;font-size:12px;color:#111827;\\">";
h+="<div><div><b>T.I.N. of Owner:</b> "+esc(o.tinDisplay)+"</div><div><b>Registered Name:</b> "+esc(o.regNameDisplay)+"</div><div><b>Trade Name:</b> "+esc(o.tradeName)+"</div></div>";
h+="<div style=\\"text-align:left;\\"><div><b>Owners Address:</b> "+esc(slsSan_(o.address))+"</div><div>"+esc(slsSan_(o.address2))+"</div></div>";
h+="</div>";
h+="<div style=\\"margin-top:10px;font-size:12px;\\"><b>Taxable Month:</b> <i>"+esc(slsMonthName_(tm))+"</i></div>";
h+="<table style=\\"width:100%;border-collapse:collapse;margin-top:8px;font-size:11px;\\">";
h+="<thead><tr style=\\"border-top:1px solid #111827;border-bottom:1px solid #111827;\\">";
var cols=["T.I.N.","Registered Name","Total Purchase","Zero Rated","Exempt","Gross Taxable","Services / Goods-Other / Capital","Taxable Net","Input Tax"];
for(var ci=0;ci<cols.length;ci++){var num=ci>=2&&ci!==6;h+="<th style=\\"padding:5px 8px;text-align:"+(num?"right":(ci===6?"center":"left"))+";font-weight:700;\\">"+cols[ci]+"</th>";}
h+="</tr></thead><tbody>";
var tTp=0,tZe=0,tEx=0,tGt=0,tTn=0,tIn=0;
for(var ri=0;ri<list.length;ri++){
var s=list[ri];
tTp+=s.totalPurchases;tZe+=s.zero;tEx+=s.exempt;tGt+=s.grossTaxable;tTn+=s.taxableNet;tIn+=s.inputTax;
h+="<tr><td style=\\"padding:3px 8px;vertical-align:top;\\">"+esc(s.tin||"000-000-000")+"</td>";
h+="<td style=\\"padding:3px 8px;vertical-align:top;\\">"+esc(slsSan_(s.regName||s.name))+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;vertical-align:top;\\">"+reliefAmt_(s.totalPurchases)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;vertical-align:top;\\">"+reliefAmt_(s.zero)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;vertical-align:top;\\">"+reliefAmt_(s.exempt)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;vertical-align:top;\\">"+reliefAmt_(s.grossTaxable)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;vertical-align:top;\\">"+reliefAmt_(s.services)+"<br>"+reliefAmt_(s.goods)+"<br>"+reliefAmt_(s.capital)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;vertical-align:top;\\">"+reliefAmt_(s.taxableNet)+"</td>";
h+="<td style=\\"padding:3px 8px;text-align:right;vertical-align:top;\\">"+reliefAmt_(s.inputTax)+"</td></tr>";
}
h+="<tr style=\\"border-top:1px solid #111827;border-bottom:1px solid #111827;font-weight:700;\\">";
h+="<td colspan=\\"2\\" style=\\"padding:5px 8px;\\">Taxable Month Totals</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tTp)+"</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tZe)+"</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tEx)+"</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tGt)+"</td>";
h+="<td style=\\"padding:5px 8px;\\"></td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tTn)+"</td>";
h+="<td style=\\"padding:5px 8px;text-align:right;\\">"+reliefAmt_(tIn)+"</td></tr>";
h+="</tbody></table></div>";
}
if(!shown)h+="<div class=\\"report-block\\"><div style=\\"padding:18px;text-align:center;color:#6B7280;\\">No purchase-reportable transactions for the selected period.</div></div>";
return h;
}
/* ── Build SLP DAT (RELIEF H/D records; per supplier per month).
H amounts (8): exempt, zero, services, capital, goods-other, inputTax, inputTax, 0.00
D amounts (6): exempt, zero, services, capital, goods-other, inputTax. D has NO branch. ── */
function buildSlpDatFiles_(){
var pv=purchaseVat_();var rows=pv.spjRows||[];var o=slsOwner_();
var active=vatActiveMonths_(SLP_STATE);
function q(s){return "\\""+slsSan_(s)+"\\"";}
function q30(s){return "\\""+slsSan_(s).substring(0,30)+"\\"";}
function numH(n){return (n||0).toFixed(2);}
function numD(n){return (!n||Math.abs(n)<0.005)?"0":(n).toFixed(2);}
var byM=slsRowsByMonth_(rows);var files=[];
for(var mi=0;mi<byM.order.length;mi++){
var tm=byM.order[mi];
if(active.indexOf(tm)===-1)continue;
var list=byM.map[tm];var period=slsLastDay_(tm);
var hEx=0,hZe=0,hSv=0,hCa=0,hGo=0,hIn=0;var buckets={},border=[];
for(var ri=0;ri<list.length;ri++){
var s=list[ri];
hEx+=s.exempt;hZe+=s.zero;hSv+=s.services;hCa+=s.capital;hGo+=s.goods;hIn+=s.inputTax;
var tin9=s.normalizedTin||"";var key,name,ctin;
if(tin9 && tin9!=="000000000"){key=tin9;name=s.regName||s.name||"";ctin=tin9;}
else{key="VARIOUS";name="VARIOUS SUPPLIERS";ctin="000000000";}
if(!buckets[key]){buckets[key]={ctin:ctin,name:name,last:s.lastName||"",first:s.firstName||"",middle:s.middleName||"",addr1:s.addr1,addr2:s.addr2,ex:0,ze:0,sv:0,ca:0,go:0,it:0};border.push(key);}
var b=buckets[key];b.ex+=s.exempt;b.ze+=s.zero;b.sv+=s.services;b.ca+=s.capital;b.go+=s.goods;b.it+=s.inputTax;
}
var lines=[];
lines.push(["H","P",q(o.tin9),q(o.branch),q(o.regName),q(o.last),q(o.first),q(o.middle),q(o.address),q(o.address2),numH(hEx),numH(hZe),numH(hSv),numH(hCa),numH(hGo),numH(hIn),numH(hIn),numH(0),o.rdoCode,period,String(o.fyEndMonth)].join(","));
for(var bi=0;bi<border.length;bi++){var bk=buckets[border[bi]];var isIndiv=!!(bk.last||bk.first||bk.middle);lines.push(["D","P",q(bk.ctin),q(isIndiv?"":bk.name),isIndiv?q(bk.last):q(""),isIndiv?q(bk.first):q(""),isIndiv?q(bk.middle):q(""),q30(bk.addr1),q30(bk.addr2),numD(bk.ex),numD(bk.ze),numD(bk.sv),numD(bk.ca),numD(bk.go),numD(bk.it),o.tin9,period].join(","));}
var pp=tm.split("-");
files.push({tm:tm,filename:(o.tin9||"SLP")+"P"+pp[1]+pp[0]+".DAT",content:lines.join("\\n")});
}
return files;
}
function renderSlpDatView_(){
var files=buildSlpDatFiles_();
var h="<div class=\\"report-block\\" style=\\"max-width:1040px;margin:0 auto;\\">";
if(!files.length){h+="<div style=\\"padding:18px;text-align:center;color:#6B7280;\\">No purchase-reportable rows for the selected period.</div></div>";return h;}
h+="<div style=\\"display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;\\"><div style=\\"font-size:12px;color:#6B7280;\\">RELIEF SLP DAT - one file per taxable month (summarized per supplier).</div>";
if(files.length>1)h+="<button class=\\"ctrl-btn\\" onclick=\\"btnFlash_(this,'Downloaded!');slpDownloadAll_()\\">Download All ("+files.length+")</button>";
h+="</div>";
for(var i=0;i<files.length;i++){var f=files[i];
h+="<div style=\\"border:1px solid #E2E8F0;border-radius:8px;margin-bottom:14px;overflow:hidden;\\">";
h+="<div style=\\"display:flex;justify-content:space-between;align-items:center;background:#F8FAFC;border-bottom:1px solid #E2E8F0;padding:8px 12px;\\"><span style=\\"font-family:Consolas,Monaco,monospace;font-size:12px;font-weight:600;color:#1F3864;\\">"+esc(f.filename)+"</span><button class=\\"ctrl-btn\\" onclick=\\"btnFlash_(this,'Downloaded!');slpDownloadOne_('"+f.tm+"')\\">Download .DAT</button></div>";
h+="<pre style=\\"margin:0;padding:14px;font-family:Consolas,Monaco,monospace;font-size:12px;white-space:pre-wrap;word-break:break-all;\\">"+esc(f.content)+"</pre></div>";
}
h+="</div>";return h;
}
function slpDownloadOne_(tm){var files=buildSlpDatFiles_();for(var i=0;i<files.length;i++){if(files[i].tm===tm){downloadText_(files[i].filename,files[i].content);return;}}}
function slpDownloadAll_(){
var files=buildSlpDatFiles_();if(!files.length)return;
if(files.length===1){downloadText_(files[0].filename,files[0].content);return;}
var list="";for(var i=0;i<files.length;i++)list+="<div style=\\"font-family:Consolas,Monaco,monospace;color:#1F3864;\\">"+esc(files[i].filename)+"</div>";
showConfirm_("Download all SLP DAT files","Ida-download ang <b>"+files.length+"</b> na .DAT file (isa kada buwan):<div style=\\"margin-top:8px;\\">"+list+"</div>","Download "+files.length+" files",function(){files.forEach(function(f,idx){setTimeout(function(){downloadText_(f.filename,f.content);},idx*500);});});
}
function slpSetYear_(v){var y=parseInt(v);if(y>=2000&&y<=2100){SLP_STATE.year=y;SLP_STATE.checked=[true,true,true];}renderSLP();}
function slpSetQuarter_(v){SLP_STATE.quarter=parseInt(v)||1;SLP_STATE.checked=[true,true,true];renderSLP();}
function slpToggleMonth_(i){SLP_STATE.checked[i]=!SLP_STATE.checked[i];renderSLP();}
function slpLazyInit_(){
if(SLP_STATE.year)return;
var pv=purchaseVat_();var rows=pv.spjRows||[];var tm="";
for(var i=0;i<rows.length;i++){var t=rows[i].taxMonth||(rows[i].date||"").substring(0,7);if(t&&(!tm||t<tm))tm=t;}
if(tm&&tm.length>=7){var p=tm.split("-");SLP_STATE.year=parseInt(p[0]);SLP_STATE.quarter=Math.floor((parseInt(p[1])-1)/3)+1;}
else{var d=new Date();SLP_STATE.year=d.getFullYear();SLP_STATE.quarter=Math.floor(d.getMonth()/3)+1;}
}
function setSlpView_(v){SLP_STATE.view=v;renderSLP();}
function renderSLP(){
slpLazyInit_();
var c="<div class=\\"ctrl-bar\\">";
c+="<div class=\\"ctrl-group\\"><label>View</label><div style=\\"display:flex;gap:6px;\\">";
c+="<button class=\\"ctrl-btn"+(SLP_STATE.view==="report"?" on":"")+"\\" onclick=\\"setSlpView_('report')\\">SLP Report</button>";
c+="<button class=\\"ctrl-btn"+(SLP_STATE.view==="dat"?" on":"")+"\\" onclick=\\"setSlpView_('dat')\\">DAT File</button>";
c+="<button class=\\"ctrl-btn"+(SLP_STATE.view==="val"?" on":"")+"\\" onclick=\\"setSlpView_('val')\\">Validation</button>";
c+="</div></div>";
c+=vatPeriodBar_(SLP_STATE,"slp");
c+="</div>";
document.getElementById("controls").innerHTML=c;
var h;
if(SLP_STATE.view==="report")h=renderSlpReport_();
else if(SLP_STATE.view==="dat")h=renderSlpDatView_();
else h=renderPurchaseValidation_();
document.getElementById("report").innerHTML=h;
}
/* ── Purchase Validation (returns HTML; shown as 3rd toggle inside SLP) ── */
function renderPurchaseValidation_(){
var pv=purchaseVat_(); var v=pv.validation||{errors:[],warnings:[]};
var h="<div class=\\"report-block\\">";
if((!v.errors||!v.errors.length)&&(!v.warnings||!v.warnings.length)){
h+="<div class=\\"alert-card green\\"><div class=\\"alert-icon\\">\\u2705</div><div class=\\"alert-body\\"><div class=\\"alert-title\\">All clear</div><div class=\\"alert-text\\">No errors or warnings on the purchase-side books.</div></div></div>";
}else{
var e=v.errors||[],w=v.warnings||[];
for(var i=0;i<e.length;i++)h+="<div class=\\"alert-card red\\" style=\\"margin-bottom:8px\\"><div class=\\"alert-icon\\">\\uD83D\\uDD34</div><div class=\\"alert-body\\"><div class=\\"alert-title\\">Error</div><div class=\\"alert-text\\">"+esc(e[i].msg||"")+"</div></div></div>";
for(var j=0;j<w.length;j++)h+="<div class=\\"alert-card\\" style=\\"margin-bottom:8px\\"><div class=\\"alert-icon\\">\\u26A0\\uFE0F</div><div class=\\"alert-body\\"><div class=\\"alert-title\\">Warning</div><div class=\\"alert-text\\">"+esc(w[j].msg||"")+"</div></div></div>";
}
h+="</div>";return h;
}
`; }
/* ════════ 2551Q - Quarterly Percentage Tax Return (client JS module) ════════ */
function getReturns2551JS_() { return `
var Q2551_STATE={taxYear:0,viewMode:"Q1",amended:false,perQuarter:{}};
function fmt2551(n){if(n===null||n===undefined||n==="")return"";if(typeof n!=="number")n=parseFloat(n)||0;return n.toLocaleString("en-PH",{minimumFractionDigits:2,maximumFractionDigits:2});}
function r2_2551(n){return Math.round((n||0)*100)/100;}
function q2551Range(taxYear,quarter){var m={Q1:["-01-01","-03-31"],Q2:["-04-01","-06-30"],Q3:["-07-01","-09-30"],Q4:["-10-01","-12-31"]};var r=m[quarter]||m.Q1;return{from:taxYear+r[0],to:taxYear+r[1]};}
function parseAtc2551_(tag){var t=String(tag||"").trim();if(!t)return{code:"",rate:0,label:""};var code=(t.split(/\\s*-\\s*/)[0]||"").replace(/\\s+/g,"").toUpperCase();var rm=t.match(/(\\d+(?:\\.\\d+)?)\\s*%/);var rate=rm?(parseFloat(rm[1])/100):0;return{code:code,rate:rate,label:t};}
function compute2551Q(jeRows,coa,taxYear,quarter){
var rg=q2551Range(taxYear,quarter);var acctTag={};
for(var ci=0;ci<(coa||[]).length;ci++){var a=coa[ci];if(!a||!a.title)continue;if(a.active&&String(a.active).toUpperCase()==="NO")continue;var pt=String(a.ptTaxLine||a.taxLine2551||"").trim();if(pt)acctTag[String(a.title).trim()]=pt;}
var byTag={},order=[];
for(var j=0;j<(jeRows||[]).length;j++){var je=jeRows[j];if(!je||!je.date)continue;if(je.date<rg.from||je.date>rg.to)continue;if(je.status&&String(je.status).toLowerCase()!=="posted")continue;var tag=acctTag[je.account];if(!tag)continue;if(byTag[tag]===undefined){byTag[tag]=0;order.push(tag);}byTag[tag]+=((je.credit||0)-(je.debit||0));}
var rows=[],totalTax=0;
for(var i=0;i<order.length;i++){var tg=order[i];var p=parseAtc2551_(tg);var base=r2_2551(byTag[tg]);var td=r2_2551(base*p.rate);totalTax+=td;rows.push({tag:tg,code:p.code,rate:p.rate,base:base,taxDue:td});}
return {rows:rows,totalTax:r2_2551(totalTax)};
}
function q2551Quarters(vm){var order=["Q1","Q2","Q3","Q4"];var ret=[];order.forEach(function(q){if((vm||"").indexOf(q)>=0)ret.push(q);});return ret.length?ret:["Q1"];}
function toggle2551Quarter(q,on){var order=["Q1","Q2","Q3","Q4"];if(order.indexOf(q)<0)return;var cur={};order.forEach(function(qq){if(Q2551_STATE.viewMode.indexOf(qq)>=0)cur[qq]=true;});if(on)cur[q]=true;else delete cur[q];var act=order.filter(function(qq){return cur[qq];});if(!act.length){cur[q]=true;act=[q];}Q2551_STATE.viewMode=act.join("");build2551Q();}
function q2551Field(q,f){var pq=Q2551_STATE.perQuarter[q]||(Q2551_STATE.perQuarter[q]={});return pq[f]||0;}
function set2551Field(q,f,v){if(!Q2551_STATE.perQuarter[q])Q2551_STATE.perQuarter[q]={};Q2551_STATE.perQuarter[q][f]=parseFloat(v)||0;render2551Q();}
function q2551LazyInit(){if(Q2551_STATE.taxYear)return;var tm="";var je=(typeof DATA!=="undefined"&&DATA&&DATA.jeRows)?DATA.jeRows:[];for(var i=0;i<je.length;i++){var d=je[i].date;if(d&&(!tm||d<tm))tm=d;}Q2551_STATE.taxYear=(tm&&tm.length>=4)?parseInt(tm.substring(0,4)):new Date().getFullYear();}function build2551Q(){build2551QControls();render2551Q();}
function build2551QControls(){
q2551LazyInit();var s=Q2551_STATE;var yrNow=new Date().getFullYear();
var h="<div class=\\"ctrl-bar\\">";
h+="<div class=\\"ctrl-group\\"><label>1. Tax Year</label><input type=\\"number\\" value=\\""+s.taxYear+"\\" min=\\"2018\\" max=\\""+(yrNow+1)+"\\" onchange=\\"Q2551_STATE.taxYear=parseInt(this.value)||"+yrNow+";build2551Q()\\" style=\\"width:84px\\"></div>";
h+="<div class=\\"ctrl-group\\"><label>3. Quarter</label><div style=\\"display:flex;gap:10px;align-items:center;\\">";
["Q1","Q2","Q3","Q4"].forEach(function(q){var ck=s.viewMode.indexOf(q)>=0;h+="<label style=\\"font-weight:400;display:flex;gap:4px;align-items:center;cursor:pointer;font-size:13px;\\"><input type=\\"checkbox\\" "+(ck?"checked":"")+" onchange=\\"toggle2551Quarter('"+q+"',this.checked)\\">"+q+"</label>";});
h+="</div></div>";
h+="<div class=\\"ctrl-group\\"><label>4. Amended?</label><select onchange=\\"Q2551_STATE.amended=this.value==='yes';render2551Q()\\"><option value=\\"no\\""+(!s.amended?" selected":"")+">No</option><option value=\\"yes\\""+(s.amended?" selected":"")+">Yes</option></select></div>";
h+="</div>";
document.getElementById("controls").innerHTML=h;
}
function render2551Q(){
var s=Q2551_STATE;var quarters=q2551Quarters(s.viewMode);
var settings=(typeof DATA!=="undefined"&&DATA&&DATA.settings)?DATA.settings:{};
var je=(typeof DATA!=="undefined"&&DATA&&DATA.jeRows)?DATA.jeRows:[];
var coa=(typeof DATA!=="undefined"&&DATA&&DATA.coa)?DATA.coa:[];
var res={};for(var i=0;i<quarters.length;i++)res[quarters[i]]=compute2551Q(je,coa,s.taxYear,quarters[i]);
var tagInfo={},tagOrder=[];
for(var qi=0;qi<quarters.length;qi++){var rws=res[quarters[qi]].rows;for(var ri=0;ri<rws.length;ri++){var rw=rws[ri];if(!tagInfo[rw.tag]){tagInfo[rw.tag]={code:rw.code,rate:rw.rate,label:rw.tag};tagOrder.push(rw.tag);}}}
function baseFor(q,tag){var rr=res[q].rows;for(var i=0;i<rr.length;i++)if(rr[i].tag===tag)return rr[i].base;return 0;}
function colsHdr(){var x="";for(var i=0;i<quarters.length;i++)x+="<th class=\\"num\\">"+quarters[i]+"</th>";return x;}
function numCol(fn){var x="";for(var i=0;i<quarters.length;i++)x+="<td class=\\"num\\">"+fn(quarters[i])+"</td>";return x;}
function inpCol(field){var x="";for(var i=0;i<quarters.length;i++){var q=quarters[i];var v=q2551Field(q,field);x+="<td class=\\"num\\"><input type=\\"number\\" step=\\"0.01\\" value=\\""+(v||"")+"\\" placeholder=\\"0.00\\" onchange=\\"set2551Field('"+q+"','"+field+"',this.value)\\" class=\\"q1701-inp\\"></td>";}return x;}
function taxDue(q){return res[q].totalTax;}
function credits(q){return q2551Field(q,"cwt")+q2551Field(q,"prior")+q2551Field(q,"other");}
function stillPay(q){return r2_2551(taxDue(q)-credits(q));}
function penalties(q){return q2551Field(q,"surcharge")+q2551Field(q,"interest")+q2551Field(q,"compromise");}
function totalPay(q){return r2_2551(stillPay(q)+penalties(q));}
var h="<div class=\\"q1701-report\\">";
h+="<div class=\\"q1701-section q1701-titleblock\\"><div class=\\"q1701-bn\\">"+esc(settings.businessName||"[Business Name]")+"</div><div class=\\"q1701-ttl\\">BIR Form 2551Q - Quarterly Percentage Tax Return</div><div class=\\"q1701-meta\\">Tax Year "+s.taxYear+" · "+quarters.join(", ")+(s.amended?" · AMENDED":"")+"</div></div>";
h+="<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">Part I - Background Information</div><table class=\\"q1701-bg\\">";
h+="<tr><td class=\\"lbl\\">6. TIN</td><td>"+esc(settings.tin||"")+"</td><td class=\\"lbl\\">7. RDO Code</td><td>"+esc(settings.rdo||"")+"</td></tr>";
h+="<tr><td class=\\"lbl\\">8. Taxpayer Name</td><td colspan=\\"3\\">"+esc(filer2550Name_(settings))+"</td></tr>";
h+="<tr><td class=\\"lbl\\">9. Registered Address</td><td colspan=\\"3\\">"+esc(settings.address||"")+"</td></tr>";
h+="<tr><td class=\\"lbl\\">10. Contact</td><td>"+esc(settings.contactNumber||"")+"</td><td class=\\"lbl\\">11. Email</td><td>"+esc(settings.email||"")+"</td></tr>";
h+="</table></div>";
h+="<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">Schedule 1 - Computation of Tax (ATC from COA Tax Line 2551Q tagging)</div><table class=\\"q1701-tbl\\"><thead><tr><th class=\\"item\\">#</th><th class=\\"desc\\">ATC / Particulars (Taxable Amount)</th>"+colsHdr()+"</tr></thead><tbody>";
if(!tagOrder.length){h+="<tr><td>1</td><td>No accounts tagged with a 2551Q Tax Line (COA col J)</td>"+numCol(function(){return fmt2551(0);})+"</tr>";}
else{for(var t=0;t<tagOrder.length;t++){var tg=tagOrder[t];var inf=tagInfo[tg];
var drillAccts=[];for(var dai=0;dai<coa.length;dai++){var daa=coa[dai];if(!daa||!daa.title)continue;if(String(daa.ptTaxLine||daa.taxLine2551||"").trim()===tg)drillAccts.push(String(daa.title).trim());}
var drillCSV=drillAccts.join("|").replace(/'/g,"\\\\'");
h+="<tr><td>"+(t+1)+"</td><td>"+esc(inf.label)+" · rate "+(inf.rate*100)+"%</td>"+(function(theTag,theAccts){return numCol(function(q){var rgq=q2551Range(s.taxYear,q);return "<span class=\\"gl-amt-clickable\\" style=\\"font-weight:600\\" title=\\"Open these accounts in GL\\" onclick=\\"drillFromChatToGL('"+theAccts+"','"+rgq.from+"','"+rgq.to+"')\\">"+fmt2551(baseFor(q,theTag))+"</span>";});})(tg,drillCSV)+"</tr>";}}
h+="<tr class=\\"tot\\"><td>7</td><td>Total Tax Due (to Part II Item 14)</td>"+numCol(function(q){return fmt2551(taxDue(q));})+"</tr>";
h+="</tbody></table></div>";
h+="<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">Part II - Total Tax Payable</div><table class=\\"q1701-tbl\\"><thead><tr><th class=\\"item\\">#</th><th class=\\"desc\\">Particulars</th>"+colsHdr()+"</tr></thead><tbody>";
h+="<tr><td>14</td><td>Total Tax Due</td>"+numCol(function(q){return fmt2551(taxDue(q));})+"</tr>";
h+="<tr><td>15</td><td>Less: Creditable Percentage Tax Withheld (BIR 2307)</td>"+inpCol("cwt")+"</tr>";
h+="<tr><td>16</td><td>Less: Tax Paid in Previously Filed Return (if Amended)</td>"+inpCol("prior")+"</tr>";
h+="<tr><td>17</td><td>Less: Other Tax Credit/Payment</td>"+inpCol("other")+"</tr>";
h+="<tr><td>18</td><td>Total Tax Credits/Payments</td>"+numCol(function(q){return fmt2551(credits(q));})+"</tr>";
h+="<tr class=\\"tot\\"><td>19</td><td>Tax Still Payable / (Overpayment)</td>"+numCol(function(q){return fmt2551(stillPay(q));})+"</tr>";
h+="<tr><td>20</td><td>Add: Surcharge</td>"+inpCol("surcharge")+"</tr>";
h+="<tr><td>21</td><td>Interest</td>"+inpCol("interest")+"</tr>";
h+="<tr><td>22</td><td>Compromise</td>"+inpCol("compromise")+"</tr>";
h+="<tr><td>23</td><td>Total Penalties</td>"+numCol(function(q){return fmt2551(penalties(q));})+"</tr>";
h+="<tr class=\\"tot\\"><td>24</td><td>TOTAL AMOUNT PAYABLE / (Overpayment)</td>"+numCol(function(q){return fmt2551(totalPay(q));})+"</tr>";
h+="</tbody></table></div>";
h+="<div style=\\"font-size:11px;color:#6B7280;margin-top:8px;\\">Taxable Amount + ATC + rate come from each Sales account's COA Tax Line (2551Q) tag (col J). Each quarter is filed separately (not cumulative).</div>";
h+="</div>";
document.getElementById("report").innerHTML=h;
ensureQ1701Style_();
}
function r2_2550(n){return Math.round((n||0)*100)/100;}
function fmt2550(n){if(n===null||n===undefined||n==="")return"";if(typeof n!=="number")n=parseFloat(n)||0;return n.toLocaleString("en-PH",{minimumFractionDigits:2,maximumFractionDigits:2});}
function q2550Range(taxYear,quarter){var m={Q1:["-01-01","-03-31"],Q2:["-04-01","-06-30"],Q3:["-07-01","-09-30"],Q4:["-10-01","-12-31"]};var r=m[quarter]||m.Q1;return{from:taxYear+r[0],to:taxYear+r[1]};}
var Q2550_STATE={taxYear:0,viewMode:"Q1",amended:false,classification:"Micro",hideEmpty:false,perQuarter:{}};
function q2550Quarters(vm){var order=["Q1","Q2","Q3","Q4"];var ret=[];order.forEach(function(q){if((vm||"").indexOf(q)>=0)ret.push(q);});return ret.length?ret:["Q1"];}
function toggle2550Quarter(q,on){var order=["Q1","Q2","Q3","Q4"];if(order.indexOf(q)<0)return;var cur={};order.forEach(function(qq){if(Q2550_STATE.viewMode.indexOf(qq)>=0)cur[qq]=true;});if(on)cur[q]=true;else delete cur[q];var act=order.filter(function(qq){return cur[qq];});if(!act.length){cur[q]=true;act=[q];}Q2550_STATE.viewMode=act.join("");build2550Q();}
function q2550Field(q,f){var pq=Q2550_STATE.perQuarter[q]||(Q2550_STATE.perQuarter[q]={});return pq[f]||0;}
function set2550Field(q,f,v){if(!Q2550_STATE.perQuarter[q])Q2550_STATE.perQuarter[q]={};Q2550_STATE.perQuarter[q][f]=parseFloat(v)||0;render2550Q();}
function q2550LazyInit(){if(Q2550_STATE.taxYear)return;var tm="";var ssj=(salesVat_().ssjRows)||[];var spj=(purchaseVat_().spjRows)||[];function scan(a){for(var i=0;i<a.length;i++){var d=a[i].date;if(d&&(!tm||d<tm))tm=d;}}scan(ssj);scan(spj);Q2550_STATE.taxYear=(tm&&tm.length>=4)?parseInt(tm.substring(0,4)):new Date().getFullYear();}
function compute2550QAuto(taxYear,quarter){
var rg=q2550Range(taxYear,quarter);
var ssj=(salesVat_().ssjRows)||[];var spj=(purchaseVat_().spjRows)||[];
var sv={vatable:0,zero:0,exempt:0,output:0};
for(var i=0;i<ssj.length;i++){var s=ssj[i];var d=s.date||"";if(d<rg.from||d>rg.to)continue;sv.vatable+=s.vatable||0;sv.zero+=s.zero||0;sv.exempt+=s.exempt||0;sv.output+=s.outputVAT||0;}
var pv={exempt:0,zero:0,taxableNet:0,input:0};
for(var j=0;j<spj.length;j++){var p=spj[j];var d2=p.date||"";if(d2<rg.from||d2>rg.to)continue;pv.exempt+=p.exempt||0;pv.zero+=p.zero||0;pv.taxableNet+=p.taxableNet||0;pv.input+=p.inputTax||0;}
return {vatable:r2_2550(sv.vatable),zero:r2_2550(sv.zero),exempt:r2_2550(sv.exempt),output:r2_2550(sv.output),pExempt:r2_2550(pv.exempt),pZero:r2_2550(pv.zero),pTaxableNet:r2_2550(pv.taxableNet),pInput:r2_2550(pv.input)};
}
function compute2550QLines(a,mf){
mf=mf||{};function g(k){return mf[k]||0;}
var i31A=r2_2550(a.vatable),i31B=r2_2550(a.output),i32A=r2_2550(a.zero),i33A=r2_2550(a.exempt);
var i34A=r2_2550(i31A+i32A+i33A),i34B=r2_2550(i31B);
var i37=r2_2550(i34B-g("m35")+g("m36"));
var i43=r2_2550(g("m38")+g("m39")+g("m40")+g("m41")+g("m42"));
var i44A=r2_2550(a.pTaxableNet),i44B=r2_2550(a.pInput);
var i48A=r2_2550(a.pExempt+a.pZero);
var i50A=r2_2550(i44A+g("m45A")+g("m46A")+g("m47A")+i48A+g("m49A"));
var i50B=r2_2550(i44B+g("m45B")+g("m46B")+g("m47B"));
var i51=r2_2550(i43+i50B);
var i57=r2_2550(g("m52")+g("m53")+g("m54")+g("m55")+g("m56"));
var i59=r2_2550(i57+g("m58"));
var i60=r2_2550(i51-i59);
var i61=r2_2550(i37-i60);
var i15=r2_2550(i61);
var i20=r2_2550(g("m16")+g("m17")+g("m18")+g("m19"));
var i21=r2_2550(i15-i20);
var i25=r2_2550(g("m22")+g("m23")+g("m24"));
var i26=r2_2550(i21+i25);
return {i31A:i31A,i31B:i31B,i32A:i32A,i33A:i33A,i34A:i34A,i34B:i34B,i37:i37,i43:i43,i44A:i44A,i44B:i44B,i48A:i48A,i50A:i50A,i50B:i50B,i51:i51,i57:i57,i59:i59,i60:i60,i61:i61,i15:i15,i20:i20,i21:i21,i25:i25,i26:i26};
}
function readMf2550(q){var f=["m35","m36","m38","m39","m40","m41","m42","m45A","m45B","m46A","m46B","m47A","m47B","m49A","m52","m53","m54","m55","m56","m58","m16","m17","m18","m19","m22","m23","m24"];var mf={};for(var i=0;i<f.length;i++)mf[f[i]]=q2550Field(q,f[i]);return mf;}
function build2550Q(){build2550QControls();render2550Q();}
function build2550QControls(){
q2550LazyInit();var s=Q2550_STATE;var yrNow=new Date().getFullYear();
var h="<div class=\\"ctrl-bar\\">";
h+="<div class=\\"ctrl-group\\"><label>1. Tax Year</label><input type=\\"number\\" value=\\""+s.taxYear+"\\" min=\\"2018\\" max=\\""+(yrNow+1)+"\\" onchange=\\"Q2550_STATE.taxYear=parseInt(this.value)||"+yrNow+";build2550Q()\\" style=\\"width:84px\\"></div>";
h+="<div class=\\"ctrl-group\\"><label>3. Quarter</label><div style=\\"display:flex;gap:10px;align-items:center;\\">";
["Q1","Q2","Q3","Q4"].forEach(function(q){var ck=s.viewMode.indexOf(q)>=0;h+="<label style=\\"font-weight:400;display:flex;gap:4px;align-items:center;cursor:pointer;font-size:13px;\\"><input type=\\"checkbox\\" "+(ck?"checked":"")+" onchange=\\"toggle2550Quarter('"+q+"',this.checked)\\">"+q+"</label>";});
h+="</div></div>";
h+="<div class=\\"ctrl-group\\"><label>13. Classification</label><select onchange=\\"Q2550_STATE.classification=this.value;render2550Q()\\">";
["Micro","Small","Medium","Large"].forEach(function(c){h+="<option value=\\""+c+"\\""+(s.classification===c?" selected":"")+">"+c+"</option>";});
h+="</select></div>";
h+="<div class=\\"ctrl-group\\"><label>5. Amended?</label><select onchange=\\"Q2550_STATE.amended=this.value==='yes';render2550Q()\\"><option value=\\"no\\""+(!s.amended?" selected":"")+">No</option><option value=\\"yes\\""+(s.amended?" selected":"")+">Yes</option></select></div>";
h+="<div class=\\"ctrl-group\\"><label>View</label><label style=\\"font-weight:400;display:flex;gap:4px;align-items:center;cursor:pointer;font-size:13px;\\"><input type=\\"checkbox\\" "+(s.hideEmpty?"checked":"")+" onchange=\\"Q2550_STATE.hideEmpty=this.checked;render2550Q()\\">Hide empty lines</label></div>";
h+="</div>";
document.getElementById("controls").innerHTML=h;
}
function filer2550Name_(settings){var cls=String(settings.taxpayerClass||"").toUpperCase();if(cls.indexOf("NON")>=0)return settings.registeredNameNonInd||settings.businessName||"";var ln=settings.ownerLast||"",fn=settings.ownerFirst||"",mn=settings.ownerMiddle||"";var nm=(ln?ln+", ":"")+fn+(mn?" "+mn:"");return nm.trim()||settings.businessName||"";}
/* 2550Q drill helpers - map VAT row-5 tags (bookMeta) to the account columns behind each line.
Mirrors server normalizeVatTag_ / normalizePurchaseVatTag_. */
function q2550NormSalesTag(raw){var t=String(raw==null?"":raw).trim().toLowerCase();if(!t)return"NONE";if(t==="exempt")return"EXEMPT";if(t==="zero-rated"||t==="zero rated"||t==="zerorated")return"ZERO_RATED";if(t==="vatable"||t==="taxable")return"VATABLE";if(t==="output tax"||t==="output vat")return"OUTPUT_TAX";return"NONE";}
function q2550NormPurchTag(raw){var t=String(raw==null?"":raw).trim().toLowerCase();if(!t)return"NONE";if(t==="exempt")return"EXEMPT";if(t==="zero-rated"||t==="zero rated"||t==="zerorated")return"ZERO_RATED";if(t==="vatable services"||t==="taxable services"||t==="services")return"VATABLE_SERVICES";if(t==="vatable capital goods"||t==="capital goods")return"VATABLE_CAPITAL";if(t==="vatable goods other than capital goods"||t==="goods other than capital goods"||t==="goods other")return"VATABLE_GOODS";if(t==="input tax"||t==="input vat")return"INPUT_TAX";return"NONE";}
function q2550DrillAccts(kind){
var bm=(typeof DATA!=="undefined"&&DATA&&DATA.bookMeta)?DATA.bookMeta:null;if(!bm)return[];
var wantSales={vatable:["VATABLE"],zero:["ZERO_RATED"],exempt:["EXEMPT"],output:["OUTPUT_TAX"],sales:["VATABLE","ZERO_RATED","EXEMPT"]};
var wantPurch={pnet:["VATABLE_SERVICES","VATABLE_CAPITAL","VATABLE_GOODS"],pinput:["INPUT_TAX"],pnovat:["EXEMPT","ZERO_RATED"]};
var out=[],seen={};
function scan(book,norm,want){if(!book||!book.headers||!book.vatTags)return;for(var i=0;i<book.headers.length;i++){var tg=norm(book.vatTags[i]);if(want.indexOf(tg)<0)continue;var t=String(book.headers[i]||"").trim();if(t&&!seen[t]){seen[t]=true;out.push(t);}}}
if(wantSales[kind]){scan(bm.crb,q2550NormSalesTag,wantSales[kind]);scan(bm.sj,q2550NormSalesTag,wantSales[kind]);}
if(wantPurch[kind]){scan(bm.cdb,q2550NormPurchTag,wantPurch[kind]);scan(bm.pj,q2550NormPurchTag,wantPurch[kind]);}
return out;
}
function render2550Q(){
var s=Q2550_STATE;var quarters=q2550Quarters(s.viewMode);
var settings=(typeof DATA!=="undefined"&&DATA&&DATA.settings)?DATA.settings:{};
var res={};for(var i=0;i<quarters.length;i++){var q=quarters[i];var a=compute2550QAuto(s.taxYear,q);res[q]=compute2550QLines(a,readMf2550(q));}
function colsHdr(){var x="";for(var i=0;i<quarters.length;i++)x+="<th class=\\"num\\">"+quarters[i]+"</th>";return x;}
function valsC(key){var arr=[];for(var i=0;i<quarters.length;i++)arr.push(res[quarters[i]][key]||0);return arr;}
function valsF(field){var arr=[];for(var i=0;i<quarters.length;i++)arr.push(q2550Field(quarters[i],field));return arr;}
function allZero(arr){for(var k=0;k<arr.length;k++)if(Math.round((arr[k]||0)*100)!==0)return false;return true;}
function numCells(key){var x="";for(var i=0;i<quarters.length;i++)x+="<td class=\\"num\\">"+fmt2550(res[quarters[i]][key])+"</td>";return x;}
function drillCells(key,kind){var accts=q2550DrillAccts(kind);if(!accts.length)return numCells(key);var a=accts.join("|").replace(/'/g,"\\\\'");var x="";for(var i=0;i<quarters.length;i++){var q=quarters[i];var rgq=q2550Range(s.taxYear,q);x+="<td class=\\"num gl-amt-clickable\\" onclick=\\"drillFromChatToGL('"+a+"','"+rgq.from+"','"+rgq.to+"')\\" title=\\"Open these accounts in GL for "+q+"\\">"+fmt2550(res[q][key])+"</td>";}return x;}
function inpCells(field){var x="";for(var i=0;i<quarters.length;i++){var q=quarters[i];var v=q2550Field(q,field);x+="<td class=\\"num\\"><input type=\\"number\\" step=\\"0.01\\" value=\\""+(v||"")+"\\" placeholder=\\"0.00\\" onchange=\\"set2550Field('"+q+"','"+field+"',this.value)\\" class=\\"q1701-inp\\"></td>";}return x;}
function compRow(item,label,key,opts){opts=opts||{};if(s.hideEmpty&&opts.sup&&allZero(valsC(key)))return"";var cl=opts.tot?" class=\\"tot\\"":"";return "<tr"+cl+"><td class=\\"item\\">"+item+"</td><td>"+esc(label)+"</td>"+(opts.drill?drillCells(key,opts.drill):numCells(key))+"</tr>";}
function inpRow(item,label,field){if(s.hideEmpty&&allZero(valsF(field)))return"";return "<tr><td class=\\"item\\">"+item+"</td><td>"+esc(label)+"</td>"+inpCells(field)+"</tr>";}
function card(title,body){return "<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">"+title+"</div><table class=\\"q1701-tbl\\"><thead><tr><th class=\\"item\\">#</th><th class=\\"desc\\">Particulars</th>"+colsHdr()+"</tr></thead><tbody>"+body+"</tbody></table></div>";}
var h="<div class=\\"q1701-report\\">";
h+="<div class=\\"q1701-section q1701-titleblock\\"><div class=\\"q1701-bn\\">"+esc(filer2550Name_(settings))+"</div><div class=\\"q1701-ttl\\">BIR Form 2550Q - Quarterly Value-Added Tax Return</div><div class=\\"q1701-meta\\">Tax Year "+s.taxYear+" · "+quarters.join(", ")+" · "+esc(s.classification)+(s.amended?" · AMENDED":"")+"</div></div>";
h+="<div class=\\"q1701-section\\"><div class=\\"q1701-sh\\">Part I - Background Information</div><table class=\\"q1701-bg\\">";
h+="<tr><td class=\\"lbl\\">7. TIN</td><td>"+esc(settings.tin||"")+"</td><td class=\\"lbl\\">8. RDO Code</td><td>"+esc(settings.rdo||"")+"</td></tr>";
h+="<tr><td class=\\"lbl\\">9. Taxpayer Name</td><td colspan=\\"3\\">"+esc(filer2550Name_(settings))+"</td></tr>";
h+="<tr><td class=\\"lbl\\">10. Registered Address</td><td colspan=\\"3\\">"+esc(settings.address||"")+"</td></tr>";
h+="<tr><td class=\\"lbl\\">11. Contact</td><td>"+esc(settings.contactNumber||"")+"</td><td class=\\"lbl\\">12. Email</td><td>"+esc(settings.email||"")+"</td></tr>";
h+="<tr><td class=\\"lbl\\">13. Classification</td><td>"+esc(s.classification)+"</td><td class=\\"lbl\\">14. Tax Relief?</td><td>No</td></tr>";
h+="</table></div>";
var out="";
out+=compRow("31A","VATable Sales","i31A",{sup:false,drill:"vatable"});
out+=compRow("31B","Output Tax (12%)","i31B",{sup:false,drill:"output"});
out+=compRow("32A","Zero-Rated Sales","i32A",{sup:false,drill:"zero"});
out+=compRow("33A","Exempt Sales","i33A",{sup:false,drill:"exempt"});
out+=compRow("34A","Total Sales (Sum 31A-33A)","i34A",{sup:false,drill:"sales"});
out+=compRow("34B","Total Output Tax Due","i34B",{sup:false,drill:"output"});
out+=inpRow("35","Less: Output VAT on Uncollected Receivables","m35");
out+=inpRow("36","Add: Output VAT on Recovered Uncollected","m36");
out+=compRow("37","Total Adjusted Output Tax Due","i37",{tot:true});
h+=card("Part IV-A - Total Sales & Output Tax",out);
out="";
out+=inpRow("38","Input Tax Carried Over from Previous Quarter","m38");
out+=inpRow("39","Input Tax Deferred on Capital Goods > 1M (legacy)","m39");
out+=inpRow("40","Transitional Input Tax","m40");
out+=inpRow("41","Presumptive Input Tax","m41");
out+=inpRow("42","Others","m42");
out+=compRow("43","Total Carry-over Input (Sum 38-42)","i43",{tot:true});
h+=card("Part IV-B - Allowable Input Tax (Carry-over)",out);
out="";
out+=compRow("44A","Domestic Purchases (net)","i44A",{sup:false,drill:"pnet"});
out+=compRow("44B","Input Tax on Domestic Purchases","i44B",{sup:false,drill:"pinput"});
out+=inpRow("45B","Input Tax - Services by Non-Residents","m45B");
out+=inpRow("46B","Input Tax - Importations","m46B");
out+=inpRow("47B","Input Tax - Others","m47B");
out+=compRow("48A","Domestic Purchases with No Input Tax","i48A",{sup:false,drill:"pnovat"});
out+=inpRow("49A","VAT-Exempt Importations","m49A");
out+=compRow("50","Total Current Purchases / Input Tax","i50B",{sup:false});
out+=compRow("51","Total Available Input Tax (43B + 50B)","i51",{tot:true});
h+=card("Part IV-C - Current Transactions",out);
out="";
out+=inpRow("52","Input Tax on Capital Goods > 1M deferred","m52");
out+=inpRow("53","Input Tax Attributable to VAT Exempt Sales","m53");
out+=inpRow("54","VAT Refund / TCC Claimed","m54");
out+=inpRow("55","Input VAT on Unpaid Payables","m55");
out+=inpRow("56","Others","m56");
out+=compRow("57","Total Deductions from Input Tax (52-56)","i57",{sup:false});
out+=inpRow("58","Add: Input VAT on Settled Unpaid Payables","m58");
out+=compRow("59","Adjusted Deductions (57 + 58)","i59",{sup:false});
out+=compRow("60","Total Allowable Input Tax (51 - 59)","i60",{sup:false});
out+=compRow("61","Net VAT Payable / (Excess Input) -> Item 15","i61",{tot:true});
h+=card("Part IV-D - Deductions & Net VAT",out);
out="";
out+=compRow("15","Net VAT Payable (from Item 61)","i15",{sup:false});
out+=inpRow("16","Less: Creditable VAT Withheld (2307)","m16");
out+=inpRow("17","Advance VAT Payments","m17");
out+=inpRow("18","VAT paid in return previously filed (if amended)","m18");
out+=inpRow("19","Other Credits / Payment","m19");
out+=compRow("20","Total Tax Credits / Payment (16-19)","i20",{sup:false});
out+=compRow("21","Tax Still Payable / (Excess Credits)","i21",{sup:false});
out+=inpRow("22","Add: Surcharge","m22");
out+=inpRow("23","Interest","m23");
out+=inpRow("24","Compromise","m24");
out+=compRow("25","Total Penalties (22-24)","i25",{sup:false});
out+=compRow("26","TOTAL AMOUNT PAYABLE / (Excess Credits)","i26",{tot:true});
h+=card("Part II - Total Tax Payable",out);
h+="<div style=\\"font-size:11px;color:#6B7280;margin-top:8px;\\">Output (31-37) and domestic input (44) auto-pull from the same VAT-tagged books as SLS/SLP. Gold cells are manual. Each quarter is filed separately.</div>";
h+="</div>";
document.getElementById("report").innerHTML=h;
ensureQ1701Style_();
}
`; }This is important so the dates in your reports and taxes are correct.
https up to /exec. Do not delete the quotes " ".You now have your own EC Tax Bookkeeping, running on your own account, with your data fully yours.
On the next screens: how to use it day to day, plus a few good-to-know notes.
Have a question? Message the EC TAXPH Facebook page.
You do not need a password for this setup - never share your password with anyone.
From now on it is simple:
For your next book, just make a copy of your already set-up Sheet (your recent one - not the template).
It is normal, do not worry. It appears because the app is your own copy. The truth:
This will go away after the official Google verification of EC TAXPH.
| Avoid | What happens | Correct |
|---|---|---|
| Pasting the code inside the Google Sheet | It will not work | Paste in the new project (Steps 2-3) |
| Not setting the timezone | Wrong dates and taxes | Manila (Step 4) |
| Code cut off when pasting | Blank or error | Use the Copy code button |
| Forgetting the web app link | App will not open | Step 6 |