added joint approval in resignation for RBM DD-ZM approval stage
This commit is contained in:
parent
ede68caefc
commit
8d7805acc9
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user