@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user