Files
sap-erp/routes/notifications.js
T
John 69b4e68baf
SAP-ERP Portal CI/CD / build (push) Failing after 5m20s
first commit
2026-09-23 17:31:02 +05:30

140 lines
7.1 KiB
JavaScript

'use strict';
// routes/notifications.js — aggregates "pending on me" items across the
// workflows that already have clear, per-user pending logic (Work Order,
// Production Order, BOM Requests, and — admin only — Password Reset
// requests), for the sidebar bell + dashboard "Pending Actions" widget.
// Deliberately scoped to these four for now; other approval workflows
// (Purchase Requests, Customer/Vendor registration, Project approvals,
// etc.) aren't included yet.
const express = require('express');
const router = express.Router();
const { verifyToken, hasStepPerm, hasStepAssigned } = require('../middleware/auth');
const appSettings = require('../services/appSettingsStore');
// Mirrors routes/workOrders.js's WORK_ORDER_STEP_KEYS (index-aligned with
// services/workOrderStore.js's STEPS) — duplicated here rather than
// exported, matching how work-order.html itself already keeps its own copy.
const WO_STEP_KEYS = ['prepared_qa', 'checked_qc', 'checked_production', 'checked_mgr_production', 'approved_mgr_qa'];
const WO_STEPS = ['Prepared By QA', 'Checked By QC', 'Checked By Store In-Charge', 'Checked By (Manager Production)', 'Approved By (Manager QA)'];
// Mirrors server.js's /api/config approvalMap for BOM Requests — which
// ROLE is on turn for a given status, at the admin-configured BOM levels
// (Settings → bomApprovalLevels; see services/appSettingsStore.js).
function bomApprovalMap() {
const levels = appSettings.bomApprovalLevels();
return {
2: { PENDING: 'manager', L1_APPROVED: 'sap_adder' },
3: { PENDING: 'manager', L1_APPROVED: 'sr_manager', L2_APPROVED: 'sap_adder' },
4: { PENDING: 'manager', L1_APPROVED: 'sr_manager', L2_APPROVED: 'sap_adder', L3_APPROVED: 'sap_adder' },
}[levels] || {};
}
router.get('/pending', verifyToken, async (req, res) => {
const user = req.user;
const items = [];
try {
const wos = await require('../services/workOrderStore').listWorkOrders({ status: 'IN_PROGRESS' });
wos.forEach(w => {
const key = WO_STEP_KEYS[w.stage];
if (key && hasStepPerm(user, `work_order:${key}`, 'approve')) {
items.push({
module: 'work_order', label: 'Work Order', title: w.woNo,
detail: `${WO_STEPS[w.stage]} — ${w.productName || w.productCode || ''}`,
link: `/work-order?open=${w.id}`, id: w.id, cardHref: '/work-order',
});
}
});
} catch (e) { console.warn('[notifications] work order scan failed:', e.message); }
try {
const poStore = require('../services/productionOrderStore');
// Which standalone page each stage's action actually happens on —
// Issue/Receipt/Close each have their own dedicated page (deep-linked
// via #order=<AbsoluteEntry>&company=<co>, same hash convention
// production.html's own action buttons already use to get there).
// Release and Transfer to Finished Goods have no standalone page of
// their own — those stay on production.html's detail popup.
const STAGE_CARD_HREF = { release: '/production', issuance: '/issue-production', receipt: '/receipt-production', transfer_fg: '/production', close: '/close-production' };
const pos = await poStore.listProductionOrders({ status: 'IN_PROGRESS' });
// REJECTED orders (receipt posted with rejection lines) still need to be
// CLOSED — surface them to the close-step users too.
const rejected = await poStore.listProductionOrders({ status: 'REJECTED' });
rejected.forEach(p => { pos.push(Object.assign({}, p, { stage: 4 })); }); // stage 4 = Close
pos.forEach(p => {
const key = poStore.STEP_KEYS[p.stage];
// Same rule as the Issue/Receipt/Close pages themselves: being ASSIGNED
// the step (any perm — view/add/edit/approve) means the action is yours,
// so it must show in the bell too. (Was 'approve'-only, which hid e.g.
// pending Receipts from users holding view/add/edit.)
if (key && hasStepAssigned(user, `production_order:${key}`)) {
const cardHref = STAGE_CARD_HREF[key] || '/production';
const link = p.sapAbsEntry
? (cardHref === '/production'
? `/production?open=${p.sapAbsEntry}`
: `${cardHref}#order=${p.sapAbsEntry}&company=${encodeURIComponent(p.company || '')}`)
: '/production';
items.push({
module: 'production_order', label: 'Production Order', title: p.sapDocNum ? `#${p.sapDocNum}` : `Item ${p.itemCode}`,
detail: `${poStore.STEPS[p.stage]} — ${p.itemName || p.itemCode || ''}`,
link, id: p.id, cardHref,
});
}
});
} catch (e) { console.warn('[notifications] production order scan failed:', e.message); }
// Batch Issuance intimations awaiting a Work Order — pending for whoever
// holds the "Prepared By QA" (create Work Order) step, same 'add' perm the
// WO create route itself requires. An intimation counts as pending until
// some Work Order references it (ZWORK_ORDERS.INTIMATION_ID).
try {
if (hasStepPerm(user, 'work_order:prepared_qa', 'add')) {
const ints = await require('../services/batchIntimationStore').listIntimations({ status: 'SENT_TO_QA' });
const wos = await require('../services/workOrderStore').listWorkOrders({});
const used = new Set(wos.filter(w => w.intimationId).map(w => String(w.intimationId)));
ints.forEach(bi => {
if (used.has(String(bi.id))) return;
const firstProd = Array.isArray(bi.products) && bi.products[0]
? (bi.products[0].productName || bi.products[0].name || bi.products[0].productCode || '') : '';
items.push({
module: 'batch_issuance', label: 'Batch Issuance', title: bi.docNo || ('#' + bi.id),
detail: `${firstProd ? firstProd + ' — ' : ''}awaiting Work Order`,
link: '/work-order', id: bi.id, cardHref: '/batch-issuance',
});
});
}
} catch (e) { console.warn('[notifications] batch issuance scan failed:', e.message); }
try {
const boms = await require('../services/bomRequestStore').listRequests({ status: 'ALL' });
const map = bomApprovalMap();
boms.forEach(b => {
const turnRole = map[b.status];
if (turnRole && turnRole === user.role) {
items.push({
module: 'bom', label: 'BOM Request', title: b.itemCode,
detail: `${b.itemName || ''} — awaiting your review`,
link: `/approvals?open=${b.id}`, id: b.id, cardHref: '/bom',
});
}
});
} catch (e) { console.warn('[notifications] bom scan failed:', e.message); }
if (user.role === 'admin' || user.role === 'sap_adder') {
try {
const pending = await require('../services/passwordResetStore').listRequests('PENDING');
pending.forEach(r => {
items.push({
module: 'password_reset', label: 'Password Reset', title: '@' + r.username,
detail: r.note || 'Requested a password reset',
link: `/admin?tab=pwresets&open=${r.id}`, id: r.id, cardHref: '/admin',
});
});
} catch (e) { console.warn('[notifications] password reset scan failed:', e.message); }
}
res.json({ success: true, count: items.length, data: items });
});
module.exports = router;