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
+90 -6
View File
@@ -34,6 +34,13 @@ const DEFAULTS = {
// a Production Order with insufficient stock; issuance is what actually
// needs it in hand).
poRequireStockAvailability: 'false',
// Item Master — Item Approval Workflow (routes/itemApproval.js). Default ON
// (current/original behavior): admin/sap_adder/system_admin push a new
// item straight to SAP; everyone else's submission queues as PENDING for
// one of those to review under Approvals → Item Approvals. OFF = no queue
// at all — every submission (any role) pushes directly, same as an
// approver's own submission today.
itemApprovalEnabled: 'true',
receiptRequireFullQty: 'false',
// When receiptRequireFullQty is on, should a by-product/process-loss line
// (e.g. ICO10791 — a negative-Planned-Qty component, its own BaseLine, NOT
@@ -160,6 +167,25 @@ const DEFAULTS = {
// Global on/off switch — SMTP itself is configured via .env (SMTP_HOST/…),
// not here, since this app has no other admin-editable server credentials.
notifyEmailsEnabled: 'true',
// Per-step opt-out — comma-separated stepFullKey/moduleKey values (same
// keys as notifyExtraEmails' targets, e.g. "work_order:prepared_qa" or
// "module:production-batch-issuance") for which stage-change email is
// switched OFF entirely, regardless of who's assigned to that step. Empty
// (default) = every step still emails, same as before this setting
// existed. Distinct from the global notifyEmailsEnabled switch above (that
// silences everything at once) and from a user's own emailNotify opt-out
// (that silences everything for just them) — this is the middle ground:
// one specific stage stays silent for EVERYONE, on purpose.
notifyDisabledSteps: '',
// Short in Stock — Auto Email Alerts (services/shortStockAlertStore.js).
// Fully independent of the notification settings above and of the
// Inventory Status Report screen — a background job (started once from
// server.js) periodically sums each watched item's on-hand quantity and
// emails the concerned users the first time it hits zero. Off by default
// until an admin actually defines a watch-list.
shortStockAlertEnabled: 'false',
// JSON array of {id, type:'ITEM'|'GROUP', value, label, recipients:[username,...]}.
shortStockAlertRules: '[]',
// Company Growth (Board) Dashboard — which SAP Item Groups are allowed to
// appear in the "Sales by Product Group" card and its filter, out of every
// group the SAP company actually has. Comma-separated codes (plain group
@@ -256,13 +282,18 @@ const DEFAULTS = {
// and its reversal, if QA later rejects it — should be posted under.
// Empty = don't send Series at all, SAP picks its own default series.
deviationDamageSeries: '',
// SAP B1 document numbering Series ID (not the series name) that every
// Inventory Transfer this portal posts should use — e.g. series "IC2627"
// is internal ID 28615. Empty = don't send Series at all, SAP picks its
// own default series.
inventoryTransferSeries: '28615',
// Comma-separated subset of SHORTAGE/SUBSTITUTION/DAMAGE — which Deviation
// types an admin has chosen to make available at all. Default = all three
// (today's behavior, unchanged unless an admin opts to narrow it). At
// least one must always stay enabled (enforced in setMany()) — a
// Deviation type with nothing enabled would leave "Raise Deviation" with
// no valid choice.
deviationEnabledTypes: 'SHORTAGE,SUBSTITUTION,DAMAGE',
deviationEnabledTypes: 'SHORTAGE,SUBSTITUTION,DAMAGE,ADDITION',
// Whole-feature switch for the Requirement Production↔Store review loop
// (Production shares a Requirement with Store, Store cross-checks it
// against SAP's own MRP Wizard output and edits it, reverts to
@@ -295,6 +326,45 @@ const DEFAULTS = {
// actually assigned it. Lets an admin cc a supervisor/QA inbox that isn't
// itself an approval-step holder.
notifyExtraEmails: '{}',
// JSON map of SAP Item Group code (string, e.g. "116") -> comma-separated
// extra recipient emails, notified in ADDITION to whoever's already getting
// a stage-change email for that Work Order/Production Order (approval-step
// holders + notifyExtraEmails above) — routed off the ORDER'S MAIN PRODUCT
// item group only, not every component's group, so one order never blasts
// every group-owner touched by its BOM. Lets an admin route notifications
// by product category (e.g. all "Solutions" emails go to one inbox) without
// that person needing to hold an approval step at all. See notifyStore.js's
// groupExtraEmailsFor().
notifyItemGroupEmails: '{}',
// Downtime Analysis report config (admin-editable, Admin → System
// Settings → "Downtime Analysis"). `causes` is the canonical downtime
// taxonomy shown on the report (admin can add more any time); `tabFieldMap`
// translates each OEE tab's OWN raw field keys (public/oee.html's
// OEE_SCHEMA — different tabs use different field names for conceptually
// the same cause, e.g. Sheet's "Head Clean" vs EBB's "Micro Stops") into
// one of those canonical causes, so machines logged under different tabs
// still roll up onto the same report. A tab/field left out of the map
// (or mapped to "") simply doesn't contribute to any cause total — set
// once with a reasonable starting guess, meant to be corrected/extended
// via the admin UI rather than being authoritative on day one. `baseDays`
// is the common "working days" calendar count for `periodFrom`..`periodTo`
// (e.g. 330) — a machine whose own first OEE entry falls after periodFrom
// gets this prorated down automatically (see services/downtimeAnalysisStore.js).
downtimeAnalysisConfig: JSON.stringify({
periodFrom: '', periodTo: '', baseDays: 330,
causes: [
'Water/tea Break', 'Meal break', 'Breakdown', 'Cleaning', 'Changeover',
'Slow speed/High Cycle Time', 'Micro Stops < 10 min (Utility failure etc)',
'Empty run (Shortage of material etc)', 'Start up/shut down',
],
tabFieldMap: {
ebb: { waterTea:'Water/tea Break', meal:'Meal break', breakdown:'Breakdown', cleaning:'Cleaning', changeOver:'Changeover', slowSpeed:'Slow speed/High Cycle Time', microStops:'Micro Stops < 10 min (Utility failure etc)', emptyRun:'Empty run (Shortage of material etc)', startShut:'Start up/shut down' },
pd: { waterTea:'Water/tea Break', meal:'Meal break', breakdown:'Breakdown', cleaning:'Cleaning', changeOver:'Changeover', slowSpeed:'Slow speed/High Cycle Time', microStops:'Micro Stops < 10 min (Utility failure etc)', emptyRun:'Empty run (Shortage of material etc)', startShut:'Start up/shut down' },
sheet: { waterTea:'Water/tea Break', meal:'Meal break', breakdown:'Breakdown', cleaning:'Cleaning', changeOver:'Changeover', slowSpeed:'Slow speed/High Cycle Time', headClean:'Micro Stops < 10 min (Utility failure etc)', otherProblem:'Empty run (Shortage of material etc)', mcStartShut:'Start up/shut down' },
moulding: { waterTea:'Water/tea Break', meal:'Meal break', breakdown:'Breakdown', cleaning:'Cleaning', changeOver:'Changeover', powerCutSlow:'Slow speed/High Cycle Time', headClean:'Micro Stops < 10 min (Utility failure etc)', nozzleClean:'Empty run (Shortage of material etc)', mcStartShut:'Start up/shut down' },
autoclave: {},
},
}),
// Work Order Raw Material table — per-item SOP display factor for the
// "Qty Req./100 ml" figure ONLY. Some raw materials are labeled by their
// supplied strength (e.g. "SODIUM LACTATE 60% USP") — SAP's BOM quantity
@@ -420,7 +490,7 @@ async function setMany(obj, updatedBy) {
// Deviation" would have no valid choice left at all.
if ('deviationEnabledTypes' in (obj || {})) {
const nextTypes = String(obj.deviationEnabledTypes || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean);
if (!nextTypes.length) throw new Error('At least one Deviation type (Shortage / Substitution / Damage-Loss) must stay enabled');
if (!nextTypes.length) throw new Error('At least one Deviation type (Shortage / Substitution / Damage-Loss / Addition) must stay enabled');
}
const now = new Date().toISOString().replace('T', ' ').replace('Z', '').substring(0, 23);
@@ -452,6 +522,7 @@ function itemCodeSeriesFloor() {
} catch { return {}; }
}
function poRequireStockAvailability() { return String(raw('poRequireStockAvailability')).toLowerCase() === 'true'; }
function itemApprovalEnabled() { return String(raw('itemApprovalEnabled')).toLowerCase() !== 'false'; }
function receiptRequireFullQty() { return String(raw('receiptRequireFullQty')).toLowerCase() === 'true'; }
function receiptExcludeByProductFromTotal() { return String(raw('receiptExcludeByProductFromTotal')).toLowerCase() !== 'false'; }
function closeRequireFullQty() { return String(raw('closeRequireFullQty')).toLowerCase() === 'true'; }
@@ -474,11 +545,17 @@ function woEffectiveDate() { return String(raw('woEffectiveDate') || ''); }
function woReviewDate() { return String(raw('woReviewDate') || ''); }
function woLogo() { return String(raw('woLogo') || ''); }
function notifyEmailsEnabled() { return String(raw('notifyEmailsEnabled')).toLowerCase() === 'true'; }
function notifyDisabledSteps() { return String(raw('notifyDisabledSteps') || '').split(',').map(s => s.trim()).filter(Boolean); }
function isNotifyStepEnabled(key) { return key ? !notifyDisabledSteps().includes(key) : true; }
function shortStockAlertEnabled() { return String(raw('shortStockAlertEnabled')).toLowerCase() === 'true'; }
function shortStockAlertRules() { try { const v = JSON.parse(raw('shortStockAlertRules') || '[]'); return Array.isArray(v) ? v : []; } catch { return []; } }
function boardProductGroups() { return String(raw('boardProductGroups') || '').split(',').map(s => s.trim()).filter(Boolean); }
function ppcItemGroups() { return String(raw('ppcItemGroups') || '').split(',').map(s => s.trim()).filter(Boolean); }
function ppcApiKey() { return String(raw('ppcApiKey') || '').trim(); }
function ppcOrderTypes() { return String(raw('ppcOrderTypes') || '').split(',').map(s => s.trim()).filter(Boolean); }
function notifyExtraEmails() { try { return JSON.parse(raw('notifyExtraEmails') || '{}') || {}; } catch { return {}; } }
function notifyItemGroupEmails() { try { return JSON.parse(raw('notifyItemGroupEmails') || '{}') || {}; } catch { return {}; } }
function downtimeAnalysisConfig() { try { return JSON.parse(raw('downtimeAnalysisConfig') || '{}') || {}; } catch { return {}; } }
function reqCalcPeriodAEnabled() { return String(raw('reqCalcPeriodAEnabled')).toLowerCase() === 'true'; }
function reqCalcPeriodBEnabled() { return String(raw('reqCalcPeriodBEnabled')).toLowerCase() === 'true'; }
function reqCalcPeriodALabel() { return String(raw('reqCalcPeriodALabel') || 'Period A').trim() || 'Period A'; }
@@ -505,10 +582,17 @@ function deviationDamageSeries() {
const n = parseInt(v, 10);
return Number.isNaN(n) ? null : n;
}
// Same null-if-unset convention as deviationDamageSeries() above.
function inventoryTransferSeries() {
const v = String(raw('inventoryTransferSeries') || '').trim();
if (!v) return null;
const n = parseInt(v, 10);
return Number.isNaN(n) ? null : n;
}
function deviationEnabledTypes() {
const v = String(raw('deviationEnabledTypes') || '').trim();
const set = new Set(v.split(',').map(s => s.trim().toUpperCase()).filter(Boolean));
return ['SHORTAGE', 'SUBSTITUTION', 'DAMAGE'].filter(t => set.has(t));
return ['SHORTAGE', 'SUBSTITUTION', 'DAMAGE', 'ADDITION'].filter(t => set.has(t));
}
function requirementStoreReviewEnabled() { return String(raw('requirementStoreReviewEnabled')).toLowerCase() === 'true'; }
function requirementStoreReviewHardGate() { return String(raw('requirementStoreReviewHardGate')).toLowerCase() === 'true'; }
@@ -536,13 +620,13 @@ function rejectionStages() {
module.exports = {
bootstrap, reload, getAll, setMany, DEFAULTS,
bomApprovalLevels, workOrderSkipQc, prodOrderTypes,
itemCodeSeriesFloor, poRequireStockAvailability, receiptRequireFullQty, receiptExcludeByProductFromTotal, closeRequireFullQty, poCloseRequireTracking, poRequireFullIssuance, woWeighingBalanceIds, woCustomQtyUnits, woSolutionBatchMaxEditLtr, woSolutionBatchMaxEditLtrPD,
itemCodeSeriesFloor, poRequireStockAvailability, itemApprovalEnabled, receiptRequireFullQty, receiptExcludeByProductFromTotal, closeRequireFullQty, poCloseRequireTracking, poRequireFullIssuance, woWeighingBalanceIds, woCustomQtyUnits, woSolutionBatchMaxEditLtr, woSolutionBatchMaxEditLtrPD,
woSolutionBatchRoundLtr, woSolutionBatchRoundLtrPD, receiptAutoclaveRejection, receiptAutoclaveItemGroups, manpowerTabs,
woCompanyName, woCompanyAddress, woFormNo, woEffectiveDate, woReviewDate, woLogo,
notifyEmailsEnabled, notifyExtraEmails, boardProductGroups, ppcItemGroups, ppcApiKey, ppcOrderTypes,
notifyEmailsEnabled, notifyExtraEmails, notifyItemGroupEmails, downtimeAnalysisConfig, notifyDisabledSteps, isNotifyStepEnabled, shortStockAlertEnabled, shortStockAlertRules, boardProductGroups, ppcItemGroups, ppcApiKey, ppcOrderTypes,
reqCalcPeriodAEnabled, reqCalcPeriodBEnabled, reqCalcPeriodALabel, reqCalcPeriodBLabel,
biAllowExceedPending, biSfgWorkflowEnabled, biMultiSolutionVolumesEnabled, woHideSfgFromPacking, woShowWfiInRawMaterial, woHideStdQtyUnitPicker, rmOvgOverrideEnabled, oeeMachineNames,
deviationScrapWarehouse, deviationRequireQaApproval, deviationDeferIssueUntilApproval, deviationShowRemoveOldLineButton, deviationDamageSeries, deviationEnabledTypes,
deviationScrapWarehouse, deviationRequireQaApproval, deviationDeferIssueUntilApproval, deviationShowRemoveOldLineButton, deviationDamageSeries, deviationEnabledTypes, inventoryTransferSeries,
requirementStoreReviewEnabled, requirementStoreReviewHardGate,
preWoStoreReviewEnabled,
woRawPotencyFactors, woRawQtyIssuedSource, woPackQtyRoundUp,