first commit
SAP-ERP Portal CI/CD / build (push) Failing after 5m20s

This commit is contained in:
John
2026-09-23 17:31:02 +05:30
commit 69b4e68baf
51657 changed files with 3864077 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
'use strict';
// cf_items (this store's own config table) lives on the app's own database —
// only fetchItemValue/fetchItemClosingBalance below touch real SAP data
// (OACT/JDT1), and those receive their pool as a parameter from the caller
// (routes/cashFlow.js, which imports the SAP pool directly), so they're
// unaffected by this import.
const { getPool } = require('./appSqlPool');
async function bootstrap() {
const pool = await getPool();
await pool.request().query(`
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='cf_items' AND xtype='U')
CREATE TABLE cf_items (
id INT IDENTITY(1,1) PRIMARY KEY,
section NVARCHAR(20) NOT NULL DEFAULT 'Assets',
item_type NVARCHAR(20) NOT NULL DEFAULT 'working',
sort_order INT NOT NULL DEFAULT 999,
label NVARCHAR(200) NOT NULL,
acct_codes NVARCHAR(MAX) NOT NULL DEFAULT '[]',
notes NVARCHAR(500) NULL,
updated_at DATETIME DEFAULT GETDATE()
)
`);
// Migration: add item_type if upgrading
await pool.request().query(`
IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE name='item_type' AND object_id=OBJECT_ID('cf_items'))
ALTER TABLE cf_items ADD item_type NVARCHAR(20) NOT NULL DEFAULT 'working'
`);
// Migration: rename 'capital' item_type items to section='Capital' if they are in Assets section
await pool.request().query(`
UPDATE cf_items SET section='Capital' WHERE section='Assets' AND item_type='capital'
`);
}
async function getItems() {
const pool = await getPool();
const r = await pool.request().query(
`SELECT id, section, item_type, sort_order, label, acct_codes, notes
FROM cf_items ORDER BY
CASE section WHEN 'Assets' THEN 1 WHEN 'Liabilities' THEN 2 WHEN 'Capital' THEN 3 WHEN 'Cash' THEN 4 ELSE 5 END,
sort_order, id`
);
return r.recordset.map(row => ({
id: row.id,
section: row.section,
item_type: row.item_type || 'working',
sort_order: row.sort_order,
label: row.label,
acct_codes: JSON.parse(row.acct_codes || '[]'),
notes: row.notes || '',
}));
}
async function saveItem({ id, section, item_type, label, acct_codes, sort_order, notes }) {
const pool = await getPool();
const codes = JSON.stringify(Array.isArray(acct_codes) ? acct_codes : []);
const ord = sort_order != null ? parseInt(sort_order) : 999;
const type = item_type || 'working';
if (id) {
await pool.request()
.input('id', id).input('sec', section).input('typ', type)
.input('lbl', label).input('c', codes).input('ord', ord).input('n', notes || null)
.query(`UPDATE cf_items SET section=@sec, item_type=@typ, label=@lbl, acct_codes=@c, sort_order=@ord, notes=@n, updated_at=GETDATE() WHERE id=@id`);
} else {
await pool.request()
.input('sec', section).input('typ', type)
.input('lbl', label).input('c', codes).input('ord', ord).input('n', notes || null)
.query(`INSERT INTO cf_items (section, item_type, label, acct_codes, sort_order, notes) VALUES (@sec, @typ, @lbl, @c, @ord, @n)`);
}
}
async function deleteItem(id) {
const pool = await getPool();
await pool.request().input('id', id).query(`DELETE FROM cf_items WHERE id=@id`);
}
// Derive 4-char prefix for LIKE matching (same logic as balanceSheetStore)
function acctPrefix(code) {
const trimmed = String(code).replace(/0+$/, '');
return trimmed.length >= 4 ? trimmed : String(code).slice(0, 4);
}
// Period net change from live DB (Debit − Credit for the date range)
async function fetchItemValue(pool, acctCodes, from, to) {
if (!acctCodes || !acctCodes.length) return 0;
const req = pool.request().input('from', from).input('to', to);
const conditions = acctCodes.map((c, i) => {
req.input(`p${i}`, acctPrefix(c) + '%');
return `T0.[AcctCode] LIKE @p${i}`;
}).join(' OR ');
const r = await req.query(`
SELECT ISNULL(SUM(
CASE WHEN T1.[RefDate] BETWEEN @from AND @to
THEN ISNULL(T1.[Debit],0) - ISNULL(T1.[Credit],0)
ELSE 0 END
), 0) AS net_change
FROM OACT T0
LEFT JOIN JDT1 T1 ON T0.[AcctCode] = T1.[Account]
WHERE ${conditions}
`);
return parseFloat(r.recordset[0]?.net_change || 0);
}
// Closing balance from live DB — cumulative Debit − Credit up to asOf (= 'to' date)
async function fetchItemClosingBalance(pool, acctCodes, asOf) {
if (!acctCodes || !acctCodes.length) return 0;
const req = pool.request().input('asOf', asOf);
const conditions = acctCodes.map((c, i) => {
req.input(`p${i}`, acctPrefix(c) + '%');
return `T0.[AcctCode] LIKE @p${i}`;
}).join(' OR ');
const r = await req.query(`
SELECT ISNULL(SUM(
CASE WHEN T1.[RefDate] <= @asOf
THEN ISNULL(T1.[Debit],0) - ISNULL(T1.[Credit],0)
ELSE 0 END
), 0) AS closing_balance
FROM OACT T0
LEFT JOIN JDT1 T1 ON T0.[AcctCode] = T1.[Account]
WHERE ${conditions}
`);
return parseFloat(r.recordset[0]?.closing_balance || 0);
}
// Period net change from snapshot accounts array
function fetchItemValueFromSnapshot(accounts, acctCodes) {
if (!acctCodes || !acctCodes.length) return 0;
const prefixes = acctCodes.map(c => acctPrefix(c));
return accounts
.filter(a => prefixes.some(p => String(a.acct_code).startsWith(p)))
.reduce((s, a) => s + ((Number(a.period_debit) || 0) - (Number(a.period_credit) || 0)), 0);
}
// Closing balance from snapshot: opening_balance + period_debit − period_credit
function fetchItemClosingBalanceFromSnapshot(accounts, acctCodes) {
if (!acctCodes || !acctCodes.length) return 0;
const prefixes = acctCodes.map(c => acctPrefix(c));
return accounts
.filter(a => prefixes.some(p => String(a.acct_code).startsWith(p)))
.reduce((s, a) =>
s + (Number(a.opening_balance) || 0) + (Number(a.period_debit) || 0) - (Number(a.period_credit) || 0), 0);
}
// Opening balance from snapshot: the opening_balance field (before period start)
function fetchOpeningBalanceFromSnapshot(accounts, acctCodes) {
if (!acctCodes || !acctCodes.length) return 0;
const prefixes = acctCodes.map(c => acctPrefix(c));
return accounts
.filter(a => prefixes.some(p => String(a.acct_code).startsWith(p)))
.reduce((s, a) => s + (Number(a.opening_balance) || 0), 0);
}
module.exports = {
bootstrap, getItems, saveItem, deleteItem,
fetchItemValue, fetchItemClosingBalance,
fetchItemValueFromSnapshot, fetchItemClosingBalanceFromSnapshot,
fetchOpeningBalanceFromSnapshot,
acctPrefix,
};