few more bugs fixed and dealer ide ui alignmen and visibibity chabges done F& F made manuall trigger

This commit is contained in:
Laxman 2026-05-14 14:38:35 +05:30
parent fb07f7ab61
commit e99a28b7f7
8 changed files with 42 additions and 26 deletions

View File

@ -35,6 +35,8 @@ console.log('✓ Termination stage resolution passed.');
console.log('Testing getPreviousStage (Resignation)...'); console.log('Testing getPreviousStage (Resignation)...');
assert.equal(getPreviousStage(REQUEST_TYPES.RESIGNATION, RESIGNATION_STAGES.RBM), RESIGNATION_STAGES.ASM); assert.equal(getPreviousStage(REQUEST_TYPES.RESIGNATION, RESIGNATION_STAGES.RBM), RESIGNATION_STAGES.ASM);
assert.equal(getPreviousStage(REQUEST_TYPES.RESIGNATION, RESIGNATION_STAGES.ZBH), RESIGNATION_STAGES.RBM); assert.equal(getPreviousStage(REQUEST_TYPES.RESIGNATION, RESIGNATION_STAGES.ZBH), RESIGNATION_STAGES.RBM);
assert.equal(getPreviousStage(REQUEST_TYPES.RESIGNATION, RESIGNATION_STAGES.FNF_INITIATED), RESIGNATION_STAGES.AWAITING_FNF);
assert.equal(getPreviousStage(REQUEST_TYPES.RESIGNATION, RESIGNATION_STAGES.DD_ADMIN), RESIGNATION_STAGES.LEGAL);
assert.equal(getPreviousStage(REQUEST_TYPES.RESIGNATION, RESIGNATION_STAGES.COMPLETED), RESIGNATION_STAGES.FNF_INITIATED); assert.equal(getPreviousStage(REQUEST_TYPES.RESIGNATION, RESIGNATION_STAGES.COMPLETED), RESIGNATION_STAGES.FNF_INITIATED);
console.log('✓ Resignation stage resolution passed.'); console.log('✓ Resignation stage resolution passed.');

View File

@ -200,6 +200,8 @@ export const RESIGNATION_STAGES = {
DD_LEAD: 'DD Lead', DD_LEAD: 'DD Lead',
NBH: 'NBH', NBH: 'NBH',
DD_ADMIN: 'DD Admin', DD_ADMIN: 'DD Admin',
/** Post DD Admin — workflow paused until an authorized user runs Push to F&F (no automatic F&F). */
AWAITING_FNF: 'Awaiting F&F',
LEGAL: 'Legal', LEGAL: 'Legal',
SPARES_CLEARANCE: 'Spares Clearance', SPARES_CLEARANCE: 'Spares Clearance',
SERVICE_CLEARANCE: 'Service Clearance', SERVICE_CLEARANCE: 'Service Clearance',

View File

@ -30,6 +30,8 @@ export const getResignationStatusForStage = (stage: string): string => {
case RESIGNATION_STAGES.NBH: case RESIGNATION_STAGES.NBH:
case RESIGNATION_STAGES.DD_ADMIN: case RESIGNATION_STAGES.DD_ADMIN:
return `${stage} Review`; return `${stage} Review`;
case RESIGNATION_STAGES.AWAITING_FNF:
return 'Awaiting F&F — manual initiation';
case RESIGNATION_STAGES.LEGAL: case RESIGNATION_STAGES.LEGAL:
return 'Legal - Resignation Letter'; return 'Legal - Resignation Letter';
case RESIGNATION_STAGES.FNF_INITIATED: case RESIGNATION_STAGES.FNF_INITIATED:

View File

@ -39,9 +39,10 @@ export const getPreviousStage = (requestType: string, currentStage: string): str
[RESIGNATION_STAGES.ZBH]: RESIGNATION_STAGES.RBM, [RESIGNATION_STAGES.ZBH]: RESIGNATION_STAGES.RBM,
[RESIGNATION_STAGES.DD_LEAD]: RESIGNATION_STAGES.ZBH, [RESIGNATION_STAGES.DD_LEAD]: RESIGNATION_STAGES.ZBH,
[RESIGNATION_STAGES.NBH]: RESIGNATION_STAGES.DD_LEAD, [RESIGNATION_STAGES.NBH]: RESIGNATION_STAGES.DD_LEAD,
[RESIGNATION_STAGES.DD_ADMIN]: RESIGNATION_STAGES.NBH, [RESIGNATION_STAGES.LEGAL]: RESIGNATION_STAGES.NBH,
[RESIGNATION_STAGES.LEGAL]: RESIGNATION_STAGES.DD_ADMIN, [RESIGNATION_STAGES.DD_ADMIN]: RESIGNATION_STAGES.LEGAL,
[RESIGNATION_STAGES.FNF_INITIATED]: RESIGNATION_STAGES.LEGAL, [RESIGNATION_STAGES.AWAITING_FNF]: RESIGNATION_STAGES.DD_ADMIN,
[RESIGNATION_STAGES.FNF_INITIATED]: RESIGNATION_STAGES.AWAITING_FNF,
[RESIGNATION_STAGES.COMPLETED]: RESIGNATION_STAGES.FNF_INITIATED [RESIGNATION_STAGES.COMPLETED]: RESIGNATION_STAGES.FNF_INITIATED
}; };
return flow[currentStage] || null; return flow[currentStage] || null;

