sale order
SAP-ERP Portal CI/CD / build (push) Successful in 3m57s

This commit is contained in:
John
2026-10-05 18:45:17 +05:30
parent e725a6571b
commit eead8f5ffd
129 changed files with 14252 additions and 452 deletions
+40 -1
View File
@@ -140,6 +140,23 @@ async function bootstrap() {
function toTs(iso) { return iso ? iso.replace('T', ' ').replace('Z', '').substring(0, 23) : null; }
function safeJson(v, f) { if (!v) return f; try { return JSON.parse(v); } catch { return f; } }
// This Production Order's item's SAP Item Group (ItmsGrpCod), for
// notifyStore's Item-Group email routing (Admin → System Settings → "Notify
// by Item Group"). Queried against the SAP company's OWN database via
// services/sqlPool.js's getPool(company) — NOT this file's own getConn(),
// which only ever points at the portal's local APP_SQL_* tracking DB, not
// SAP. Never throws — a lookup failure just means no group-routed
// recipients get added, the normal step/module recipients still fire.
async function itemGroupOf(itemCode, company) {
if (!itemCode) return null;
try {
const { getPool } = require('./sqlPool');
const pool = await getPool(company || null);
const r = await pool.request().query(`SELECT "ItmsGrpCod" FROM [dbo].[OITM] WHERE "ItemCode" = '${String(itemCode).replace(/'/g, "''")}'`);
return r.recordset && r.recordset[0] ? String(r.recordset[0].ItmsGrpCod) : null;
} catch (e) { console.warn('[PO-STORE] itemGroupOf lookup failed:', e.message); return null; }
}
function fromRow(row) {
if (!row) return null;
const stage = row.STAGE || 0;
@@ -211,6 +228,7 @@ async function insertProductionOrder(p) {
const created = await findById(idRows[0].ID);
notify().notify({
stepFullKey: notifyStepFor(created, STEP_KEYS[0]),
itemGroupCode: await itemGroupOf(created.itemCode, created.company),
title: `Production Order ${created.sapDocNum || '#' + created.id} — awaiting ${STEPS[0]}`,
lines: [['Production Order', created.sapDocNum || '#' + created.id], ['Item', created.itemName || created.itemCode], ['Created By', created.createdByName], ['Pending Step', STEPS[0]]],
url: `${process.env.APP_BASE_URL || ''}/production-order?id=${created.id}`,
@@ -338,6 +356,7 @@ async function advanceStage(id, { action, stepKey, by, byName, remarks }) {
const nextStepKey = STEP_KEYS[newStage];
notify().notify({
stepFullKey: notifyStepFor(advanced, nextStepKey),
itemGroupCode: await itemGroupOf(advanced.itemCode, advanced.company),
title: `Production Order ${advanced.sapDocNum || '#' + advanced.id} — awaiting ${STEPS[newStage]}`,
lines: [['Production Order', advanced.sapDocNum || '#' + advanced.id], ['Item', advanced.itemName || advanced.itemCode], ['Completed By', byName || by], ['Pending Step', STEPS[newStage]]],
url,
@@ -424,6 +443,7 @@ async function catchUpStage(id, targetStage, { by, byName, remarks }) {
const nextStepKey = STEP_KEYS[targetStage];
notify().notify({
stepFullKey: notifyStepFor(caughtUp, nextStepKey),
itemGroupCode: await itemGroupOf(caughtUp.itemCode, caughtUp.company),
title: `Production Order ${caughtUp.sapDocNum || '#' + caughtUp.id} — awaiting ${STEPS[targetStage]}`,
lines: [['Production Order', caughtUp.sapDocNum || '#' + caughtUp.id], ['Item', caughtUp.itemName || caughtUp.itemCode], ['Pending Step', STEPS[targetStage]]],
url,
@@ -478,8 +498,27 @@ async function receiveManual(id, { by, byName, remarks }) {
return findById(id);
}
// BATCH_NUMBER/MFG_DATE/EXP_DATE are copied from the Work Order at
// insertProductionOrder() time and never touched again after that — a
// deliberate snapshot everywhere else (see LOCKED_BATCH_FIELDS in
// routes/batchIntimations.js), EXCEPT a rejected Work Order can still be
// edited+resubmitted with a corrected batch no. even after its Production
// Order already exists (rejection only checks WO status, not whether a PO
// was generated earlier). Without this, the Production Order — and every
// screen that searches/displays batch no. off it (Production Order list,
// Issue for Production) — permanently keeps showing the OLD batch even
// though the Work Order and SAP's own OWOR.Comments have the corrected one.
// Called from workOrderStore.resubmitAfterReject(); a no-op if no Production
// Order was ever generated for this Work Order.
async function syncBatchFromWorkOrder(workOrderId, { batchNumber, mfgDate, expDate }) {
await exec(
`UPDATE ${TABLE} SET BATCH_NUMBER=?, MFG_DATE=?, EXP_DATE=?, UPDATED_AT=? WHERE WORK_ORDER_ID=? AND (IS_DELETED=0 OR IS_DELETED IS NULL)`,
[batchNumber || '', mfgDate || '', expDate || '', toTs(new Date().toISOString()), parseInt(workOrderId)]
);
}
module.exports = {
bootstrap, STEPS, STEP_KEYS, ISSUANCE_STAGE, insertProductionOrder,
listProductionOrders, findById, findBySapAbsEntry, advanceStage, verify, receiveManual, softDelete,
adminForceClose, adminForceCancel, catchUpStage,
adminForceClose, adminForceCancel, catchUpStage, syncBatchFromWorkOrder,
};