178 lines
7.1 KiB
JavaScript
178 lines
7.1 KiB
JavaScript
// backend/services/hanaItemStore.js
|
|
// SQL persistence for item creation approval requests.
|
|
'use strict';
|
|
const { getPool } = require('./appSqlPool');
|
|
const { DEFAULT_COMPANY } = require('./companyConfig');
|
|
|
|
const tbl = () => `[dbo].[ZITEM_PORTAL]`;
|
|
|
|
async function exec(sqlQuery, params = []) {
|
|
const pool = await getPool();
|
|
const request = pool.request();
|
|
let idx = 0;
|
|
const replaced = sqlQuery.replace(/\?/g, () => {
|
|
const name = `p${idx}`;
|
|
request.input(name, params[idx]);
|
|
idx++;
|
|
return `@${name}`;
|
|
});
|
|
const result = await request.query(replaced);
|
|
return result.recordset || [];
|
|
}
|
|
|
|
function isAlreadyExists(e) {
|
|
const m = (e.message || '').toLowerCase();
|
|
return m.includes('already exists') || m.includes('duplicate') || m.includes('existing object') || m.includes('there is already an object');
|
|
}
|
|
|
|
async function bootstrap() {
|
|
console.log('[ITEM STORE] Checking table...');
|
|
let tableExisted = false;
|
|
await exec(`
|
|
CREATE TABLE ${tbl()} (
|
|
[ID] INT IDENTITY(1,1) PRIMARY KEY,
|
|
[STATUS] NVARCHAR(30) DEFAULT 'PENDING',
|
|
[ITEM_CODE] NVARCHAR(50),
|
|
[ITEM_NAME] NVARCHAR(200),
|
|
[ITEM_DATA] NVARCHAR(MAX),
|
|
[SUBMITTED_BY] NVARCHAR(60),
|
|
[SUBMITTED_BY_NAME] NVARCHAR(100),
|
|
[SUBMITTED_AT] DATETIME2 DEFAULT GETDATE(),
|
|
[ADMIN_NOTES] NVARCHAR(MAX),
|
|
[ADMIN_EDITED_DATA] NVARCHAR(MAX),
|
|
[REVIEWED_BY] NVARCHAR(60),
|
|
[REVIEWED_AT] DATETIME2,
|
|
[APPROVED_BY] NVARCHAR(60),
|
|
[APPROVED_AT] DATETIME2,
|
|
[REJECTED_BY] NVARCHAR(60),
|
|
[REJECTED_AT] DATETIME2,
|
|
[SAP_ITEM_CODE] NVARCHAR(50),
|
|
[APPROVAL_LOG] NVARCHAR(MAX),
|
|
[COMPANY] NVARCHAR(50) DEFAULT '${DEFAULT_COMPANY}'
|
|
)
|
|
`).catch(e => {
|
|
if (!isAlreadyExists(e)) throw e;
|
|
tableExisted = true;
|
|
console.log('[ITEM STORE] Table already exists — checking for missing columns...');
|
|
});
|
|
if (tableExisted) {
|
|
const cols = [
|
|
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZITEM_PORTAL' AND COLUMN_NAME='COMPANY') ALTER TABLE ${tbl()} ADD [COMPANY] NVARCHAR(50) DEFAULT '${DEFAULT_COMPANY}'`,
|
|
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZITEM_PORTAL' AND COLUMN_NAME='ADMIN_EDITED_DATA') ALTER TABLE ${tbl()} ADD [ADMIN_EDITED_DATA] NVARCHAR(MAX)`,
|
|
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZITEM_PORTAL' AND COLUMN_NAME='APPROVAL_LOG') ALTER TABLE ${tbl()} ADD [APPROVAL_LOG] NVARCHAR(MAX)`,
|
|
];
|
|
for (const sql of cols) await exec(sql).catch(() => {});
|
|
console.log('[ITEM STORE] ✅ Column migration check complete');
|
|
}
|
|
console.log('[ITEM STORE] ✅ Table ready');
|
|
}
|
|
|
|
function rowToItem(row) {
|
|
if (!row) return null;
|
|
let itemData = {}, adminEditedData = null, approvalLog = [];
|
|
try { itemData = JSON.parse(row.ITEM_DATA || '{}'); } catch (_) {}
|
|
try { adminEditedData = row.ADMIN_EDITED_DATA ? JSON.parse(row.ADMIN_EDITED_DATA) : null; } catch (_) {}
|
|
try { approvalLog = JSON.parse(row.APPROVAL_LOG || '[]'); } catch (_) {}
|
|
return {
|
|
id: row.ID,
|
|
status: row.STATUS,
|
|
itemCode: row.ITEM_CODE || '',
|
|
itemName: row.ITEM_NAME || '',
|
|
itemData,
|
|
adminEditedData,
|
|
submittedBy: row.SUBMITTED_BY || '',
|
|
submittedByName: row.SUBMITTED_BY_NAME || '',
|
|
submittedAt: row.SUBMITTED_AT ? new Date(row.SUBMITTED_AT).toISOString() : null,
|
|
adminNotes: row.ADMIN_NOTES || '',
|
|
reviewedBy: row.REVIEWED_BY || null,
|
|
reviewedAt: row.REVIEWED_AT ? new Date(row.REVIEWED_AT).toISOString() : null,
|
|
approvedBy: row.APPROVED_BY || null,
|
|
approvedAt: row.APPROVED_AT ? new Date(row.APPROVED_AT).toISOString() : null,
|
|
rejectedBy: row.REJECTED_BY || null,
|
|
rejectedAt: row.REJECTED_AT ? new Date(row.REJECTED_AT).toISOString() : null,
|
|
sapItemCode: row.SAP_ITEM_CODE || null,
|
|
approvalLog,
|
|
company: row.COMPANY || '',
|
|
};
|
|
}
|
|
|
|
function ts(isoStr) {
|
|
if (!isoStr) return null;
|
|
return isoStr.replace('T', ' ').replace('Z', '').substring(0, 23);
|
|
}
|
|
|
|
async function insertItem(data) {
|
|
const rows = await exec(`
|
|
INSERT INTO ${tbl()} (
|
|
[STATUS],[ITEM_CODE],[ITEM_NAME],[ITEM_DATA],
|
|
[SUBMITTED_BY],[SUBMITTED_BY_NAME],[SUBMITTED_AT],
|
|
[APPROVAL_LOG],[COMPANY]
|
|
) OUTPUT INSERTED.ID VALUES (?,?,?,?,?,?,GETDATE(),?,?)
|
|
`, [
|
|
data.status || 'PENDING',
|
|
(data.itemCode || '').substring(0, 50),
|
|
(data.itemName || '').substring(0, 200),
|
|
JSON.stringify(data.itemData || {}),
|
|
(data.submittedBy || '').substring(0, 60),
|
|
(data.submittedByName || '').substring(0, 100),
|
|
JSON.stringify(data.approvalLog || []),
|
|
(data.company || '').substring(0, 50),
|
|
]);
|
|
const id = rows[0].ID;
|
|
console.log(`[ITEM STORE] ✅ Inserted ID=${id}`);
|
|
return { id };
|
|
}
|
|
|
|
async function findById(id) {
|
|
const rows = await exec(`SELECT * FROM ${tbl()} WHERE [ID]=?`, [parseInt(id)]);
|
|
return rowToItem(rows[0]);
|
|
}
|
|
|
|
async function listByStatus(status, companyDB, submittedBy = null) {
|
|
const conditions = [];
|
|
const params = [];
|
|
if (status !== 'ALL') { conditions.push(`[STATUS]=?`); params.push(status.toUpperCase()); }
|
|
if (submittedBy) { conditions.push(`[SUBMITTED_BY]=?`); params.push(submittedBy); }
|
|
if (companyDB && companyDB !== 'ALL') { conditions.push(`[COMPANY]=?`); params.push(companyDB); }
|
|
const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
const rows = await exec(`SELECT * FROM ${tbl()} ${where} ORDER BY [SUBMITTED_AT] DESC`, params);
|
|
return rows.map(rowToItem);
|
|
}
|
|
|
|
async function updateItem(id, patch) {
|
|
const COL_MAP = {
|
|
status: 'STATUS',
|
|
itemCode: 'ITEM_CODE',
|
|
itemName: 'ITEM_NAME',
|
|
itemData: 'ITEM_DATA',
|
|
adminNotes: 'ADMIN_NOTES',
|
|
adminEditedData: 'ADMIN_EDITED_DATA',
|
|
approvalLog: 'APPROVAL_LOG',
|
|
reviewedBy: 'REVIEWED_BY',
|
|
reviewedAt: 'REVIEWED_AT',
|
|
approvedBy: 'APPROVED_BY',
|
|
approvedAt: 'APPROVED_AT',
|
|
rejectedBy: 'REJECTED_BY',
|
|
rejectedAt: 'REJECTED_AT',
|
|
sapItemCode: 'SAP_ITEM_CODE',
|
|
company: 'COMPANY',
|
|
};
|
|
const JSON_COLS = new Set(['ITEM_DATA', 'ADMIN_EDITED_DATA', 'APPROVAL_LOG']);
|
|
const TS_COLS = new Set(['REVIEWED_AT', 'APPROVED_AT', 'REJECTED_AT']);
|
|
|
|
const setClauses = [], vals = [];
|
|
Object.entries(patch).forEach(([k, v]) => {
|
|
const col = COL_MAP[k];
|
|
if (!col) return;
|
|
setClauses.push(`[${col}]=?`);
|
|
if (JSON_COLS.has(col)) vals.push(v === null ? null : JSON.stringify(v));
|
|
else if (TS_COLS.has(col)) vals.push(v ? ts(v) : null);
|
|
else vals.push(v === null || v === undefined ? null : String(v));
|
|
});
|
|
if (!setClauses.length) return;
|
|
vals.push(parseInt(id));
|
|
await exec(`UPDATE ${tbl()} SET ${setClauses.join(',')} WHERE [ID]=?`, vals);
|
|
console.log(`[ITEM STORE] ✅ Updated ID=${id}`);
|
|
}
|
|
|
|
module.exports = { bootstrap, insertItem, findById, listByStatus, updateItem }; |