dealer onboarding dashboard flow changes made
This commit is contained in:
parent
37ecf3ba85
commit
65d2af7447
8
check_policies.ts
Normal file
8
check_policies.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import db from './src/database/models/index.js';
|
||||||
|
|
||||||
|
async function checkPolicies() {
|
||||||
|
const policies = await db.StageApprovalPolicy.findAll();
|
||||||
|
console.log(JSON.stringify(policies, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPolicies().catch(console.error);
|
||||||
18
repair_all_progress.ts
Normal file
18
repair_all_progress.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import db from './src/database/models/index.js';
|
||||||
|
import { syncApplicationProgress } from './src/common/utils/progress.js';
|
||||||
|
|
||||||
|
async function repairAllProgress() {
|
||||||
|
console.log('--- Repairing Progress Tracker State for ALL Applications ---');
|
||||||
|
|
||||||
|
const apps = await db.Application.findAll();
|
||||||
|
console.log(`Found ${apps.length} applications to sync.`);
|
||||||
|
|
||||||
|
for (const app of apps) {
|
||||||
|
console.log(`Syncing Progress for Application: ${app.applicationId} (Status: ${app.overallStatus})`);
|
||||||
|
await syncApplicationProgress(app.id, app.overallStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('--- REPAIR COMPLETE ---');
|
||||||
|
}
|
||||||
|
|
||||||
|
repairAllProgress().catch(console.error).then(() => process.exit(0));
|
||||||
27
repair_fdd_policy.ts
Normal file
27
repair_fdd_policy.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import db from './src/database/models/index.js';
|
||||||
|
|
||||||
|
async function repairPolicy() {
|
||||||
|
console.log('--- Repairing FDD_VERIFICATION Policy ---');
|
||||||
|
|
||||||
|
// Update FDD_VERIFICATION to allow Finance and DD Admin
|
||||||
|
const [policy] = await db.StageApprovalPolicy.findOrCreate({
|
||||||
|
where: { stageCode: 'FDD_VERIFICATION' },
|
||||||
|
defaults: {
|
||||||
|
stageCode: 'FDD_VERIFICATION',
|
||||||
|
minApprovals: 1,
|
||||||
|
approvalMode: 'ROLE_MANDATORY',
|
||||||
|
requiredRoles: ['FDD', 'Finance', 'DD Admin', 'Finance Admin'],
|
||||||
|
isActive: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (policy) {
|
||||||
|
await policy.update({
|
||||||
|
requiredRoles: ['FDD', 'Finance', 'DD Admin', 'Finance Admin', 'DD Head'],
|
||||||
|
minApprovals: 1
|
||||||
|
});
|
||||||
|
console.log('Policy updated successfully:', policy.requiredRoles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repairPolicy().catch(console.error).then(() => process.exit(0));
|
||||||
@ -27,9 +27,9 @@ const policies = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
stageCode: 'LOI_APPROVAL',
|
stageCode: 'LOI_APPROVAL',
|
||||||
minApprovals: 3,
|
minApprovals: 2,
|
||||||
approvalMode: 'ROLE_MANDATORY',
|
approvalMode: 'ROLE_MANDATORY',
|
||||||
requiredRoles: ['Finance', 'DD Head', 'NBH'],
|
requiredRoles: ['DD Head', 'NBH'],
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -43,7 +43,7 @@ const policies = [
|
|||||||
stageCode: 'FDD_VERIFICATION',
|
stageCode: 'FDD_VERIFICATION',
|
||||||
minApprovals: 1,
|
minApprovals: 1,
|
||||||
approvalMode: 'ROLE_MANDATORY',
|
approvalMode: 'ROLE_MANDATORY',
|
||||||
requiredRoles: ['FDD'],
|
requiredRoles: ['DD Admin', 'Super Admin'],
|
||||||
isActive: true
|
isActive: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@ -41,8 +41,8 @@ const configs = [
|
|||||||
{ documentType: 'Statutory Approval Certificate', stageCode: 'FDD', allowedRoles: [ROLES.FDD, ROLES.FINANCE, ROLES.SUPER_ADMIN, ROLES.DD_ADMIN] },
|
{ documentType: 'Statutory Approval Certificate', stageCode: 'FDD', allowedRoles: [ROLES.FDD, ROLES.FINANCE, ROLES.SUPER_ADMIN, ROLES.DD_ADMIN] },
|
||||||
|
|
||||||
// LOI / Security (Approval Process)
|
// LOI / Security (Approval Process)
|
||||||
{ documentType: 'Initial Security Deposit Receipt', stageCode: 'LOI Approval', allowedRoles: [ROLES.DEALER, ROLES.FINANCE, ROLES.DD_HEAD, ROLES.NBH, ROLES.SUPER_ADMIN], isMandatory: true },
|
{ documentType: 'Security Deposit Receipt', stageCode: 'LOI Approval', allowedRoles: [ROLES.DEALER, ROLES.FINANCE, ROLES.DD_HEAD, ROLES.NBH, ROLES.SUPER_ADMIN], isMandatory: true },
|
||||||
{ documentType: 'Final Security Deposit Receipt', stageCode: 'LOA Approval', allowedRoles: [ROLES.DEALER, ROLES.FINANCE, ROLES.DD_HEAD, ROLES.NBH, ROLES.SUPER_ADMIN], isMandatory: true },
|
{ documentType: 'First Fill Receipt', stageCode: 'LOA Approval', allowedRoles: [ROLES.DEALER, ROLES.FINANCE, ROLES.DD_HEAD, ROLES.NBH, ROLES.SUPER_ADMIN], isMandatory: true },
|
||||||
{ documentType: 'LOI Acknowledgement Copy', stageCode: 'LOI Issue', allowedRoles: ALL_ROLES },
|
{ documentType: 'LOI Acknowledgement Copy', stageCode: 'LOI Issue', allowedRoles: ALL_ROLES },
|
||||||
{ documentType: 'Nodal Agreement', stageCode: 'LOI Approval', allowedRoles: [ROLES.LEGAL_ADMIN, ROLES.DEALER, ROLES.SUPER_ADMIN] },
|
{ documentType: 'Nodal Agreement', stageCode: 'LOI Approval', allowedRoles: [ROLES.LEGAL_ADMIN, ROLES.DEALER, ROLES.SUPER_ADMIN] },
|
||||||
|
|
||||||
|
|||||||
@ -12,16 +12,16 @@ const seedSystemConfigs = async () => {
|
|||||||
|
|
||||||
const configs = [
|
const configs = [
|
||||||
{
|
{
|
||||||
key: 'INITIAL_SECURITY_DEPOSIT',
|
key: 'SECURITY_DEPOSIT',
|
||||||
value: { amount: 500000, currency: 'INR' },
|
value: { amount: 500000, currency: 'INR' },
|
||||||
category: 'SECURITY_DEPOSIT',
|
category: 'SECURITY_DEPOSIT',
|
||||||
description: 'Default Initial Security Deposit amount for new dealer onboarding'
|
description: 'Default Security Deposit amount for new dealer onboarding'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'FINAL_SECURITY_DEPOSIT',
|
key: 'FIRST_FILL',
|
||||||
value: { amount: 1500000, currency: 'INR' },
|
value: { amount: 1500000, currency: 'INR' },
|
||||||
category: 'SECURITY_DEPOSIT',
|
category: 'SECURITY_DEPOSIT',
|
||||||
description: 'Default Final Security Deposit amount for new dealer onboarding'
|
description: 'Default First Fill amount for new dealer onboarding'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -380,8 +380,8 @@ export const DOCUMENT_TYPES = {
|
|||||||
STATUTORY_AUDIT: 'Statutory Approval Certificate',
|
STATUTORY_AUDIT: 'Statutory Approval Certificate',
|
||||||
BANK_GUARANTEE: 'Bank Guarantee Document',
|
BANK_GUARANTEE: 'Bank Guarantee Document',
|
||||||
SECURITY_DEPOSIT_RECEIPT: 'Security Deposit Receipt',
|
SECURITY_DEPOSIT_RECEIPT: 'Security Deposit Receipt',
|
||||||
SECURITY_DEPOSIT_INITIAL: 'Initial Security Deposit Receipt',
|
SECURITY_DEPOSIT: 'Security Deposit Receipt',
|
||||||
SECURITY_DEPOSIT_FINAL: 'Final Security Deposit Receipt',
|
FIRST_FILL: 'First Fill Receipt',
|
||||||
RELOCATION_PROPERTY_DOCS: 'Property documents for new location',
|
RELOCATION_PROPERTY_DOCS: 'Property documents for new location',
|
||||||
RELOCATION_LEASE_AGREEMENT: 'Lease/Rental agreement for new location',
|
RELOCATION_LEASE_AGREEMENT: 'Lease/Rental agreement for new location',
|
||||||
RELOCATION_NOC_LANDLORD: 'NOC from current landlord',
|
RELOCATION_NOC_LANDLORD: 'NOC from current landlord',
|
||||||
|
|||||||
@ -50,8 +50,22 @@ export const updateApplicationProgress = async (applicationId: string, stageName
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Whenever a stage is marked 'active' or 'completed',
|
// Whenever a stage is marked 'active' or 'completed',
|
||||||
// all previous stages MUST be completed.
|
// all previous stages MUST exist and be marked 'completed'.
|
||||||
if (status === 'active' || status === 'completed') {
|
if (status === 'active' || status === 'completed') {
|
||||||
|
const previousStages = ONBOARDING_STAGES.filter(s => s.order < stage.order);
|
||||||
|
for (const prev of previousStages) {
|
||||||
|
await ApplicationProgress.findOrCreate({
|
||||||
|
where: { applicationId, stageName: prev.name },
|
||||||
|
defaults: {
|
||||||
|
stageOrder: prev.order,
|
||||||
|
status: 'completed',
|
||||||
|
completionPercentage: 100,
|
||||||
|
stageCompletedAt: new Date()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also update any existing ones that weren't completed
|
||||||
await ApplicationProgress.update(
|
await ApplicationProgress.update(
|
||||||
{ status: 'completed', completionPercentage: 100, stageCompletedAt: new Date() },
|
{ status: 'completed', completionPercentage: 100, stageCompletedAt: new Date() },
|
||||||
{
|
{
|
||||||
@ -114,8 +128,9 @@ export const syncApplicationProgress = async (applicationId: string, overallStat
|
|||||||
// Determine status for this stage
|
// Determine status for this stage
|
||||||
const isCompleted = [
|
const isCompleted = [
|
||||||
'Submitted', 'Questionnaire Completed', 'Shortlisted', 'Level 1 Approved',
|
'Submitted', 'Questionnaire Completed', 'Shortlisted', 'Level 1 Approved',
|
||||||
'Level 2 Approved', 'Level 3 Approved', 'LOI Issued', 'EOR Complete',
|
'Level 2 Approved', 'Level 3 Approved', 'FDD Verification', 'LOI Issued',
|
||||||
'Approved', 'Onboarded'
|
'Dealer Code Generation', 'Architecture Team Completion', 'LOA Issued',
|
||||||
|
'EOR Complete', 'Approved', 'Onboarded'
|
||||||
].includes(overallStatus);
|
].includes(overallStatus);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -58,6 +58,16 @@ const processStageDecision = async (params: {
|
|||||||
}) => {
|
}) => {
|
||||||
const { applicationId, stageCode, decision, remarks, userId, roleCode, interviewId, nextStatus, nextStage, nextProgress } = params;
|
const { applicationId, stageCode, decision, remarks, userId, roleCode, interviewId, nextStatus, nextStage, nextProgress } = params;
|
||||||
|
|
||||||
|
const targetId = applicationId as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
|
||||||
|
const application = await db.Application.findOne({
|
||||||
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!application) return { notFound: true };
|
||||||
|
const resolvedId = application.id;
|
||||||
|
|
||||||
const policy = await db.StageApprovalPolicy.findOne({ where: { stageCode } });
|
const policy = await db.StageApprovalPolicy.findOne({ where: { stageCode } });
|
||||||
if (!policy) return { noPolicy: true };
|
if (!policy) return { noPolicy: true };
|
||||||
|
|
||||||
@ -65,7 +75,7 @@ const processStageDecision = async (params: {
|
|||||||
|
|
||||||
// Check if user is an assigned participant
|
// Check if user is an assigned participant
|
||||||
const userAssignments = await db.RequestParticipant.findAll({
|
const userAssignments = await db.RequestParticipant.findAll({
|
||||||
where: { requestId: applicationId, requestType: 'application', userId }
|
where: { requestId: resolvedId, requestType: 'application', userId }
|
||||||
});
|
});
|
||||||
|
|
||||||
const isAssigned = userAssignments.some((p: any) => {
|
const isAssigned = userAssignments.some((p: any) => {
|
||||||
@ -85,7 +95,7 @@ const processStageDecision = async (params: {
|
|||||||
// --- Sequential Enforcement (SRS 6.16.2 & 6.18.3.1 Compliance) ---
|
// --- Sequential Enforcement (SRS 6.16.2 & 6.18.3.1 Compliance) ---
|
||||||
if (roleCode !== 'Super Admin' && roleCode !== 'DD Admin') {
|
if (roleCode !== 'Super Admin' && roleCode !== 'DD Admin') {
|
||||||
const approvedActions = await db.StageApprovalAction.findAll({
|
const approvedActions = await db.StageApprovalAction.findAll({
|
||||||
where: { applicationId, stageCode, decision: 'Approved' }
|
where: { applicationId: resolvedId, stageCode, decision: 'Approved' }
|
||||||
});
|
});
|
||||||
const approvedRoles = new Set(approvedActions.map((a: any) => a.actorRole));
|
const approvedRoles = new Set(approvedActions.map((a: any) => a.actorRole));
|
||||||
|
|
||||||
@ -120,13 +130,13 @@ const processStageDecision = async (params: {
|
|||||||
// Record Action - Robust handle for null interviewId which breaks unique constraint in Postgres
|
// Record Action - Robust handle for null interviewId which breaks unique constraint in Postgres
|
||||||
if (!interviewId) {
|
if (!interviewId) {
|
||||||
const existing = await db.StageApprovalAction.findOne({
|
const existing = await db.StageApprovalAction.findOne({
|
||||||
where: { applicationId, stageCode, actorUserId: userId, interviewId: null }
|
where: { applicationId: resolvedId, stageCode, actorUserId: userId, interviewId: null }
|
||||||
});
|
});
|
||||||
if (existing) {
|
if (existing) {
|
||||||
await existing.update({ decision, remarks: remarks || null, actorRole: assignedRole || roleCode });
|
await existing.update({ decision, remarks: remarks || null, actorRole: assignedRole || roleCode });
|
||||||
} else {
|
} else {
|
||||||
await db.StageApprovalAction.create({
|
await db.StageApprovalAction.create({
|
||||||
applicationId,
|
applicationId: resolvedId,
|
||||||
stageCode,
|
stageCode,
|
||||||
actorUserId: userId,
|
actorUserId: userId,
|
||||||
actorRole: assignedRole || roleCode,
|
actorRole: assignedRole || roleCode,
|
||||||
@ -136,7 +146,7 @@ const processStageDecision = async (params: {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await db.StageApprovalAction.upsert({
|
await db.StageApprovalAction.upsert({
|
||||||
applicationId,
|
applicationId: resolvedId,
|
||||||
interviewId: interviewId,
|
interviewId: interviewId,
|
||||||
stageCode,
|
stageCode,
|
||||||
actorUserId: userId,
|
actorUserId: userId,
|
||||||
@ -153,14 +163,64 @@ const processStageDecision = async (params: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- FDD Integration: Link approval to FddReport table for dashboard mapping ---
|
||||||
|
if (stageCode === 'FDD_VERIFICATION' && decision === 'Approved') {
|
||||||
|
const assignment = await db.FddAssignment.findOne({ where: { applicationId: resolvedId } });
|
||||||
|
if (assignment) {
|
||||||
|
// Find latest audit report document
|
||||||
|
const lastReportDoc = await db.OnboardingDocument.findOne({
|
||||||
|
where: { applicationId: resolvedId, documentType: 'FDD Final Audit Report' },
|
||||||
|
order: [['createdAt', 'DESC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Parse structured recommendation/findings from remarks
|
||||||
|
let recommendation = 'Recommended';
|
||||||
|
let findings = remarks || 'Submission reviewed.';
|
||||||
|
if (remarks?.includes('[RECOMMENDATION:')) {
|
||||||
|
const parts = remarks.split('[RECOMMENDATION: ');
|
||||||
|
if (parts[1]) {
|
||||||
|
recommendation = parts[1].split(']')[0];
|
||||||
|
findings = remarks.split('\nFindings: ')[1] || remarks.split(']')[1]?.trim() || remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.FddReport.create({
|
||||||
|
assignmentId: assignment.id,
|
||||||
|
reportDocumentId: lastReportDoc?.id || null,
|
||||||
|
findings,
|
||||||
|
recommendation,
|
||||||
|
verifiedAt: new Date(),
|
||||||
|
verifiedBy: userId
|
||||||
|
});
|
||||||
|
|
||||||
|
await assignment.update({ status: 'Report Submitted' });
|
||||||
|
|
||||||
|
// Bridge: Initialize LOI Records for the next stage (Moved from fdd.controller.ts for Admin Review flow)
|
||||||
|
console.log(`[DEBUG] FDD Approved by Admin. Initializing LOI Records for Application: ${resolvedId}`);
|
||||||
|
const [loiReq] = await db.LoiRequest.findOrCreate({
|
||||||
|
where: { applicationId: resolvedId },
|
||||||
|
defaults: { status: 'Pending Approval', requestedBy: userId }
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextRoles = ['DD Head', 'NBH'];
|
||||||
|
await Promise.all(nextRoles.map(async (role) => {
|
||||||
|
await db.LoiApproval.findOrCreate({
|
||||||
|
where: { requestId: loiReq.id, approverRole: role },
|
||||||
|
defaults: { action: 'Pending', level: 1 }
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
console.log(`[DEBUG] LOI Records initialized for ${nextRoles.join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Evaluate Policy via Centralized Service (FIXED unique user count)
|
// Evaluate Policy via Centralized Service (FIXED unique user count)
|
||||||
const evaluation = await WorkflowService.evaluateStagePolicy(applicationId, stageCode);
|
const evaluation = await WorkflowService.evaluateStagePolicy(resolvedId, stageCode);
|
||||||
|
|
||||||
const hasRejection = decision === 'Rejected'; // Immediate rejection if ANY required actor rejects (business rule)
|
const hasRejection = decision === 'Rejected'; // Immediate rejection if ANY required actor rejects (business rule)
|
||||||
let statusUpdated = false;
|
let statusUpdated = false;
|
||||||
|
|
||||||
if (hasRejection) {
|
if (hasRejection) {
|
||||||
const application = await db.Application.findByPk(applicationId);
|
const application = await db.Application.findByPk(resolvedId);
|
||||||
if (application) {
|
if (application) {
|
||||||
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.REJECTED, userId, {
|
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.REJECTED, userId, {
|
||||||
reason: `Rejected during ${stageCode} stage: ${remarks}`,
|
reason: `Rejected during ${stageCode} stage: ${remarks}`,
|
||||||
@ -169,7 +229,7 @@ const processStageDecision = async (params: {
|
|||||||
statusUpdated = true;
|
statusUpdated = true;
|
||||||
}
|
}
|
||||||
} else if (evaluation.policyMet) {
|
} else if (evaluation.policyMet) {
|
||||||
const application = await db.Application.findByPk(applicationId);
|
const application = await db.Application.findByPk(resolvedId);
|
||||||
if (application) {
|
if (application) {
|
||||||
let targetStatus = nextStatus;
|
let targetStatus = nextStatus;
|
||||||
let targetStage = nextStage;
|
let targetStage = nextStage;
|
||||||
@ -181,7 +241,7 @@ const processStageDecision = async (params: {
|
|||||||
targetStage = APPLICATION_STAGES.LOI;
|
targetStage = APPLICATION_STAGES.LOI;
|
||||||
targetProgress = 75;
|
targetProgress = 75;
|
||||||
} else if (stageCode === 'LOA_APPROVAL') {
|
} else if (stageCode === 'LOA_APPROVAL') {
|
||||||
targetStatus = APPLICATION_STATUS.LOA_ISSUED;
|
targetStatus = APPLICATION_STATUS.EOR_IN_PROGRESS;
|
||||||
targetStage = APPLICATION_STAGES.LOA;
|
targetStage = APPLICATION_STAGES.LOA;
|
||||||
targetProgress = 95;
|
targetProgress = 95;
|
||||||
}
|
}
|
||||||
@ -225,9 +285,9 @@ const processInterviewApprovalDecision = async (params: {
|
|||||||
// Ensure policy exists for interviews
|
// Ensure policy exists for interviews
|
||||||
await ensureInterviewPolicy(interview.level);
|
await ensureInterviewPolicy(interview.level);
|
||||||
|
|
||||||
const nextStatusMap: any = { 1: 'Level 1 Approved', 2: 'Level 2 Approved', 3: 'Level 3 Approved' };
|
const nextStatusMap: any = { 1: 'Level 1 Approved', 2: 'Level 2 Approved', 3: 'FDD Verification' };
|
||||||
const nextStageMap: any = { 1: APPLICATION_STAGES.LEVEL_1_APPROVED, 2: APPLICATION_STAGES.LEVEL_2_APPROVED, 3: APPLICATION_STAGES.FDD };
|
const nextStageMap: any = { 1: APPLICATION_STAGES.LEVEL_1_APPROVED, 2: APPLICATION_STAGES.LEVEL_2_APPROVED, 3: APPLICATION_STAGES.FDD };
|
||||||
const progressMap: any = { 1: 40, 2: 55, 3: 70 };
|
const progressMap: any = { 1: 40, 2: 55, 3: 65 };
|
||||||
|
|
||||||
const result = await processStageDecision({
|
const result = await processStageDecision({
|
||||||
applicationId: interview.applicationId,
|
applicationId: interview.applicationId,
|
||||||
@ -275,8 +335,9 @@ export const submitQuestionnaireResponse = async (req: AuthRequest, res: Respons
|
|||||||
const { applicationId, questionnaireId, responses } = req.body; // responses: [{ questionId, responseValue, attachmentUrl }]
|
const { applicationId, questionnaireId, responses } = req.body; // responses: [{ questionId, responseValue, attachmentUrl }]
|
||||||
|
|
||||||
// Find application UUID first (handles readable ID)
|
// Find application UUID first (handles readable ID)
|
||||||
const application = await db.Application.findOne({
|
const _isUUID_qr = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(applicationId as string);
|
||||||
where: { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] }
|
const application = await db.Application.findOne({
|
||||||
|
where: _isUUID_qr ? { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } : { applicationId: applicationId }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
@ -367,8 +428,9 @@ export const scheduleInterview = async (req: AuthRequest, res: Response) => {
|
|||||||
const levelNum = typeof level === 'string' ? parseInt(level.replace(/\D/g, ''), 10) : level;
|
const levelNum = typeof level === 'string' ? parseInt(level.replace(/\D/g, ''), 10) : level;
|
||||||
console.log(`Parsed Level: ${level} -> ${levelNum}`);
|
console.log(`Parsed Level: ${level} -> ${levelNum}`);
|
||||||
|
|
||||||
const application = await db.Application.findOne({
|
const _isUUID_si = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(applicationId as string);
|
||||||
where: { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] }
|
const application = await db.Application.findOne({
|
||||||
|
where: _isUUID_si ? { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } : { applicationId: applicationId }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
@ -1016,8 +1078,9 @@ export const submitStageDecision = async (req: AuthRequest, res: Response) => {
|
|||||||
if (result.noPolicy) {
|
if (result.noPolicy) {
|
||||||
// Fallback: If no policy, just update application status directly (legacy behavior)
|
// Fallback: If no policy, just update application status directly (legacy behavior)
|
||||||
if (nextStatus) {
|
if (nextStatus) {
|
||||||
const application = await db.Application.findOne({
|
const _isUUID_fb = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(applicationId as string);
|
||||||
where: { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] }
|
const application = await db.Application.findOne({
|
||||||
|
where: _isUUID_fb ? { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } : { applicationId: applicationId }
|
||||||
});
|
});
|
||||||
if (application) {
|
if (application) {
|
||||||
await WorkflowService.transitionApplication(application, nextStatus, req.user?.id || null, {
|
await WorkflowService.transitionApplication(application, nextStatus, req.user?.id || null, {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
|
import { Op } from 'sequelize';
|
||||||
import db from '../../database/models/index.js';
|
import db from '../../database/models/index.js';
|
||||||
const { EorChecklist, EorChecklistItem, OnboardingDocument, RelocationDocument } = db;
|
const { EorChecklist, EorChecklistItem, OnboardingDocument, RelocationDocument } = db;
|
||||||
import { AuthRequest } from '../../types/express.types.js';
|
import { AuthRequest } from '../../types/express.types.js';
|
||||||
@ -6,9 +7,24 @@ import { AuthRequest } from '../../types/express.types.js';
|
|||||||
export const getChecklist = async (req: Request, res: Response) => {
|
export const getChecklist = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { applicationId, relocationId } = req.params;
|
const { applicationId, relocationId } = req.params;
|
||||||
|
|
||||||
|
// Resolve human-readable applicationId (e.g. APP-2026-79CE90) to UUID
|
||||||
|
let resolvedAppId = applicationId as string;
|
||||||
|
if (applicationId) {
|
||||||
|
const appIdStr = applicationId as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(appIdStr);
|
||||||
|
if (!isUUID) {
|
||||||
|
const app = await db.Application.findOne({ where: { applicationId: appIdStr } });
|
||||||
|
if (!app) {
|
||||||
|
res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolvedAppId = app.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let checklist = await EorChecklist.findOne({
|
let checklist = await EorChecklist.findOne({
|
||||||
where: relocationId ? { relocationId } : { applicationId },
|
where: relocationId ? { relocationId } : { applicationId: resolvedAppId },
|
||||||
// proofDocument is now polymorphic, would need manual stitch or sub-selects
|
|
||||||
include: [{ model: EorChecklistItem, as: 'items' }]
|
include: [{ model: EorChecklistItem, as: 'items' }]
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -47,29 +63,36 @@ export const getChecklist = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
export const createChecklist = async (req: AuthRequest, res: Response) => {
|
export const createChecklist = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { applicationId, relocationId } = req.body;
|
const { applicationId: rawAppId, relocationId } = req.body;
|
||||||
|
|
||||||
if (!applicationId && !relocationId) {
|
if (!rawAppId && !relocationId) {
|
||||||
return res.status(400).json({ success: false, message: 'applicationId or relocationId is required' });
|
return res.status(400).json({ success: false, message: 'applicationId or relocationId is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applicationId) {
|
// Resolve applicationId to UUID (handles readable IDs like APP-2026-79CE90)
|
||||||
const application = await db.Application.findByPk(applicationId);
|
let resolvedAppId: string | null = null;
|
||||||
|
if (rawAppId) {
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(rawAppId);
|
||||||
|
const application = isUUID
|
||||||
|
? await db.Application.findByPk(rawAppId)
|
||||||
|
: await db.Application.findOne({ where: { applicationId: rawAppId } });
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
resolvedAppId = application.id;
|
||||||
} else if (relocationId) {
|
} else if (relocationId) {
|
||||||
const relocation = await db.RelocationRequest.findByPk(relocationId);
|
const relocation = await db.RelocationRequest.findByPk(relocationId);
|
||||||
if (!relocation) return res.status(404).json({ success: false, message: 'Relocation request not found' });
|
if (!relocation) return res.status(404).json({ success: false, message: 'Relocation request not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const [checklist, created] = await EorChecklist.findOrCreate({
|
const [checklist, created] = await EorChecklist.findOrCreate({
|
||||||
where: relocationId ? { relocationId } : { applicationId },
|
where: relocationId ? { relocationId } : { applicationId: resolvedAppId },
|
||||||
defaults: {
|
defaults: {
|
||||||
status: 'In Progress',
|
status: 'In Progress',
|
||||||
applicationId: applicationId || null,
|
applicationId: resolvedAppId || null,
|
||||||
relocationId: relocationId || null
|
relocationId: relocationId || null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (created) {
|
if (created) {
|
||||||
// Define Default Mandatory Items per SRS/Frontend
|
// Define Default Mandatory Items per SRS/Frontend
|
||||||
let defaultItems = [];
|
let defaultItems = [];
|
||||||
@ -111,6 +134,30 @@ export const createChecklist = async (req: AuthRequest, res: Response) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
await EorChecklistItem.bulkCreate(itemsData);
|
await EorChecklistItem.bulkCreate(itemsData);
|
||||||
|
|
||||||
|
// AUTO-MAP existing documents from OnboardingDocument table
|
||||||
|
if (resolvedAppId) {
|
||||||
|
const existingDocs = await OnboardingDocument.findAll({
|
||||||
|
where: { applicationId: resolvedAppId, status: 'active' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingDocs.length > 0) {
|
||||||
|
const typeMap: any = {
|
||||||
|
'GST Certificate': 'GST certificate including Accessories & Apparels billing',
|
||||||
|
'Virtual Code Confirmation': 'Virtual code availability',
|
||||||
|
'Trade Certificate': 'Trade certificate with test ride bikes registration',
|
||||||
|
'DMS Infra Details': 'DMS infra'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const doc of existingDocs) {
|
||||||
|
const targetDescription = typeMap[doc.documentType] || doc.documentType;
|
||||||
|
await EorChecklistItem.update(
|
||||||
|
{ proofDocumentId: doc.id },
|
||||||
|
{ where: { checklistId: checklist.id, description: { [Op.iLike]: targetDescription.trim() } } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status transition will be handled by the global handleApprove workflow or explicit trigger
|
// Status transition will be handled by the global handleApprove workflow or explicit trigger
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
|
import { Op } from 'sequelize';
|
||||||
import db from '../../database/models/index.js';
|
import db from '../../database/models/index.js';
|
||||||
const { FddAssignment, FddReport, AuditLog, Application } = db;
|
const { FddAssignment, FddReport, AuditLog, Application } = db;
|
||||||
import { AuthRequest } from '../../types/express.types.js';
|
import { AuthRequest } from '../../types/express.types.js';
|
||||||
@ -8,8 +9,20 @@ import { WorkflowService } from '../../services/WorkflowService.js';
|
|||||||
export const getAssignment = async (req: Request, res: Response) => {
|
export const getAssignment = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { applicationId } = req.params;
|
const { applicationId } = req.params;
|
||||||
|
const targetId = applicationId as string;
|
||||||
|
|
||||||
|
// Resolve application first to get UUID
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
const application = await Application.findOne({
|
||||||
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!application) {
|
||||||
|
return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
}
|
||||||
|
|
||||||
const assignment = await FddAssignment.findOne({
|
const assignment = await FddAssignment.findOne({
|
||||||
where: { applicationId },
|
where: { applicationId: application.id },
|
||||||
include: [{ model: FddReport, as: 'reports' }]
|
include: [{ model: FddReport, as: 'reports' }]
|
||||||
});
|
});
|
||||||
res.json({ success: true, data: assignment });
|
res.json({ success: true, data: assignment });
|
||||||
@ -22,22 +35,30 @@ export const getAssignment = async (req: Request, res: Response) => {
|
|||||||
export const assignAgency = async (req: AuthRequest, res: Response) => {
|
export const assignAgency = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { applicationId, assignedToAgency } = req.body;
|
const { applicationId, assignedToAgency } = req.body;
|
||||||
|
const targetId = applicationId as string;
|
||||||
|
|
||||||
|
// Resolve application first to get UUID
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
const application = await Application.findOne({
|
||||||
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!application) {
|
||||||
|
return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
}
|
||||||
|
|
||||||
const assignment = await FddAssignment.create({
|
const assignment = await FddAssignment.create({
|
||||||
applicationId,
|
applicationId: application.id,
|
||||||
assignedToAgency, // Agency User ID
|
assignedToAgency, // Agency User ID
|
||||||
status: 'Assigned'
|
status: 'Assigned'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bridge: Transition application to active FDD stage
|
// Bridge: Transition application to active FDD stage
|
||||||
const application = await Application.findByPk(applicationId);
|
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.FDD_VERIFICATION, req.user?.id || null, {
|
||||||
if (application) {
|
reason: 'FDD Agency assigned. Initiating financial due diligence.',
|
||||||
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.FDD_VERIFICATION, req.user?.id || null, {
|
stage: APPLICATION_STAGES.FDD,
|
||||||
reason: 'FDD Agency assigned. Initiating financial due diligence.',
|
progressPercentage: 70
|
||||||
stage: APPLICATION_STAGES.FDD,
|
});
|
||||||
progressPercentage: 70
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await AuditLog.create({
|
await AuditLog.create({
|
||||||
userId: req.user?.id,
|
userId: req.user?.id,
|
||||||
@ -72,70 +93,7 @@ export const uploadReport = async (req: AuthRequest, res: Response) => {
|
|||||||
{ where: { id: assignmentId } }
|
{ where: { id: assignmentId } }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Transition Application status (AUTOMATION)
|
res.status(201).json({ success: true, message: 'FDD Report uploaded successfully. Pending Admin review.', data: report });
|
||||||
const assignmentRecord = await FddAssignment.findByPk(assignmentId);
|
|
||||||
if (assignmentRecord) {
|
|
||||||
const application = await Application.findByPk(assignmentRecord.applicationId);
|
|
||||||
if (application) {
|
|
||||||
// Ensure LOI Request exists for the next stage
|
|
||||||
const [loiRequest] = await db.LoiRequest.findOrCreate({
|
|
||||||
where: { applicationId: application.id },
|
|
||||||
defaults: {
|
|
||||||
requestedBy: req.user?.id,
|
|
||||||
status: 'In Progress'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pre-initialize Finance approval for LOI stage
|
|
||||||
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOI_IN_PROGRESS, req.user?.id || null, {
|
|
||||||
reason: 'FDD Report submitted and verified. Moving to LOI Approval stage.',
|
|
||||||
stage: APPLICATION_STAGES.LOI,
|
|
||||||
progressPercentage: 65
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bridge 2.0: Automatically initialize LOI Records so the Initial Payment auto-approval finds them
|
|
||||||
console.log(`[DEBUG] Initializing LOI Records for Application: ${application.id}`);
|
|
||||||
const [loiReq] = await db.LoiRequest.findOrCreate({
|
|
||||||
where: { applicationId: application.id },
|
|
||||||
defaults: { status: 'Pending Approval' }
|
|
||||||
});
|
|
||||||
console.log(`[DEBUG] LOI Request ID: ${loiReq.id}, Overall Status: ${loiReq.status}`);
|
|
||||||
|
|
||||||
const roles = ['Finance', 'DD Head', 'NBH'];
|
|
||||||
await Promise.all(roles.map(async (role) => {
|
|
||||||
let action = 'Pending';
|
|
||||||
let comments = null;
|
|
||||||
|
|
||||||
if (role === 'Finance') {
|
|
||||||
console.log(`[DEBUG] Checking for existing verified INITIAL deposit for ${application.id}`);
|
|
||||||
const verifiedDeposit = await db.SecurityDeposit.findOne({
|
|
||||||
where: { applicationId: application.id, depositType: 'INITIAL', status: 'Verified' }
|
|
||||||
});
|
|
||||||
if (verifiedDeposit) {
|
|
||||||
console.log(`[DEBUG] FOUND VERIFIED DEPOSIT! Auto-approving Finance role in LOI.`);
|
|
||||||
action = 'Approved';
|
|
||||||
comments = 'Auto-approved: Initial Security Deposit already verified.';
|
|
||||||
} else {
|
|
||||||
console.log(`[DEBUG] NO Verified INITIAL deposit found during FDD upload.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [approval, created] = await db.LoiApproval.findOrCreate({
|
|
||||||
where: { requestId: loiReq.id, approverRole: role },
|
|
||||||
defaults: { action, comments, level: 1 }
|
|
||||||
});
|
|
||||||
console.log(`[DEBUG] Role ${role}: Status=${approval.action} (Created: ${created})`);
|
|
||||||
return approval;
|
|
||||||
}));
|
|
||||||
|
|
||||||
// If Finance was auto-approved, trigger policy evaluation
|
|
||||||
console.log(`[DEBUG] Finalizing FDD Upload -> Evaluating Stage Policy for LOI_APPROVAL`);
|
|
||||||
const evalResult = await WorkflowService.evaluateStagePolicy(application.id, 'LOI_APPROVAL');
|
|
||||||
console.log(`[DEBUG] Policy Met: ${evalResult.policyMet}, Approved Roles: ${Array.from(evalResult.approvedRoles || [])}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(201).json({ success: true, message: 'FDD Report uploaded', data: report });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Upload FDD report error:', error);
|
console.error('Upload FDD report error:', error);
|
||||||
res.status(500).json({ success: false, message: 'Error uploading report' });
|
res.status(500).json({ success: false, message: 'Error uploading report' });
|
||||||
|
|||||||
@ -26,8 +26,20 @@ const ensureLoaPolicy = async () => {
|
|||||||
export const getRequest = async (req: Request, res: Response) => {
|
export const getRequest = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { applicationId } = req.params;
|
const { applicationId } = req.params;
|
||||||
|
const targetId = applicationId as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
|
||||||
|
// Resolve application first to get UUID
|
||||||
|
const application = await db.Application.findOne({
|
||||||
|
where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!application) {
|
||||||
|
return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
}
|
||||||
|
|
||||||
const request = await LoaRequest.findOne({
|
const request = await LoaRequest.findOne({
|
||||||
where: { applicationId },
|
where: { applicationId: application.id },
|
||||||
include: [
|
include: [
|
||||||
{ model: LoaApproval, as: 'approvals' },
|
{ model: LoaApproval, as: 'approvals' },
|
||||||
{ model: LoaDocumentGenerated, as: 'generatedDocuments' }
|
{ model: LoaDocumentGenerated, as: 'generatedDocuments' }
|
||||||
@ -43,12 +55,17 @@ export const getRequest = async (req: Request, res: Response) => {
|
|||||||
export const createRequest = async (req: AuthRequest, res: Response) => {
|
export const createRequest = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { applicationId } = req.body;
|
const { applicationId } = req.body;
|
||||||
|
const targetId = applicationId as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
|
||||||
|
const application = await db.Application.findOne({
|
||||||
|
where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
const application = await db.Application.findByPk(applicationId);
|
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
|
||||||
const [request, created] = await LoaRequest.findOrCreate({
|
const [request, created] = await LoaRequest.findOrCreate({
|
||||||
where: { applicationId },
|
where: { applicationId: application.id },
|
||||||
defaults: {
|
defaults: {
|
||||||
requestedBy: req.user?.id,
|
requestedBy: req.user?.id,
|
||||||
status: 'In Progress'
|
status: 'In Progress'
|
||||||
@ -176,8 +193,8 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
|
|
||||||
const application = await db.Application.findByPk(request.applicationId);
|
const application = await db.Application.findByPk(request.applicationId);
|
||||||
if (application) {
|
if (application) {
|
||||||
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOA_ISSUED, req.user.id, {
|
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.EOR_IN_PROGRESS, req.user.id, {
|
||||||
reason: 'LOA fully approved and issued',
|
reason: 'LOA fully approved. Moving to EOR Work.',
|
||||||
progressPercentage: 97
|
progressPercentage: 97
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -205,9 +222,20 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
export const getApprovalStatus = async (req: AuthRequest, res: Response) => {
|
export const getApprovalStatus = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { applicationId } = req.params;
|
const { applicationId } = req.params;
|
||||||
|
const targetId = applicationId as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
|
||||||
|
const application = await db.Application.findOne({
|
||||||
|
where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!application) {
|
||||||
|
return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
}
|
||||||
|
|
||||||
const policy = await ensureLoaPolicy();
|
const policy = await ensureLoaPolicy();
|
||||||
const actions = await StageApprovalAction.findAll({
|
const actions = await StageApprovalAction.findAll({
|
||||||
where: { applicationId, stageCode: LOA_STAGE_CODE },
|
where: { applicationId: application.id, stageCode: LOA_STAGE_CODE },
|
||||||
include: [{ model: User, as: 'actor', attributes: ['id', 'fullName', 'email', 'roleCode'] }],
|
include: [{ model: User, as: 'actor', attributes: ['id', 'fullName', 'email', 'roleCode'] }],
|
||||||
order: [['updatedAt', 'DESC']]
|
order: [['updatedAt', 'DESC']]
|
||||||
});
|
});
|
||||||
@ -285,55 +313,15 @@ export const updateSecurityDeposit = async (req: AuthRequest, res: Response) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- AUTOMATION: After verification transitions ---
|
// --- AUTOMATION: After verification transitions ---
|
||||||
|
|
||||||
// 1. If INITIAL Payment Verified -> Approve LOI Finance Role
|
// 1. If INITIAL Payment Verified -> Move to LOI Issue Stage
|
||||||
// Bridge 1.0: AUTOMATED LOI APPROVAL IF INITIAL PAYMENT IS VERIFIED
|
|
||||||
console.log(`[DEBUG] Payment Verification Trace -> Deposit Type: ${depositType}, Status: ${status}`);
|
|
||||||
if ((depositType === 'INITIAL' || !depositType) && status === 'Verified') {
|
if ((depositType === 'INITIAL' || !depositType) && status === 'Verified') {
|
||||||
console.log(`[DEBUG] Initial Deposit VERIFIED for Application: ${application.id}. Ensuring LOI records exist...`);
|
console.log(`[DEBUG] Initial Security Deposit verified. Moving to LOI Issued stage...`);
|
||||||
const LoiRequest = db.LoiRequest;
|
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOI_ISSUED, req.user?.id || null, {
|
||||||
const LoiApproval = db.LoiApproval;
|
reason: 'Initial Security Deposit verified. Proceeding to LOI Issuance.',
|
||||||
|
stage: APPLICATION_STAGES.LOI,
|
||||||
// 1. Proactively ensure the LOI Request exists if the payment is cleared
|
progressPercentage: 80
|
||||||
const [loiReq, createdReq] = await db.LoiRequest.findOrCreate({
|
|
||||||
where: { applicationId: application.id },
|
|
||||||
defaults: { status: 'Pending Approval' }
|
|
||||||
});
|
});
|
||||||
console.log(`[DEBUG] LOI Request ID: ${loiReq.id}, Status: ${loiReq.status} (Created: ${createdReq})`);
|
|
||||||
|
|
||||||
// 2. Initialize the three required approval roles for the LOI step
|
|
||||||
const roles = ['Finance', 'DD Head', 'NBH'];
|
|
||||||
await Promise.all(roles.map(async (role) => {
|
|
||||||
const [approval, created] = await db.LoiApproval.findOrCreate({
|
|
||||||
where: { requestId: loiReq.id, approverRole: role },
|
|
||||||
defaults: { action: 'Pending', level: 1 }
|
|
||||||
});
|
|
||||||
console.log(`[DEBUG] Role ${role}: Status=${approval.action} (Created: ${created})`);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 3. Mark the Finance role as Approved based on this verified payment
|
|
||||||
const financeApproval = await db.LoiApproval.findOne({
|
|
||||||
where: { requestId: loiReq.id, approverRole: 'Finance' }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (financeApproval) {
|
|
||||||
console.log(`[DEBUG] Marking Finance Approval record as Approved...`);
|
|
||||||
await financeApproval.update({
|
|
||||||
action: 'Approved',
|
|
||||||
actorUserId: req.user?.id,
|
|
||||||
actionedAt: new Date(),
|
|
||||||
comments: 'Initial Security Deposit verified.'
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[DEBUG] Initial Security Deposit verified. Transitioning to LOI Issued...`);
|
|
||||||
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOI_ISSUED, req.user?.id || null, {
|
|
||||||
reason: 'Initial Security Deposit verified. Proceeding to LOI Issuance.',
|
|
||||||
stage: APPLICATION_STAGES.LOI,
|
|
||||||
progressPercentage: 80
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(`[DEBUG] No pending Finance approval in LOI stage. Skipping auto-bridge.`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. If FINAL Payment Verified -> Move to LOA Pending stage
|
// 2. If FINAL Payment Verified -> Move to LOA Pending stage
|
||||||
|
|||||||
@ -12,20 +12,41 @@ const ensureLoiPolicy = async () => {
|
|||||||
where: { stageCode: LOI_STAGE_CODE },
|
where: { stageCode: LOI_STAGE_CODE },
|
||||||
defaults: {
|
defaults: {
|
||||||
stageCode: LOI_STAGE_CODE,
|
stageCode: LOI_STAGE_CODE,
|
||||||
minApprovals: 3,
|
minApprovals: 2,
|
||||||
approvalMode: 'ROLE_MANDATORY',
|
approvalMode: 'ROLE_MANDATORY',
|
||||||
requiredRoles: ['Finance', 'DD Head', 'NBH'],
|
requiredRoles: ['DD Head', 'NBH'],
|
||||||
isActive: true
|
isActive: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If policy already exists but has Finance, update it
|
||||||
|
if (policy && Array.isArray(policy.requiredRoles) && policy.requiredRoles.includes('Finance')) {
|
||||||
|
await policy.update({
|
||||||
|
requiredRoles: ['DD Head', 'NBH'],
|
||||||
|
minApprovals: 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return policy;
|
return policy;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRequest = async (req: Request, res: Response) => {
|
export const getRequest = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { applicationId } = req.params;
|
const { applicationId } = req.params;
|
||||||
|
const targetId = applicationId as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
|
||||||
|
// Resolve application first to get UUID
|
||||||
|
const application = await db.Application.findOne({
|
||||||
|
where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!application) {
|
||||||
|
return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
}
|
||||||
|
|
||||||
const request = await LoiRequest.findOne({
|
const request = await LoiRequest.findOne({
|
||||||
where: { applicationId },
|
where: { applicationId: application.id },
|
||||||
include: [
|
include: [
|
||||||
{ model: LoiApproval, as: 'approvals' },
|
{ model: LoiApproval, as: 'approvals' },
|
||||||
{ model: LoiDocumentGenerated, as: 'generatedDocuments' },
|
{ model: LoiDocumentGenerated, as: 'generatedDocuments' },
|
||||||
@ -73,33 +94,38 @@ export const acknowledgeRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
export const createRequest = async (req: AuthRequest, res: Response) => {
|
export const createRequest = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { applicationId } = req.body;
|
const { applicationId } = req.body;
|
||||||
|
const targetId = applicationId as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
|
||||||
|
const application = await db.Application.findOne({
|
||||||
|
where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
const application = await db.Application.findByPk(applicationId);
|
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
|
||||||
const [request, created] = await LoiRequest.findOrCreate({
|
const [request, created] = await LoiRequest.findOrCreate({
|
||||||
where: { applicationId },
|
where: { applicationId: application.id },
|
||||||
defaults: {
|
defaults: {
|
||||||
requestedBy: req.user?.id,
|
requestedBy: req.user?.id,
|
||||||
status: 'In Progress'
|
status: 'In Progress'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize first level approval (Finance) if not already exists
|
// Initialize first level approval (DD Head) if not already exists
|
||||||
await LoiApproval.findOrCreate({
|
await LoiApproval.findOrCreate({
|
||||||
where: { requestId: request.id, level: 1 },
|
where: { requestId: request.id, level: 1 },
|
||||||
defaults: {
|
defaults: {
|
||||||
approverRole: 'Finance',
|
approverRole: 'DD Head',
|
||||||
action: 'Pending'
|
action: 'Pending'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOI_IN_PROGRESS, req.user?.id || null, {
|
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOI_IN_PROGRESS, req.user?.id || null, {
|
||||||
reason: 'LOI Request initiated with Finance approval',
|
reason: 'LOI Request initiated for DD Head approval',
|
||||||
progressPercentage: 75
|
progressPercentage: 75
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(201).json({ success: true, message: 'LOI Request initiated with Finance approval', data: request });
|
res.status(201).json({ success: true, message: 'LOI Request initiated for DD Head approval', data: request });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Create LOI request error:', error);
|
console.error('Create LOI request error:', error);
|
||||||
res.status(500).json({ success: false, message: 'Error creating LOI request' });
|
res.status(500).json({ success: false, message: 'Error creating LOI request' });
|
||||||
@ -260,9 +286,20 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
export const getApprovalStatus = async (req: AuthRequest, res: Response) => {
|
export const getApprovalStatus = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { applicationId } = req.params;
|
const { applicationId } = req.params;
|
||||||
|
const targetId = applicationId as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
|
||||||
|
const application = await db.Application.findOne({
|
||||||
|
where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!application) {
|
||||||
|
return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
}
|
||||||
|
|
||||||
const policy = await ensureLoiPolicy();
|
const policy = await ensureLoiPolicy();
|
||||||
const actions = await StageApprovalAction.findAll({
|
const actions = await StageApprovalAction.findAll({
|
||||||
where: { applicationId, stageCode: LOI_STAGE_CODE },
|
where: { applicationId: application.id, stageCode: LOI_STAGE_CODE },
|
||||||
include: [{ model: User, as: 'actor', attributes: ['id', 'fullName', 'email', 'roleCode'] }],
|
include: [{ model: User, as: 'actor', attributes: ['id', 'fullName', 'email', 'roleCode'] }],
|
||||||
order: [['updatedAt', 'DESC']]
|
order: [['updatedAt', 'DESC']]
|
||||||
});
|
});
|
||||||
|
|||||||
@ -264,7 +264,7 @@ export const getApplicationById = async (req: AuthRequest, res: Response) => {
|
|||||||
'GST Certificate', 'PAN Card', 'Bank Statement', 'Cancelled Check',
|
'GST Certificate', 'PAN Card', 'Bank Statement', 'Cancelled Check',
|
||||||
'Partnership Deed', 'LLP Agreement', 'Certificate of Incorporation', 'MOA', 'AOA',
|
'Partnership Deed', 'LLP Agreement', 'Certificate of Incorporation', 'MOA', 'AOA',
|
||||||
'Property Documents', 'Rental Agreement', 'Firm Registration', 'CIBIL Report',
|
'Property Documents', 'Rental Agreement', 'Firm Registration', 'CIBIL Report',
|
||||||
'FDD Final Audit Report', 'FDD Audit Report'
|
'FDD Final Audit Report', 'FDD Audit Report', 'Income Tax Returns (ITR)', 'Business Valuation Report'
|
||||||
];
|
];
|
||||||
|
|
||||||
if (restrictedData.uploadedDocuments) {
|
if (restrictedData.uploadedDocuments) {
|
||||||
@ -302,17 +302,21 @@ export const getApplicationById = async (req: AuthRequest, res: Response) => {
|
|||||||
export const updateApplicationStatus = async (req: AuthRequest, res: Response) => {
|
export const updateApplicationStatus = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
const targetId = id as string;
|
||||||
const { status, stage, reason } = req.body;
|
const { status, stage, reason } = req.body;
|
||||||
|
|
||||||
const application = await Application.findByPk(id);
|
// Resolve application by ID (UUID) or Registeration Number (applicationId)
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
const application = await Application.findOne({
|
||||||
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
|
||||||
if (application) {
|
await WorkflowService.transitionApplication(application, status, req.user?.id || null, {
|
||||||
await WorkflowService.transitionApplication(application, status, req.user?.id || null, {
|
reason: reason || 'Manual Status Update',
|
||||||
reason: reason || 'Manual Status Update',
|
stage: stage
|
||||||
stage: stage
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ success: true, message: 'Application status updated successfully' });
|
res.json({ success: true, message: 'Application status updated successfully' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -335,13 +339,10 @@ export const uploadDocuments = async (req: any, res: Response) => {
|
|||||||
return res.status(400).json({ success: false, message: 'Document type is required' });
|
return res.status(400).json({ success: false, message: 'Document type is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const targetId = id as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
const application = await Application.findOne({
|
const application = await Application.findOne({
|
||||||
where: {
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
[Op.or]: [
|
|
||||||
{ id },
|
|
||||||
{ applicationId: id }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!application) {
|
if (!application) {
|
||||||
@ -402,11 +403,19 @@ export const uploadDocuments = async (req: any, res: Response) => {
|
|||||||
await db.EorChecklistItem.bulkCreate(itemsData);
|
await db.EorChecklistItem.bulkCreate(itemsData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the matching item - Link only, don't auto-verify (requested by user)
|
console.log(`[debug] EOR Checklist found/created: ${checklist.id} for Application: ${application.id}`);
|
||||||
await db.EorChecklistItem.update(
|
|
||||||
|
const [updatedCount] = await db.EorChecklistItem.update(
|
||||||
{ proofDocumentId: newDoc.id, isCompliant: false },
|
{ proofDocumentId: newDoc.id, isCompliant: false },
|
||||||
{ where: { checklistId: checklist.id, description: documentType } }
|
{
|
||||||
|
where: {
|
||||||
|
checklistId: checklist.id,
|
||||||
|
description: { [Op.iLike]: documentType.trim() }
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(`[debug] EOR items updated: ${updatedCount} for type: ${documentType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
@ -424,14 +433,12 @@ export const getApplicationDocuments = async (req: AuthRequest, res: Response) =
|
|||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const targetId = id as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
|
||||||
// Resolve ID to primary key if it's an appId string
|
// Resolve ID to primary key if it's an appId string
|
||||||
const application = await Application.findOne({
|
const application = await Application.findOne({
|
||||||
where: {
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
[Op.or]: [
|
|
||||||
{ id },
|
|
||||||
{ applicationId: id }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!application) {
|
if (!application) {
|
||||||
@ -484,8 +491,9 @@ export const bulkShortlist = async (req: AuthRequest, res: Response) => {
|
|||||||
|
|
||||||
// Update Applications sequentially via WorkflowService for consistency
|
// Update Applications sequentially via WorkflowService for consistency
|
||||||
for (const appId of applicationIds) {
|
for (const appId of applicationIds) {
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(appId);
|
||||||
const application = await Application.findOne({
|
const application = await Application.findOne({
|
||||||
where: { [Op.or]: [{ id: appId }, { applicationId: appId }] }
|
where: isUUID ? { [Op.or]: [{ id: appId }, { applicationId: appId }] } : { applicationId: appId }
|
||||||
});
|
});
|
||||||
if (application) {
|
if (application) {
|
||||||
await application.update({
|
await application.update({
|
||||||
@ -541,8 +549,9 @@ export const bulkShortlist = async (req: AuthRequest, res: Response) => {
|
|||||||
const assignStageEvaluators = async (appIdOrId: string) => {
|
const assignStageEvaluators = async (appIdOrId: string) => {
|
||||||
try {
|
try {
|
||||||
console.log(`[debug] Starting stage evaluator assignment for App: ${appIdOrId}`);
|
console.log(`[debug] Starting stage evaluator assignment for App: ${appIdOrId}`);
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(appIdOrId);
|
||||||
const application = await Application.findOne({
|
const application = await Application.findOne({
|
||||||
where: { [Op.or]: [{ id: appIdOrId }, { applicationId: appIdOrId }] },
|
where: isUUID ? { [Op.or]: [{ id: appIdOrId }, { applicationId: appIdOrId }] } : { applicationId: appIdOrId },
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: District,
|
model: District,
|
||||||
@ -693,14 +702,18 @@ const assignStageEvaluators = async (appIdOrId: string) => {
|
|||||||
export const retriggerEvaluators = async (req: AuthRequest, res: Response) => {
|
export const retriggerEvaluators = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const application = await Application.findByPk(id);
|
const targetId = id as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
const application = await Application.findOne({
|
||||||
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
|
||||||
// Remove existing auto-mapped participants (Interviews, LOI, LOA)
|
// Remove existing auto-mapped participants (Interviews, LOI, LOA)
|
||||||
// Using a more robust Postgres-compatible JSON path check
|
// Using a more robust Postgres-compatible JSON path check
|
||||||
await db.RequestParticipant.destroy({
|
await db.RequestParticipant.destroy({
|
||||||
where: {
|
where: {
|
||||||
requestId: id,
|
requestId: application.id,
|
||||||
requestType: 'application',
|
requestType: 'application',
|
||||||
joinedMethod: 'auto',
|
joinedMethod: 'auto',
|
||||||
[Op.and]: [
|
[Op.and]: [
|
||||||
@ -726,6 +739,7 @@ export const retriggerEvaluators = async (req: AuthRequest, res: Response) => {
|
|||||||
export const assignArchitectureTeam = async (req: AuthRequest, res: Response) => {
|
export const assignArchitectureTeam = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
const targetId = id as string;
|
||||||
const { userId, assignedTo, remarks } = req.body;
|
const { userId, assignedTo, remarks } = req.body;
|
||||||
const targetUserId = userId || assignedTo;
|
const targetUserId = userId || assignedTo;
|
||||||
|
|
||||||
@ -733,7 +747,10 @@ export const assignArchitectureTeam = async (req: AuthRequest, res: Response) =>
|
|||||||
return res.status(400).json({ success: false, message: 'Architecture team member (userId) is required' });
|
return res.status(400).json({ success: false, message: 'Architecture team member (userId) is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const application = await Application.findByPk(id);
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
const application = await Application.findOne({
|
||||||
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
|
||||||
await application.update({
|
await application.update({
|
||||||
@ -768,9 +785,13 @@ export const assignArchitectureTeam = async (req: AuthRequest, res: Response) =>
|
|||||||
export const updateArchitectureStatus = async (req: AuthRequest, res: Response) => {
|
export const updateArchitectureStatus = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
const targetId = id as string;
|
||||||
const { status, remarks } = req.body;
|
const { status, remarks } = req.body;
|
||||||
|
|
||||||
const application = await Application.findByPk(id);
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
const application = await Application.findOne({
|
||||||
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
|
||||||
const updateData: any = {
|
const updateData: any = {
|
||||||
@ -805,8 +826,12 @@ import { ExternalMocksService } from '../../common/utils/externalMocks.service.j
|
|||||||
export const generateDealerCodes = async (req: AuthRequest, res: Response) => {
|
export const generateDealerCodes = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params; // applicationId
|
const { id } = req.params; // applicationId
|
||||||
|
const targetId = id as string;
|
||||||
|
|
||||||
const application = await Application.findByPk(id);
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
const application = await Application.findOne({
|
||||||
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
|
||||||
// Trigger Mock SAP Integration
|
// Trigger Mock SAP Integration
|
||||||
@ -815,7 +840,7 @@ export const generateDealerCodes = async (req: AuthRequest, res: Response) => {
|
|||||||
// Save Dealer Codes
|
// Save Dealer Codes
|
||||||
await db.DealerCode.create({
|
await db.DealerCode.create({
|
||||||
dealerCode: sapData.salesCode, // Use sales code as primary dealer code
|
dealerCode: sapData.salesCode, // Use sales code as primary dealer code
|
||||||
applicationId: id,
|
applicationId: application.id,
|
||||||
salesCode: sapData.salesCode,
|
salesCode: sapData.salesCode,
|
||||||
serviceCode: sapData.serviceCode,
|
serviceCode: sapData.serviceCode,
|
||||||
gmaCode: sapData.gmaCode,
|
gmaCode: sapData.gmaCode,
|
||||||
@ -827,7 +852,7 @@ export const generateDealerCodes = async (req: AuthRequest, res: Response) => {
|
|||||||
|
|
||||||
// Create Final Security Deposit record (Blocker for LOA)
|
// Create Final Security Deposit record (Blocker for LOA)
|
||||||
await db.SecurityDeposit.findOrCreate({
|
await db.SecurityDeposit.findOrCreate({
|
||||||
where: { applicationId: id, depositType: 'FINAL' },
|
where: { applicationId: application.id, depositType: 'FINAL' },
|
||||||
defaults: {
|
defaults: {
|
||||||
amount: 1500000, // 15 Lakhs Final
|
amount: 1500000, // 15 Lakhs Final
|
||||||
status: 'Pending'
|
status: 'Pending'
|
||||||
@ -959,7 +984,12 @@ export const deleteDocumentConfig = async (req: AuthRequest, res: Response) => {
|
|||||||
export const updateApplication = async (req: AuthRequest, res: Response) => {
|
export const updateApplication = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const application = await Application.findByPk(id);
|
const targetId = id as string;
|
||||||
|
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
const application = await Application.findOne({
|
||||||
|
where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
if (!application) return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
|
|
||||||
await application.update(req.body);
|
await application.update(req.body);
|
||||||
|
|||||||
@ -85,8 +85,14 @@ export const submitResponse = async (req: AuthRequest, res: Response) => {
|
|||||||
try {
|
try {
|
||||||
const { applicationId, responses } = req.body; // responses: [{ questionId, value }]
|
const { applicationId, responses } = req.body; // responses: [{ questionId, value }]
|
||||||
|
|
||||||
|
const targetId = applicationId as string;
|
||||||
|
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId);
|
||||||
|
|
||||||
// Verify application
|
// Verify application
|
||||||
const application = await Application.findByPk(applicationId);
|
const application = await Application.findOne({
|
||||||
|
where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
|
||||||
|
});
|
||||||
|
|
||||||
if (!application) {
|
if (!application) {
|
||||||
return res.status(404).json({ success: false, message: 'Application not found' });
|
return res.status(404).json({ success: false, message: 'Application not found' });
|
||||||
}
|
}
|
||||||
@ -96,7 +102,7 @@ export const submitResponse = async (req: AuthRequest, res: Response) => {
|
|||||||
if (!questionnaire) return res.status(400).json({ success: false, message: 'No active questionnaire' });
|
if (!questionnaire) return res.status(400).json({ success: false, message: 'No active questionnaire' });
|
||||||
|
|
||||||
const responseRecords = responses.map((r: any) => ({
|
const responseRecords = responses.map((r: any) => ({
|
||||||
applicationId,
|
applicationId: application.id,
|
||||||
questionnaireId: questionnaire.id,
|
questionnaireId: questionnaire.id,
|
||||||
questionId: r.questionId,
|
questionId: r.questionId,
|
||||||
responseValue: r.value,
|
responseValue: r.value,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user