added joint approval in resignation for RBM DD-ZM approval stage

This commit is contained in:
laxman h 2026-04-29 16:37:45 +05:30
parent ede68caefc
commit 8d7805acc9
3 changed files with 83 additions and 13 deletions

View File

@ -22,6 +22,7 @@ import { getResignationStatusForStage, normalizeClearanceStatus } from '../../co
import { resolveEntityUuidByType } from '../../common/utils/requestResolver.js';
import { OFFBOARDING_ACTIONS } from '../../common/config/constants.js';
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorknote.js';
// Removed generateResignationId and moved to NomenclatureService
const resolveResignationUuid = async (id: string) => {
@ -44,7 +45,7 @@ export const createResignation = async (req: AuthRequest, res: Response, next: N
}
const existingResignation = await db.Resignation.findOne({
where: { outletId, status: { [Op.notIn]: ['Completed', 'Rejected'] } }
where: { outletId, status: { [Op.notIn]: ['Completed', 'Rejected', 'Withdrawn', 'Revoked'] } }
});
if (existingResignation) {
await transaction.rollback();
@ -400,6 +401,66 @@ export const approveResignation = async (req: AuthRequest, res: Response, next:
const sourceStage = resignation.currentStage;
// JOINT APPROVAL LOGIC FOR RBM STAGE
if (sourceStage === RESIGNATION_STAGES.RBM) {
// Log the current user's approval in audit
await db.ResignationAudit.create({
userId: req.user.id,
resignationId: resignation.id,
action: 'PARTIAL_APPROVE',
remarks: `Partial approval by ${req.user.roleCode}${remarks ? ': ' + remarks : ''}`,
details: { roleCode: req.user.roleCode, stage: sourceStage }
}, { transaction });
// Ensure worknote is added for this partial approval
if (remarks) {
await writeWorkflowActivityWorknote({
requestId: resignation.id,
requestType: 'resignation',
userId: req.user.id,
noteText: `Approved: ${remarks}`,
noteType: 'internal'
});
}
// Check if both RBM and DD_ZM have approved
const requiredRoles = [ROLES.RBM, ROLES.DD_ZM];
const partialLogs = await db.ResignationAudit.findAll({
where: {
resignationId: resignation.id,
action: 'PARTIAL_APPROVE'
},
transaction
});
const approvedRoles = new Set(
partialLogs.map((log: any) => log.details?.roleCode)
);
const hasAllRequiredApprovals = requiredRoles.every(role => approvedRoles.has(role));
if (!hasAllRequiredApprovals) {
// Append to timeline directly without transitioning the stage
const timelineEntry = {
stage: sourceStage,
targetStage: nextStage,
timestamp: new Date(),
user: req.user.fullName,
action: `Approved by ${req.user.roleCode}`,
remarks: remarks || ''
};
const updatedTimeline = [...(resignation.timeline || []), timelineEntry];
await resignation.update({ timeline: updatedTimeline }, { transaction });
await transaction.commit();
return res.json({
success: true,
message: 'Approval recorded. Waiting for the other required approver (RBM or DD-ZM).',
resignation
});
}
}
// Transition via Workflow Service
await ResignationWorkflowService.transitionResignation(resignation, nextStage, req.user.id, {
remarks,

View File

@ -148,9 +148,9 @@ export class ResignationWorkflowService {
if (!user) return false;
if (user.roleCode === ROLES.SUPER_ADMIN) return true;
const stageToRole: Record<string, string> = {
const stageToRole: Record<string, string | string[]> = {
[RESIGNATION_STAGES.ASM]: ROLES.ASM,
[RESIGNATION_STAGES.RBM]: ROLES.RBM,
[RESIGNATION_STAGES.RBM]: [ROLES.RBM, ROLES.DD_ZM],
[RESIGNATION_STAGES.ZBH]: ROLES.ZBH,
[RESIGNATION_STAGES.DD_LEAD]: ROLES.DD_LEAD,
[RESIGNATION_STAGES.NBH]: ROLES.NBH,
@ -160,6 +160,9 @@ export class ResignationWorkflowService {
};
const requiredRole = stageToRole[resignation.currentStage];
if (Array.isArray(requiredRole)) {
return requiredRole.includes(user.roleCode);
}
return user.roleCode === requiredRole;
}
}

View File

@ -14,6 +14,7 @@ const EMAILS = {
DEALER: args.dealerEmail,
ASM: 'abhishek@royalenfield.com',
RBM: 'manish@royalenfield.com',
DD_ZM: 'piyush@royalenfield.com',
ZBH: 'manav@royalenfield.com',
DD_LEAD: 'jaya@royalenfield.com',
NBH: 'yashwin@royalenfield.com',
@ -158,13 +159,14 @@ async function run() {
await delay();
const approvals = [
{ name: 'ASM', email: EMAILS.ASM, remarks: 'Verified physical assets and dealer intent. Recommended for resignation.' },
{ name: 'RBM', email: EMAILS.RBM, remarks: 'No active sales pipeline issues in the territory. Transition plan discussed.' },
{ name: 'ZBH', email: EMAILS.ZBH, remarks: 'Zone-level resource reallocation planned. Approved.' },
{ name: 'DD Lead', email: EMAILS.DD_LEAD, remarks: 'Dealer development criteria satisfied for exit.' },
{ name: 'NBH', email: EMAILS.NBH, remarks: 'HQ clearance granted. Proceed with F&F initiation.' },
{ name: 'DD Admin', email: EMAILS.DD_ADMIN, remarks: 'Administrative checks complete. Initiating termination of commercial contract.' },
{ name: 'Legal Admin', email: EMAILS.LEGAL, remarks: 'Legal documentation verified. No active litigation found.' }
{ stage: 'ASM', name: 'ASM', email: EMAILS.ASM, remarks: 'Verified physical assets and dealer intent. Recommended for resignation.' },
{ stage: 'RBM', name: 'RBM', email: EMAILS.RBM, remarks: 'No active sales pipeline issues in the territory. Transition plan discussed.' },
{ stage: 'RBM', name: 'DD-ZM', email: EMAILS.DD_ZM, remarks: 'Joint approval evaluated and verified by DD-ZM.' },
{ stage: 'ZBH', name: 'ZBH', email: EMAILS.ZBH, remarks: 'Zone-level resource reallocation planned. Approved.' },
{ stage: 'DD Lead', name: 'DD Lead', email: EMAILS.DD_LEAD, remarks: 'Dealer development criteria satisfied for exit.' },
{ stage: 'NBH', name: 'NBH', email: EMAILS.NBH, remarks: 'HQ clearance granted. Proceed with F&F initiation.' },
{ stage: 'DD Admin', name: 'DD Admin', email: EMAILS.DD_ADMIN, remarks: 'Administrative checks complete. Initiating termination of commercial contract.' },
{ stage: 'Legal', name: 'Legal Admin', email: EMAILS.LEGAL, remarks: 'Legal documentation verified. No active litigation found.' }
];
// Fetch resignation data to determine current stage for skipping
@ -174,13 +176,17 @@ async function run() {
console.log(`Current Stage: ${currentStage}`);
const stageOrder = [
'ASM', 'RBM', 'ZBH', 'DD Lead', 'NBH', 'DD Admin', 'Legal Admin', 'F&F Initiated', 'Completed'
'ASM', 'RBM', 'ZBH', 'DD Lead', 'NBH', 'DD Admin', 'Legal', 'F&F Initiated', 'Completed'
];
const startIndex = stageOrder.indexOf(currentStage) === -1 ? 0 : stageOrder.indexOf(currentStage);
let startStageIndex = stageOrder.indexOf(currentStage) === -1 ? 0 : stageOrder.indexOf(currentStage);
const startingStage = stageOrder[startStageIndex];
let startApproveIndex = approvals.findIndex(a => a.stage === startingStage);
if (startApproveIndex === -1) startApproveIndex = 0;
let currentStep = 2;
for (let i = startIndex; i < approvals.length; i++) {
for (let i = startApproveIndex; i < approvals.length; i++) {
const actor = approvals[i];
log(currentStep, `${actor.name} (${actor.email}) approving...`);
const token = await login(actor.email);