View File

@ -247,6 +247,7 @@ export async function resolveNextActors(requestId: string, requestType: string,
// --- Resignation Specific --- // --- Resignation Specific ---
'DD Admin': [ROLES.DD_ADMIN], 'DD Admin': [ROLES.DD_ADMIN],
'Awaiting F&F': [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.NBH, ROLES.DD_ADMIN],
'Spares Clearance': [ROLES.SPARES_MANAGER], 'Spares Clearance': [ROLES.SPARES_MANAGER],
'Service Clearance': [ROLES.SERVICE_MANAGER], 'Service Clearance': [ROLES.SERVICE_MANAGER],
'Accounts Clearance': [ROLES.ACCOUNTS_MANAGER], 'Accounts Clearance': [ROLES.ACCOUNTS_MANAGER],

View File

@ -397,7 +397,8 @@ export const approveResignation = async (req: AuthRequest, res: Response, next:
[RESIGNATION_STAGES.DD_LEAD]: RESIGNATION_STAGES.NBH, [RESIGNATION_STAGES.DD_LEAD]: RESIGNATION_STAGES.NBH,
[RESIGNATION_STAGES.NBH]: RESIGNATION_STAGES.LEGAL, [RESIGNATION_STAGES.NBH]: RESIGNATION_STAGES.LEGAL,
[RESIGNATION_STAGES.LEGAL]: RESIGNATION_STAGES.DD_ADMIN, [RESIGNATION_STAGES.LEGAL]: RESIGNATION_STAGES.DD_ADMIN,
[RESIGNATION_STAGES.DD_ADMIN]: RESIGNATION_STAGES.FNF_INITIATED, // DD Admin approval moves to F&F initiation // DD Admin approval completes internal review; F&F is started only via explicit Push to F&F.
[RESIGNATION_STAGES.DD_ADMIN]: RESIGNATION_STAGES.AWAITING_FNF,
[RESIGNATION_STAGES.FNF_INITIATED]: RESIGNATION_STAGES.COMPLETED [RESIGNATION_STAGES.FNF_INITIATED]: RESIGNATION_STAGES.COMPLETED
}; };
@ -407,16 +408,16 @@ export const approveResignation = async (req: AuthRequest, res: Response, next:
return res.status(400).json({ success: false, message: 'Cannot move to next stage from current state' }); return res.status(400).json({ success: false, message: 'Cannot move to next stage from current state' });
} }
// Guard before transition: F&F initiation is allowed only on/after LWD as per SRS §4.2.2.8 // F&F records are created only from explicit Push to F&F (targetStage), not from sequential approvals.
// LWD gate applies to that manual push (SRS §4.2.2.8).
let shouldTriggerFnF = false; let shouldTriggerFnF = false;
if (nextStage === RESIGNATION_STAGES.FNF_INITIATED) { if (nextStage === RESIGNATION_STAGES.FNF_INITIATED && targetOverride) {
const today = new Date(); const today = new Date();
const lwdString = resignation.lastOperationalDateServices || resignation.lastOperationalDateSales; const lwdString = resignation.lastOperationalDateServices || resignation.lastOperationalDateSales;
const { force } = req.body; const { force } = req.body;
const lwd = lwdString ? new Date(lwdString) : null; const lwd = lwdString ? new Date(lwdString) : null;
if (lwd) { if (lwd) {
// Clear time for date-only comparison
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
lwd.setHours(0, 0, 0, 0); lwd.setHours(0, 0, 0, 0);
} }
@ -535,9 +536,14 @@ export const approveResignation = async (req: AuthRequest, res: Response, next:
await transaction.commit(); await transaction.commit();
const message = (sourceStage === RESIGNATION_STAGES.LEGAL && nextStage === RESIGNATION_STAGES.LEGAL) let message = 'Resignation approved successfully';
? 'Legal stage approved successfully. Use Push to F&F to initiate settlement as per LWD rules.' if (nextStage === RESIGNATION_STAGES.AWAITING_FNF) {
: 'Resignation approved successfully'; message =
'DD Admin approval recorded. Use Push to F&F when ready to create the Full & Final settlement (Last Working Day rules apply).';
} else if (sourceStage === RESIGNATION_STAGES.LEGAL && nextStage === RESIGNATION_STAGES.DD_ADMIN) {
message =
'Legal stage approved successfully. After DD Admin review, use Push to F&F to start settlement when ready.';
}
res.json({ success: true, message, nextStage, resignation }); res.json({ success: true, message, nextStage, resignation });
} catch (error) { } catch (error) {
@ -608,6 +614,7 @@ export const withdrawResignation = async (req: AuthRequest, res: Response, next:
const restrictedStages = [ const restrictedStages = [
RESIGNATION_STAGES.NBH, RESIGNATION_STAGES.NBH,
RESIGNATION_STAGES.DD_ADMIN, RESIGNATION_STAGES.DD_ADMIN,
RESIGNATION_STAGES.AWAITING_FNF,
RESIGNATION_STAGES.LEGAL, RESIGNATION_STAGES.LEGAL,
RESIGNATION_STAGES.FNF_INITIATED, RESIGNATION_STAGES.FNF_INITIATED,
RESIGNATION_STAGES.COMPLETED RESIGNATION_STAGES.COMPLETED
@ -1047,10 +1054,15 @@ export const updateResignationStatus = async (req: AuthRequest, res: Response, n
} }
// SRS-aligned gate: F&F can start only after Legal completion artifacts. // SRS-aligned gate: F&F can start only after Legal completion artifacts.
if (resignation.currentStage !== RESIGNATION_STAGES.LEGAL) { const pushAllowedStages = [
RESIGNATION_STAGES.AWAITING_FNF,
// Legacy rows: acceptance letter uploaded at Legal before DD Admin step completed in older builds
RESIGNATION_STAGES.LEGAL
];
if (!pushAllowedStages.includes(resignation.currentStage as any)) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: `Cannot trigger F&F from ${resignation.currentStage}. Move request to Legal stage first.` message: `Cannot trigger F&F from ${resignation.currentStage}. Complete DD Admin review first (stage must be Awaiting F&F), or use Legal only for legacy cases.`
}); });
} }

