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 { resolveEntityUuidByType } from '../../common/utils/requestResolver.js';
|
||||||
import { OFFBOARDING_ACTIONS } from '../../common/config/constants.js';
|
import { OFFBOARDING_ACTIONS } from '../../common/config/constants.js';
|
||||||
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
|
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
|
||||||
|
import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorknote.js';
|
||||||
|
|
||||||
// Removed generateResignationId and moved to NomenclatureService
|
// Removed generateResignationId and moved to NomenclatureService
|
||||||
const resolveResignationUuid = async (id: string) => {
|
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({
|
const existingResignation = await db.Resignation.findOne({
|
||||||
where: { outletId, status: { [Op.notIn]: ['Completed', 'Rejected'] } }
|
where: { outletId, status: { [Op.notIn]: ['Completed', 'Rejected', 'Withdrawn', 'Revoked'] } }
|
||||||
});
|
});
|
||||||
if (existingResignation) {
|
if (existingResignation) {
|
||||||
await transaction.rollback();
|
await transaction.rollback();
|
||||||
@ -400,6 +401,66 @@ export const approveResignation = async (req: AuthRequest, res: Response, next:
|
|||||||
|
|
||||||
const sourceStage = resignation.currentStage;
|
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
|
// Transition via Workflow Service
|
||||||
await ResignationWorkflowService.transitionResignation(resignation, nextStage, req.user.id, {
|
await ResignationWorkflowService.transitionResignation(resignation, nextStage, req.user.id, {
|
||||||
remarks,
|
remarks,
|
||||||
|
|||||||
@ -148,9 +148,9 @@ export class ResignationWorkflowService {
|
|||||||
if (!user) return false;
|
if (!user) return false;
|
||||||
if (user.roleCode === ROLES.SUPER_ADMIN) return true;
|
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.ASM]: ROLES.ASM,
|
||||||
[RESIGNATION_STAGES.RBM]: ROLES.RBM,
|
[RESIGNATION_STAGES.RBM]: [ROLES.RBM, ROLES.DD_ZM],
|
||||||
[RESIGNATION_STAGES.ZBH]: ROLES.ZBH,
|
[RESIGNATION_STAGES.ZBH]: ROLES.ZBH,
|
||||||
[RESIGNATION_STAGES.DD_LEAD]: ROLES.DD_LEAD,
|
[RESIGNATION_STAGES.DD_LEAD]: ROLES.DD_LEAD,
|
||||||
[RESIGNATION_STAGES.NBH]: ROLES.NBH,
|
[RESIGNATION_STAGES.NBH]: ROLES.NBH,
|
||||||
@ -160,6 +160,9 @@ export class ResignationWorkflowService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const requiredRole = stageToRole[resignation.currentStage];
|
const requiredRole = stageToRole[resignation.currentStage];
|
||||||
|
if (Array.isArray(requiredRole)) {
|
||||||
|
return requiredRole.includes(user.roleCode);
|
||||||
|
}
|
||||||
return user.roleCode === requiredRole;
|
return user.roleCode === requiredRole;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const EMAILS = {
|
|||||||
DEALER: args.dealerEmail,
|
DEALER: args.dealerEmail,
|
||||||
ASM: 'abhishek@royalenfield.com',
|
ASM: 'abhishek@royalenfield.com',
|
||||||
RBM: 'manish@royalenfield.com',
|
RBM: 'manish@royalenfield.com',
|
||||||
|
DD_ZM: 'piyush@royalenfield.com',
|
||||||
ZBH: 'manav@royalenfield.com',
|
ZBH: 'manav@royalenfield.com',
|
||||||
DD_LEAD: 'jaya@royalenfield.com',
|
DD_LEAD: 'jaya@royalenfield.com',
|
||||||
NBH: 'yashwin@royalenfield.com',
|
NBH: 'yashwin@royalenfield.com',
|
||||||
@ -158,13 +159,14 @@ async function run() {
|
|||||||
await delay();
|
await delay();
|
||||||
|
|
||||||
const approvals = [
|
const approvals = [
|
||||||
{ name: 'ASM', email: EMAILS.ASM, remarks: 'Verified physical assets and dealer intent. Recommended for resignation.' },
|
{ stage: 'ASM', 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.' },
|
{ stage: 'RBM', 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.' },
|
{ stage: 'RBM', name: 'DD-ZM', email: EMAILS.DD_ZM, remarks: 'Joint approval evaluated and verified by DD-ZM.' },
|
||||||
{ name: 'DD Lead', email: EMAILS.DD_LEAD, remarks: 'Dealer development criteria satisfied for exit.' },
|
{ stage: 'ZBH', name: 'ZBH', email: EMAILS.ZBH, remarks: 'Zone-level resource reallocation planned. Approved.' },
|
||||||
{ name: 'NBH', email: EMAILS.NBH, remarks: 'HQ clearance granted. Proceed with F&F initiation.' },
|
{ stage: 'DD Lead', name: 'DD Lead', email: EMAILS.DD_LEAD, remarks: 'Dealer development criteria satisfied for exit.' },
|
||||||
{ name: 'DD Admin', email: EMAILS.DD_ADMIN, remarks: 'Administrative checks complete. Initiating termination of commercial contract.' },
|
{ stage: 'NBH', name: 'NBH', email: EMAILS.NBH, remarks: 'HQ clearance granted. Proceed with F&F initiation.' },
|
||||||
{ name: 'Legal Admin', email: EMAILS.LEGAL, remarks: 'Legal documentation verified. No active litigation found.' }
|
{ 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
|
// Fetch resignation data to determine current stage for skipping
|
||||||
@ -174,13 +176,17 @@ async function run() {
|
|||||||
console.log(`Current Stage: ${currentStage}`);
|
console.log(`Current Stage: ${currentStage}`);
|
||||||
|
|
||||||
const stageOrder = [
|
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;
|
let currentStep = 2;
|
||||||
for (let i = startIndex; i < approvals.length; i++) {
|
for (let i = startApproveIndex; i < approvals.length; i++) {
|
||||||
const actor = approvals[i];
|
const actor = approvals[i];
|
||||||
log(currentStep, `${actor.name} (${actor.email}) approving...`);
|
log(currentStep, `${actor.name} (${actor.email}) approving...`);
|
||||||
const token = await login(actor.email);
|
const token = await login(actor.email);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user