Files
sap-erp/services/oeeStore.js
T
John eead8f5ffd
SAP-ERP Portal CI/CD / build (push) Successful in 3m57s
sale order
2026-10-05 18:45:17 +05:30

146 lines
6.5 KiB
JavaScript

'use strict';
// services/oeeStore.js — "Overall Equipment Efficiency" (OEE) data entry.
// Stored in the PORTAL's own app DB (not SAP), one document per (tab, month):
// a monthly sheet for one production area (EBB Production, PD Production, …).
// The row grid (date + shift + all the downtime/output inputs, plus the
// derived rates) is kept verbatim as a JSON array — the per-tab column schema
// and formulas live on the client (public/oee.html); the server just persists
// what it is given and enforces per-user tab permissions (see routes/oee.js).
const { getPool } = require('./appSqlPool');
const TABLE = `[dbo].[ZOEE_ENTRIES]`;
async function exec(sqlQuery, params = []) {
const pool = await getPool();
const req = pool.request();
let i = 0;
const text = sqlQuery.replace(/\?/g, () => { const n = `p${i}`; req.input(n, params[i]); i++; return `@${n}`; });
const result = await req.query(text);
return result.recordset || [];
}
function isAlreadyExists(e) {
const m = (e.message || '').toLowerCase();
return m.includes('already exists') || m.includes('there is already an object');
}
async function bootstrap() {
console.log('[OEE] Checking table', TABLE, '…');
await exec(`
CREATE TABLE ${TABLE} (
ID INT IDENTITY(1,1) PRIMARY KEY,
COMPANY NVARCHAR(100),
TAB NVARCHAR(50) NOT NULL,
MONTH NVARCHAR(20) NOT NULL,
ROWS_JSON NVARCHAR(MAX),
REMARK NVARCHAR(MAX),
CANCELED BIT DEFAULT 0,
CREATED_BY NVARCHAR(50),
CREATED_BY_ID INT,
CREATED_AT DATETIME2,
UPDATED_AT DATETIME2
)
`).catch(e => { if (!isAlreadyExists(e)) throw e; else console.log('[OEE] Table already exists — OK'); });
console.log('[OEE] ✅ Ready');
}
function safeJson(v, fb) { if (!v) return fb; try { return JSON.parse(v); } catch (_e) { return fb; } }
function fromRow(r, withRows) {
if (!r) return null;
const o = {
id: r.ID,
company: r.COMPANY || '',
tab: r.TAB,
month: r.MONTH,
remark: r.REMARK || '',
canceled: !!r.CANCELED,
createdBy: r.CREATED_BY || '',
createdById: r.CREATED_BY_ID || null,
createdAt: r.CREATED_AT ? new Date(r.CREATED_AT).toISOString() : null,
updatedAt: r.UPDATED_AT ? new Date(r.UPDATED_AT).toISOString() : null,
rowCount: 0,
};
const rows = safeJson(r.ROWS_JSON, []);
o.rowCount = Array.isArray(rows) ? rows.length : 0;
if (withRows) o.rows = Array.isArray(rows) ? rows : [];
return o;
}
// List documents (headers only). Optional filters: company, tab, month, and
// an allow-set of tabs (per-user restriction — omit for no restriction).
async function list({ company, tab, month, monthFrom, monthTo, allowTabs, top = 30, skip = 0 } = {}) {
const where = []; const params = [];
if (company) { where.push(`COMPANY = ?`); params.push(company); }
if (tab) { where.push(`TAB = ?`); params.push(tab); }
if (month) { where.push(`MONTH = ?`); params.push(month); }
if (monthFrom) { where.push(`MONTH >= ?`); params.push(monthFrom); }
if (monthTo) { where.push(`MONTH <= ?`); params.push(monthTo); }
if (Array.isArray(allowTabs) && allowTabs.length) {
where.push(`TAB IN (${allowTabs.map(() => '?').join(',')})`);
allowTabs.forEach(t => params.push(t));
}
const w = where.length ? `WHERE ${where.join(' AND ')}` : '';
const rows = await exec(
`SELECT * FROM ${TABLE} ${w} ORDER BY ID DESC OFFSET ${Math.max(0, skip | 0)} ROWS FETCH NEXT ${Math.min(100, top | 0)} ROWS ONLY`,
params
);
// Genuine total (same filter, no paging) — drives "Page X of Y" on the
// client instead of an open-ended "Page X". Wrapped so a COUNT failure
// never breaks the list itself — the page still loads, just without a total.
let total = null;
try {
const countRows = await exec(`SELECT COUNT(*) AS n FROM ${TABLE} ${w}`, params);
const n = Number(countRows?.[0]?.n);
total = isNaN(n) ? null : n;
} catch (e) { console.warn('[OEE-STORE] COUNT(*) failed — pager will show no total:', e.message); }
return { data: rows.map(r => fromRow(r, false)), total };
}
async function getById(id) {
const rows = await exec(`SELECT * FROM ${TABLE} WHERE ID = ?`, [parseInt(id)]);
return rows.length ? fromRow(rows[0], true) : null;
}
// Every non-cancelled document (WITH its full row data) across a month
// range, for a report that needs to scan actual shift rows rather than just
// list documents — e.g. Downtime Analysis, which pulls every shift entry
// across every tab/machine for a date range. Unbounded (no top/skip): this
// is for server-side aggregation, not a paginated UI list.
async function listAllInRange({ company, monthFrom, monthTo } = {}) {
const where = [`(CANCELED = 0 OR CANCELED IS NULL)`]; const params = [];
if (company) { where.push(`COMPANY = ?`); params.push(company); }
if (monthFrom){ where.push(`MONTH >= ?`); params.push(monthFrom); }
if (monthTo) { where.push(`MONTH <= ?`); params.push(monthTo); }
const rows = await exec(`SELECT * FROM ${TABLE} WHERE ${where.join(' AND ')}`, params);
return rows.map(r => fromRow(r, true));
}
async function create({ company, tab, month, rows, remark, createdBy, createdById }) {
const now = new Date().toISOString().replace('T', ' ').replace('Z', '').substring(0, 23);
const idRows = await exec(
`INSERT INTO ${TABLE} (COMPANY, TAB, MONTH, ROWS_JSON, REMARK, CANCELED, CREATED_BY, CREATED_BY_ID, CREATED_AT, UPDATED_AT)
VALUES (?, ?, ?, ?, ?, 0, ?, ?, ?, ?);
SELECT SCOPE_IDENTITY() AS ID;`,
[company || '', tab, month, JSON.stringify(Array.isArray(rows) ? rows : []), remark || null,
createdBy || null, createdById || null, now, now]
);
return idRows[0].ID;
}
async function update(id, { month, rows, remark }) {
const sets = []; const vals = [];
if (month !== undefined) { sets.push('MONTH = ?'); vals.push(month); }
if (rows !== undefined) { sets.push('ROWS_JSON = ?'); vals.push(JSON.stringify(Array.isArray(rows) ? rows : [])); }
if (remark !== undefined) { sets.push('REMARK = ?'); vals.push(remark || null); }
sets.push('UPDATED_AT = ?'); vals.push(new Date().toISOString().replace('T', ' ').replace('Z', '').substring(0, 23));
vals.push(parseInt(id));
await exec(`UPDATE ${TABLE} SET ${sets.join(', ')} WHERE ID = ?`, vals);
}
async function cancel(id) {
await exec(`UPDATE ${TABLE} SET CANCELED = 1, UPDATED_AT = SYSUTCDATETIME() WHERE ID = ?`, [parseInt(id)]);
}
module.exports = { bootstrap, list, getById, create, update, cancel, listAllInRange };