140 lines
7.1 KiB
JavaScript
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;
|