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
+117
View File
@@ -0,0 +1,117 @@
// services/itemGroupClassStore.js
// Admin-configured mapping: SAP Item Group (OITB.ItmsGrpCod) → which Work
// Order table its items should land in — RAW / PACK / COMPONENT. Lets the
// portal replace the hardcoded Solution-name/PK-code heuristics in
// work-order.html with an explicit, editable rule per item group.
const sql = require('mssql');
const TABLE = `[dbo].[ZITEM_GROUP_CLASS]`;
let _conn = null;
async function getConn() {
if (_conn) return _conn;
const config = {
server: process.env.APP_SQL_HOST,
port: parseInt(process.env.APP_SQL_PORT),
user: process.env.APP_SQL_USER,
password: process.env.APP_SQL_PASSWORD,
database: process.env.APP_SQL_DATABASE,
options: { encrypt: true, trustServerCertificate: true },
};
_conn = await sql.connect(config);
return _conn;
}
async function exec(sqlQuery, params = []) {
const conn = await getConn();
const request = conn.request();
params.forEach((param, index) => { request.input(`param${index}`, param); });
const replacedSql = sqlQuery.replace(/\?/g, (match, offset, string) => {
const paramIndex = (string.slice(0, offset).match(/\?/g) || []).length;
return `@param${paramIndex}`;
});
const result = await request.query(replacedSql);
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-GROUP-CLASS-STORE] Checking table', TABLE, '...');
await exec(`
CREATE TABLE ${TABLE} (
ITMSGRPCOD INT PRIMARY KEY,
ITMSGRPNAM NVARCHAR(100),
CLASSIFICATION NVARCHAR(20),
UPDATED_BY NVARCHAR(50),
UPDATED_AT DATETIME2
)
`).catch(e => {
if (isAlreadyExists(e)) { console.log('[ITEM-GROUP-CLASS-STORE] Table exists — OK'); }
else throw e;
});
console.log('[ITEM-GROUP-CLASS-STORE] ✅ Ready');
}
// { ITMSGRPCOD: 'RAW'|'PACK'|'COMPONENT' } for groups that have a classification set
async function getMap() {
const rows = await exec(`SELECT ITMSGRPCOD, CLASSIFICATION FROM ${TABLE} WHERE CLASSIFICATION IS NOT NULL AND CLASSIFICATION <> ''`);
const map = {};
rows.forEach(r => { map[r.ITMSGRPCOD] = r.CLASSIFICATION; });
return map;
}
async function getAll() {
const rows = await exec(`SELECT ITMSGRPCOD, ITMSGRPNAM, CLASSIFICATION FROM ${TABLE}`);
const map = {};
rows.forEach(r => { map[r.ITMSGRPCOD] = r.CLASSIFICATION || ''; });
return map;
}
// CLASSIFICATION is stored as a comma-separated set of tags — a group can be
// tagged BOTH 'RAW' and 'PACK' at once (its items then land in BOTH the Raw
// Material and Packing Material tables), or just 'COMPONENT' (mutually
// exclusive with RAW/PACK — the Components table is the "none of the above"
// bucket, so RAW/PACK always win if COMPONENT is also present).
// 'SFG' (Semi-Finished Good) is a separate, orthogonal axis — it marks a
// group's items as SFG PRODUCTS in their own right (used by the Batch
// Issuance SFG workflow to decide which items may be intimated without a
// Requirement), independent of how those same items route when they appear
// as an INGREDIENT inside another product's BOM — so it can coexist with
// RAW/PACK/COMPONENT rather than being mutually exclusive with them.
const VALID_TOKENS = new Set(['RAW', 'PACK', 'COMPONENT', 'SFG']);
function normalizeClassification(raw) {
const tokens = [...new Set((raw || '').split(',').map(s => s.trim().toUpperCase()).filter(t => VALID_TOKENS.has(t)))];
if (tokens.includes('COMPONENT') && (tokens.includes('RAW') || tokens.includes('PACK')))
return tokens.filter(t => t !== 'COMPONENT').join(',');
return tokens.join(',');
}
async function setClassification(itmsGrpCod, itmsGrpNam, classification, updatedBy) {
const cls = normalizeClassification(classification);
const idRows = await exec(`
MERGE ${TABLE} AS tgt
USING (SELECT ? AS ITMSGRPCOD) AS src
ON tgt.ITMSGRPCOD = src.ITMSGRPCOD
WHEN MATCHED THEN UPDATE SET
ITMSGRPNAM = ?, CLASSIFICATION = ?, UPDATED_BY = ?, UPDATED_AT = SYSDATETIME()
WHEN NOT MATCHED THEN INSERT (ITMSGRPCOD, ITMSGRPNAM, CLASSIFICATION, UPDATED_BY, UPDATED_AT)
VALUES (?, ?, ?, ?, SYSDATETIME());
SELECT ? AS ITMSGRPCOD;
`, [itmsGrpCod, itmsGrpNam || '', cls, updatedBy || '', itmsGrpCod, itmsGrpNam || '', cls, updatedBy || '', itmsGrpCod]);
return idRows[0]?.ITMSGRPCOD;
}
// SAP Item Group codes tagged 'SFG' — used server-side to verify a Batch
// Issuance SFG intimation's products are actually SFG-classified, rather
// than trusting the client's own tab/mode selection.
async function getSfgGroupCodes() {
const map = await getMap();
return Object.keys(map).filter(code => (map[code] || '').split(',').includes('SFG')).map(Number);
}
module.exports = { bootstrap, getMap, getAll, setClassification, getSfgGroupCodes, VALID_TOKENS };