Dealer_Onboarding_Backend/src/services/RelocationWorkflowService.ts

107 lines
4.1 KiB
TypeScript

import db from '../database/models/index.js';
const { RelocationRequest, AuditLog, User } = db;
import { AUDIT_ACTIONS, RELOCATION_STAGES, ROLES } from '../common/config/constants.js';
export class RelocationWorkflowService {
/**
* Standardized method to transition a relocation request status
*/
static async transitionRelocation(request: any, targetStatus: string, userId: string | null = null, metadata: any = {}) {
const previousStatus = request.status;
const { reason, stage, progressPercentage, action, auditAction } = metadata;
const updateData: any = {
status: targetStatus,
updatedAt: new Date()
};
// Update stage if provided and valid
if (stage && Object.values(RELOCATION_STAGES).includes(stage)) {
updateData.currentStage = stage;
}
// Update progress percentage if explicitly provided
if (progressPercentage !== undefined) {
updateData.progressPercentage = progressPercentage;
}
const sourceStage = request.currentStage;
// 1. Update Request Record
await request.update(updateData);
// 2. Update Timeline (JSON array)
const user = userId ? await User.findByPk(userId) : null;
const timelineEntry = {
stage: sourceStage, // Store the stage where the action happened
targetStage: stage || targetStatus,
timestamp: new Date(),
user: user ? user.fullName : 'System',
action: action || `Transitioned to ${targetStatus}`,
remarks: reason || ''
};
const updatedTimeline = [...(request.timeline || []), timelineEntry];
await request.update({ timeline: updatedTimeline });
// 3. Create Audit Log
let resolvedAuditAction: string = AUDIT_ACTIONS.APPROVED;
if (auditAction) {
resolvedAuditAction = auditAction;
} else if (action === 'REJECT') {
resolvedAuditAction = AUDIT_ACTIONS.REJECTED;
} else if (action === 'REVOKE') {
resolvedAuditAction = AUDIT_ACTIONS.RELOCATION_REVOKED;
} else if (action === 'SEND_BACK') {
resolvedAuditAction = AUDIT_ACTIONS.RELOCATION_SENT_BACK;
}
await db.RelocationAudit.create({
userId: userId,
relocationRequestId: request.id,
action: resolvedAuditAction,
remarks: reason || '',
details: { status: targetStatus, stage: sourceStage, targetStage: stage || targetStatus }
});
console.log(`[RelocationWorkflowService] Transitioned Request ${request.requestId} to ${targetStatus}`);
return request;
}
/**
* Checks if a user is authorized to perform an action in the current stage
*/
static async canUserAction(request: any, user: any) {
if (!user) return false;
// Super Admin bypass
if (user.roleCode === ROLES.SUPER_ADMIN) return true;
const stageMapping: Record<string, string> = {
[RELOCATION_STAGES.ASM_REVIEW]: ROLES.ASM,
'DD Admin Review': ROLES.ASM, // Legacy/alias mapping for older requests
[RELOCATION_STAGES.RBM_REVIEW]: ROLES.RBM,
[RELOCATION_STAGES.DD_ZM_REVIEW]: ROLES.DD_ZM,
[RELOCATION_STAGES.ZBH_REVIEW]: ROLES.ZBH,
[RELOCATION_STAGES.DD_LEAD_REVIEW]: ROLES.DD_LEAD,
[RELOCATION_STAGES.DD_HEAD_APPROVAL]: ROLES.DD_HEAD,
[RELOCATION_STAGES.NBH_APPROVAL]: ROLES.NBH,
[RELOCATION_STAGES.LEGAL_CLEARANCE]: ROLES.LEGAL_ADMIN,
[RELOCATION_STAGES.NBH_CLEARANCE_EOR]: ROLES.NBH
};
const requiredRole = stageMapping[request.currentStage];
if (!requiredRole) return false;
// Role-based check
if (user.roleCode !== requiredRole) return false;
// Optional: Hierarchy check
// We could verify if the user is the SPECIFIC person assigned in participants
// but for now, any user with the correct role can act (consistent with simple RBAC)
return true;
}
}