View File

@ -618,24 +618,18 @@ export const updateTerminationStatus = async (req: AuthRequest, res: Response, n
transaction transaction
}); });
// If Terminated, trigger F&F initiation via Workflow Service // F&F is never started automatically on termination; authorized users run Push to F&F when ready.
// SRS REQUIREMENT: F&F settlement process is triggered only on the Last Working Day (LWD)
if (nextStage === TERMINATION_STAGES.TERMINATED) { if (nextStage === TERMINATION_STAGES.TERMINATED) {
const today = new Date(); const today = new Date();
const lwd = new Date(termination.proposedLwd); const lwd = new Date(termination.proposedLwd);
// Clear time components for date-only comparison
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
lwd.setHours(0, 0, 0, 0); lwd.setHours(0, 0, 0, 0);
const statusAfterTerm =
if (today >= lwd) { today < lwd ? 'Awaiting F&F (LWD Pending)' : 'Awaiting F&F';
logger.info(`[TerminationController] LWD reached or passed (${termination.proposedLwd}). Initiating F&F.`); await termination.update({ status: statusAfterTerm }, { transaction });
await TerminationWorkflowService.initiateFnF(termination, req.user.id, transaction); logger.info(
} else { `[TerminationController] Termination reached TERMINATED. F&F must be started manually (Push to F&F). LWD=${termination.proposedLwd}, status=${statusAfterTerm}`
logger.info(`[TerminationController] Termination approved but LWD (${termination.proposedLwd}) not yet reached. F&F will be triggered on LWD.`); );
// Keep parent status aligned while waiting for LWD-triggered F&F
await termination.update({ status: 'Awaiting F&F (LWD Pending)' }, { transaction });
}
} }
} }

View File

@ -135,6 +135,7 @@ export class ResignationWorkflowService {
[RESIGNATION_STAGES.NBH]: 65, [RESIGNATION_STAGES.NBH]: 65,
[RESIGNATION_STAGES.LEGAL]: 80, [RESIGNATION_STAGES.LEGAL]: 80,
[RESIGNATION_STAGES.DD_ADMIN]: 90, [RESIGNATION_STAGES.DD_ADMIN]: 90,
[RESIGNATION_STAGES.AWAITING_FNF]: 92,
[RESIGNATION_STAGES.FNF_INITIATED]: 95, [RESIGNATION_STAGES.FNF_INITIATED]: 95,
[RESIGNATION_STAGES.COMPLETED]: 100, [RESIGNATION_STAGES.COMPLETED]: 100,
[RESIGNATION_STAGES.REJECTED]: 100 [RESIGNATION_STAGES.REJECTED]: 100
@ -157,6 +158,7 @@ export class ResignationWorkflowService {
[RESIGNATION_STAGES.NBH]: ROLES.NBH, [RESIGNATION_STAGES.NBH]: ROLES.NBH,
[RESIGNATION_STAGES.LEGAL]: ROLES.LEGAL_ADMIN, [RESIGNATION_STAGES.LEGAL]: ROLES.LEGAL_ADMIN,
[RESIGNATION_STAGES.DD_ADMIN]: ROLES.DD_ADMIN, [RESIGNATION_STAGES.DD_ADMIN]: ROLES.DD_ADMIN,
[RESIGNATION_STAGES.AWAITING_FNF]: [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.NBH, ROLES.DD_ADMIN],
[RESIGNATION_STAGES.FNF_INITIATED]: ROLES.DD_ADMIN [RESIGNATION_STAGES.FNF_INITIATED]: ROLES.DD_ADMIN
}; };