From 19c766c99965b6f0e3664ce94160a4d5c230bd0c Mon Sep 17 00:00:00 2001 From: laxman h Date: Fri, 10 Apr 2026 20:54:59 +0530 Subject: [PATCH] end to end flow testing for all modules paralley enhancing profile schema to gather all info --- check_participants.ts | 43 +++++-- fix_missing_participants.ts | 32 ----- src/database/models/Application.ts | 5 +- src/database/models/Dealer.ts | 39 ++++++- src/database/models/FddReport.ts | 10 ++ src/database/models/LoaRequest.ts | 4 +- src/database/models/LoiRequest.ts | 4 +- src/database/models/Resignation.ts | 3 +- src/database/models/SecurityDeposit.ts | 10 +- src/database/models/TerminationRequest.ts | 3 +- .../assessment/assessment.controller.ts | 26 +++-- src/modules/dealer/dealer.controller.ts | 24 +++- src/modules/fdd/fdd.controller.ts | 66 +++++++++-- src/modules/loa/loa.controller.ts | 22 ++-- src/modules/loi/loi.controller.ts | 2 +- .../onboarding/onboarding.controller.ts | 2 +- .../self-service/constitutional.controller.ts | 24 +++- .../self-service/resignation.controller.ts | 24 +++- .../termination/termination.controller.ts | 26 ++++- src/services/ParticipantService.ts | 109 ++++++++++++------ sync_participants.ts | 30 +++++ trigger-resignation.js | 7 +- trigger-workflow.js | 16 +-- 23 files changed, 385 insertions(+), 146 deletions(-) delete mode 100644 fix_missing_participants.ts create mode 100644 sync_participants.ts diff --git a/check_participants.ts b/check_participants.ts index c13a7d3..c88b4f5 100644 --- a/check_participants.ts +++ b/check_participants.ts @@ -1,22 +1,41 @@ + +import pkg from 'pg'; +const { Client } = pkg; import 'dotenv/config'; -import db from './src/database/models/index.js'; -const { RequestParticipant } = (db as any).default || db; -const applicationId = '6139d6f9-f3c1-4e55-903b-3516d3a08955'; +async function check() { + const client = new Client({ + user: process.env.DB_USER || 'postgres', + password: process.env.DB_PASSWORD || 'Admin@123', + host: process.env.DB_HOST || 'localhost', + database: process.env.DB_NAME || 'royal_enfield_onboarding', + port: parseInt(process.env.DB_PORT || '5432'), + }); -async function checkParticipants() { try { - const count = await RequestParticipant.count({ - where: { requestId: applicationId, requestType: 'application' } - }); + await client.connect(); + const res = await client.query('SELECT * FROM request_participants WHERE "requestType" = $1', ['resignation']); + console.log(`Found ${res.rows.length} participants for Resignations.`); + + const termRes = await client.query('SELECT * FROM request_participants WHERE "requestType" = $1', ['termination']); + console.log(`Found ${termRes.rows.length} participants for Terminations.`); - console.log(`Application has ${count} participants.`); + if (res.rows.length > 0) { + console.log('Sample Resignation Participant:', JSON.stringify(res.rows[0], null, 2)); + } + + if (termRes.rows.length > 0) { + console.log('Sample Termination Participant:', JSON.stringify(termRes.rows[0], null, 2)); + } - } catch (error) { - console.error('Error checking participants:', error); + const resignations = await client.query('SELECT id, "resignationId" FROM resignations LIMIT 5'); + console.log('Resignations in DB:', resignations.rows.map(r => r.resignationId)); + + } catch (err) { + console.error('Error:', err.message); } finally { - process.exit(); + await client.end(); } } -checkParticipants(); +check(); diff --git a/fix_missing_participants.ts b/fix_missing_participants.ts deleted file mode 100644 index f76bc76..0000000 --- a/fix_missing_participants.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ParticipantService } from './src/services/ParticipantService.js'; - -async function run() { - try { - const requestId = '29b742a7-6d9f-4736-8aae-295ffe32ef75'; - console.log(`Fixing participants for resignation ${requestId}...`); - - const { Resignation, User, Dealer, Application, District } = (await import('./src/database/models/index.js')).default; - const resignation = await Resignation.findByPk(requestId); - console.log('Resignation Record:', JSON.stringify(resignation, null, 2)); - - if (resignation) { - const user = await User.findByPk(resignation.dealerId); - console.log('User Record:', JSON.stringify(user, null, 2)); - if (user && user.dealerId) { - const dealer = await Dealer.findByPk(user.dealerId, { - include: [{ model: Application, as: 'application', include: [{ model: District, as: 'district' }] }] - }); - console.log('Dealer/Application/District Record:', JSON.stringify(dealer, null, 2)); - } - } - - await ParticipantService.assignResignationParticipants(requestId); - console.log('Done.'); - process.exit(0); - } catch (error) { - console.error('Error fixing participants:', error); - process.exit(1); - } -} - -run(); diff --git a/src/database/models/Application.ts b/src/database/models/Application.ts index 3430585..ef833d5 100644 --- a/src/database/models/Application.ts +++ b/src/database/models/Application.ts @@ -271,7 +271,8 @@ export default (sequelize: Sequelize) => { Application.hasMany(models.RequestParticipant, { foreignKey: 'requestId', as: 'participants', - scope: { requestType: 'application' } + scope: { requestType: 'application' }, + constraints: false }); Application.hasOne(models.DealerCode, { foreignKey: 'applicationId', as: 'dealerCode' }); Application.hasMany(models.StageApprovalAction, { foreignKey: 'applicationId', as: 'stageApprovals' }); @@ -279,6 +280,8 @@ export default (sequelize: Sequelize) => { Application.hasMany(models.SecurityDeposit, { foreignKey: 'applicationId', as: 'securityDeposits' }); Application.hasOne(models.EorChecklist, { foreignKey: 'applicationId', as: 'eorChecklist' }); Application.hasMany(models.FddAssignment, { foreignKey: 'applicationId', as: 'fddAssignments' }); + Application.hasMany(models.LoiRequest, { foreignKey: 'applicationId', as: 'loiRequests' }); + Application.hasMany(models.LoaRequest, { foreignKey: 'applicationId', as: 'loaRequests' }); }; return Application; diff --git a/src/database/models/Dealer.ts b/src/database/models/Dealer.ts index d11d0dd..67d8359 100644 --- a/src/database/models/Dealer.ts +++ b/src/database/models/Dealer.ts @@ -2,7 +2,7 @@ import { Model, DataTypes, Sequelize } from 'sequelize'; export interface DealerAttributes { id: string; - applicationId: string; + applicationId: string | null; dealerCodeId: string | null; legalName: string; businessName: string; @@ -12,6 +12,13 @@ export interface DealerAttributes { panNumber: string | null; status: string; onboardedAt: Date | null; + loiDate: Date | null; + loaDate: Date | null; + isLegacy: boolean; + securityDepositAmount: number | null; + securityDepositDate: Date | null; + lastWorkingDay: Date | null; + exitReason: string | null; } export interface DealerInstance extends Model, DealerAttributes { } @@ -25,7 +32,7 @@ export default (sequelize: Sequelize) => { }, applicationId: { type: DataTypes.UUID, - allowNull: false, + allowNull: true, references: { model: 'applications', key: 'id' @@ -70,6 +77,34 @@ export default (sequelize: Sequelize) => { onboardedAt: { type: DataTypes.DATE, allowNull: true + }, + loiDate: { + type: DataTypes.DATE, + allowNull: true + }, + loaDate: { + type: DataTypes.DATE, + allowNull: true + }, + isLegacy: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + securityDepositAmount: { + type: DataTypes.DECIMAL(15, 2), + allowNull: true + }, + securityDepositDate: { + type: DataTypes.DATE, + allowNull: true + }, + lastWorkingDay: { + type: DataTypes.DATE, + allowNull: true + }, + exitReason: { + type: DataTypes.TEXT, + allowNull: true } }, { tableName: 'dealers', diff --git a/src/database/models/FddReport.ts b/src/database/models/FddReport.ts index 6f161a6..eea5d7e 100644 --- a/src/database/models/FddReport.ts +++ b/src/database/models/FddReport.ts @@ -8,6 +8,7 @@ export interface FddReportAttributes { recommendation: string | null; verifiedAt: Date | null; verifiedBy: string | null; + submittedBy: string | null; } export interface FddReportInstance extends Model, FddReportAttributes { } @@ -54,6 +55,14 @@ export default (sequelize: Sequelize) => { model: 'users', key: 'id' } + }, + submittedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } } }, { tableName: 'fdd_reports', @@ -64,6 +73,7 @@ export default (sequelize: Sequelize) => { FddReport.belongsTo(models.FddAssignment, { foreignKey: 'assignmentId', as: 'assignment' }); FddReport.belongsTo(models.OnboardingDocument, { foreignKey: 'reportDocumentId', as: 'reportDocument' }); FddReport.belongsTo(models.User, { foreignKey: 'verifiedBy', as: 'verifier' }); + FddReport.belongsTo(models.User, { foreignKey: 'submittedBy', as: 'submitter' }); }; return FddReport; diff --git a/src/database/models/LoaRequest.ts b/src/database/models/LoaRequest.ts index f3f9c2d..645af0d 100644 --- a/src/database/models/LoaRequest.ts +++ b/src/database/models/LoaRequest.ts @@ -2,7 +2,7 @@ import { Model, DataTypes, Sequelize } from 'sequelize'; export interface LoaRequestAttributes { id: string; - applicationId: string; + applicationId: string | null; status: string; requestedBy: string | null; approvedAt: Date | null; @@ -20,7 +20,7 @@ export default (sequelize: Sequelize) => { }, applicationId: { type: DataTypes.UUID, - allowNull: false, + allowNull: true, references: { model: 'applications', key: 'id' diff --git a/src/database/models/LoiRequest.ts b/src/database/models/LoiRequest.ts index d9c3b67..6d5d49f 100644 --- a/src/database/models/LoiRequest.ts +++ b/src/database/models/LoiRequest.ts @@ -2,7 +2,7 @@ import { Model, DataTypes, Sequelize } from 'sequelize'; export interface LoiRequestAttributes { id: string; - applicationId: string; + applicationId: string | null; status: string; requestedBy: string | null; approvedAt: Date | null; @@ -20,7 +20,7 @@ export default (sequelize: Sequelize) => { }, applicationId: { type: DataTypes.UUID, - allowNull: false, + allowNull: true, references: { model: 'applications', key: 'id' diff --git a/src/database/models/Resignation.ts b/src/database/models/Resignation.ts index 2c188ed..4b8c469 100644 --- a/src/database/models/Resignation.ts +++ b/src/database/models/Resignation.ts @@ -150,7 +150,8 @@ export default (sequelize: Sequelize) => { Resignation.hasMany(models.RequestParticipant, { foreignKey: 'requestId', as: 'participants', - scope: { requestType: 'resignation' } + scope: { requestType: 'resignation' }, + constraints: false }); }; diff --git a/src/database/models/SecurityDeposit.ts b/src/database/models/SecurityDeposit.ts index 7d8f428..7d9b9fc 100644 --- a/src/database/models/SecurityDeposit.ts +++ b/src/database/models/SecurityDeposit.ts @@ -2,12 +2,12 @@ import { Model, DataTypes, Sequelize } from 'sequelize'; export interface SecurityDepositAttributes { id: string; - applicationId: string; + applicationId: string | null; amount: number; paymentReference: string | null; proofDocumentId: string | null; status: string; - depositType: 'INITIAL' | 'FINAL'; + depositType: 'SECURITY_DEPOSIT' | 'FIRST_FILL'; verifiedAt: Date | null; verifiedBy: string | null; } @@ -23,7 +23,7 @@ export default (sequelize: Sequelize) => { }, applicationId: { type: DataTypes.UUID, - allowNull: false, + allowNull: true, references: { model: 'applications', key: 'id' @@ -50,9 +50,9 @@ export default (sequelize: Sequelize) => { defaultValue: 'pending' }, depositType: { - type: DataTypes.ENUM('INITIAL', 'FINAL'), + type: DataTypes.ENUM('SECURITY_DEPOSIT', 'FIRST_FILL'), allowNull: false, - defaultValue: 'INITIAL' + defaultValue: 'SECURITY_DEPOSIT' }, verifiedAt: { type: DataTypes.DATE, diff --git a/src/database/models/TerminationRequest.ts b/src/database/models/TerminationRequest.ts index f1be060..45d7275 100644 --- a/src/database/models/TerminationRequest.ts +++ b/src/database/models/TerminationRequest.ts @@ -114,7 +114,8 @@ export default (sequelize: Sequelize) => { TerminationRequest.hasMany(models.RequestParticipant, { foreignKey: 'requestId', as: 'participants', - scope: { requestType: 'termination' } + scope: { requestType: 'termination' }, + constraints: false }); }; diff --git a/src/modules/assessment/assessment.controller.ts b/src/modules/assessment/assessment.controller.ts index d978ae1..678a4ad 100644 --- a/src/modules/assessment/assessment.controller.ts +++ b/src/modules/assessment/assessment.controller.ts @@ -150,14 +150,24 @@ const processStageDecision = async (params: { } } - await db.FddReport.create({ - assignmentId: assignment.id, - reportDocumentId: lastReportDoc?.id || null, - findings, - recommendation, - verifiedAt: new Date(), - verifiedBy: userId - }); + let report = await db.FddReport.findOne({ where: { assignmentId: assignment.id } }); + + if (report) { + await report.update({ + verifiedAt: new Date(), + verifiedBy: userId + }); + } else { + await db.FddReport.create({ + assignmentId: assignment.id, + reportDocumentId: lastReportDoc?.id || null, + findings, + recommendation, + verifiedAt: new Date(), + verifiedBy: userId, + submittedBy: userId // Admin submitted it if no existing report + }); + } await assignment.update({ status: 'Report Submitted' }); diff --git a/src/modules/dealer/dealer.controller.ts b/src/modules/dealer/dealer.controller.ts index f1cfd00..c1ba0ce 100644 --- a/src/modules/dealer/dealer.controller.ts +++ b/src/modules/dealer/dealer.controller.ts @@ -97,6 +97,24 @@ export const createDealer = async (req: AuthRequest, res: Response) => { }, { where: { id: targetDealerCodeId } }); } + // Fetch milestone dates and payments from requests + const loiRequest = await db.LoiRequest.findOne({ + where: { applicationId: application.id, status: { [Op.iLike]: 'Approved' } }, + order: [['approvedAt', 'DESC']] + }); + const loaRequest = await db.LoaRequest.findOne({ + where: { applicationId: application.id, status: { [Op.iLike]: 'Approved' } }, + order: [['approvedAt', 'DESC']] + }); + const deposit = await db.SecurityDeposit.findOne({ + where: { + applicationId: application.id, + status: 'Verified', + depositType: 'SECURITY_DEPOSIT' // Strictly the Security Deposit + }, + order: [['verifiedAt', 'DESC']] + }); + // Create Dealer Profile dealer = await Dealer.create({ applicationId, @@ -105,7 +123,11 @@ export const createDealer = async (req: AuthRequest, res: Response) => { businessName: application.applicantName, constitutionType: application.constitutionType || 'Proprietorship', status: 'Active', - onboardedAt: new Date() + onboardedAt: new Date(), + loiDate: loiRequest?.approvedAt, + loaDate: loaRequest?.approvedAt, + securityDepositAmount: deposit?.amount, + securityDepositDate: deposit?.verifiedAt }); await AuditLog.create({ diff --git a/src/modules/fdd/fdd.controller.ts b/src/modules/fdd/fdd.controller.ts index f67dc1f..9b43042 100644 --- a/src/modules/fdd/fdd.controller.ts +++ b/src/modules/fdd/fdd.controller.ts @@ -23,7 +23,14 @@ export const getAssignment = async (req: Request, res: Response) => { const assignment = await FddAssignment.findOne({ where: { applicationId: application.id }, - include: [{ model: FddReport, as: 'reports' }] + include: [{ + model: FddReport, + as: 'reports', + include: [ + { model: db.User, as: 'submitter', attributes: ['fullName'] }, + { model: db.User, as: 'verifier', attributes: ['fullName'] } + ] + }] }); res.json({ success: true, data: assignment }); } catch (error) { @@ -76,25 +83,60 @@ export const assignAgency = async (req: AuthRequest, res: Response) => { export const uploadReport = async (req: AuthRequest, res: Response) => { try { - const { assignmentId, reportDocumentId, findings, recommendation } = req.body; + const { assignmentId, reportDocumentId, findings, recommendation, applicationId } = req.body; + let finalAssignmentId = assignmentId; + + // Auto-assign logic if assignmentId is missing + if (!finalAssignmentId && applicationId) { + const appId = 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(appId); + const application = await Application.findOne({ + where: isUUID ? { [Op.or]: [{ id: appId }, { applicationId: appId }] } : { applicationId: appId } + }); - const report = await FddReport.create({ - assignmentId, - reportDocumentId, - findings, - recommendation, - verifiedAt: new Date(), - verifiedBy: req.user?.id // Auto-verified by uploader for now? Or separate verify step? - }); + if (application) { + const [newAssignment] = await FddAssignment.findOrCreate({ + where: { applicationId: application.id }, + defaults: { + assignedToAgency: req.user?.id, + status: 'Assigned' + } + }); + finalAssignmentId = newAssignment.id; + } + } + + if (!finalAssignmentId) { + return res.status(400).json({ success: false, message: 'Assignment ID or valid Application ID is required' }); + } + + let report = await FddReport.findOne({ where: { assignmentId: finalAssignmentId } }); + + if (report) { + await report.update({ + reportDocumentId, + findings, + recommendation, + submittedBy: req.user?.id + }); + } else { + report = await FddReport.create({ + assignmentId: finalAssignmentId, + reportDocumentId, + findings, + recommendation, + submittedBy: req.user?.id + }); + } // Update Assignment Status await FddAssignment.update( { status: 'Report Submitted' }, - { where: { id: assignmentId } } + { where: { id: finalAssignmentId } } ); // Fetch application to transition - const assignment = await FddAssignment.findByPk(assignmentId); + const assignment = await FddAssignment.findByPk(finalAssignmentId); if (assignment) { const application = await Application.findByPk(assignment.applicationId); if (application) { diff --git a/src/modules/loa/loa.controller.ts b/src/modules/loa/loa.controller.ts index 0387e6c..86e079d 100644 --- a/src/modules/loa/loa.controller.ts +++ b/src/modules/loa/loa.controller.ts @@ -124,7 +124,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => { const finalDeposit = await SecurityDeposit.findOne({ where: { applicationId: request.applicationId, - depositType: 'FINAL', + depositType: 'FIRST_FILL', status: 'Verified' } }); @@ -132,7 +132,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => { if (!finalDeposit) { return res.status(400).json({ success: false, - message: `LOA Approval Blocked: The Final Security Deposit (₹15L) is either Pending or not found. Finance team must verify the payment before proceeding.` + message: `LOA Approval Blocked: The First Fill (₹15L) is either Pending or not found. Finance team must verify the payment before proceeding.` }); } } @@ -312,7 +312,7 @@ export const updateSecurityDeposit = async (req: AuthRequest, res: Response) => if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); let deposit = await SecurityDeposit.findOne({ - where: { applicationId: application.id, depositType: depositType || 'INITIAL' } + where: { applicationId: application.id, depositType: depositType || 'SECURITY_DEPOSIT' } }); if (deposit) { @@ -328,7 +328,7 @@ export const updateSecurityDeposit = async (req: AuthRequest, res: Response) => paymentReference, proofDocumentId, status: status || 'Pending', - depositType: depositType || 'INITIAL', + depositType: depositType || 'SECURITY_DEPOSIT', verifiedBy: status === 'Verified' ? req.user?.id : null, verifiedAt: status === 'Verified' ? new Date() : null }); @@ -341,18 +341,18 @@ export const updateSecurityDeposit = async (req: AuthRequest, res: Response) => // --- AUTOMATION: After verification transitions --- - // 1. If INITIAL Payment Verified -> Move to LOI Issue Stage - if ((depositType === 'INITIAL' || !depositType) && status === 'Verified') { - console.log(`[DEBUG] Initial Security Deposit verified. Moving to LOI Issued stage...`); + // 1. If SECURITY_DEPOSIT Payment Verified -> Move to LOI Issue Stage + if ((depositType === 'SECURITY_DEPOSIT' || !depositType) && status === 'Verified') { + console.log(`[DEBUG] Security Deposit verified. Moving to LOI Issued stage...`); await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOI_ISSUED, req.user?.id || null, { - reason: 'Initial Security Deposit verified. Proceeding to LOI Issuance.', + reason: 'Security Deposit verified. Proceeding to LOI Issuance.', stage: APPLICATION_STAGES.LOI, progressPercentage: 80 }); } - // 2. If FINAL Payment Verified -> Move to LOA Pending stage - if (depositType === 'FINAL' && status === 'Verified') { + // 2. If FIRST_FILL Payment Verified -> Move to LOA Pending stage + if (depositType === 'FIRST_FILL' && status === 'Verified') { // Ensure LoaRequest exists for the next step await db.LoaRequest.findOrCreate({ where: { applicationId: application.id }, @@ -360,7 +360,7 @@ export const updateSecurityDeposit = async (req: AuthRequest, res: Response) => }); await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOA_PENDING, req.user?.id || null, { - reason: 'Final Security Deposit Verified. Initiating LOA Approval stage.', + reason: 'First Fill Verified. Initiating LOA Approval stage.', progressPercentage: 90 }); } diff --git a/src/modules/loi/loi.controller.ts b/src/modules/loi/loi.controller.ts index 36d3fd1..bdcb181 100644 --- a/src/modules/loi/loi.controller.ts +++ b/src/modules/loi/loi.controller.ts @@ -244,7 +244,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => { // Create Initial Security Deposit record (Advance Payment) await db.SecurityDeposit.findOrCreate({ - where: { applicationId: request.applicationId, depositType: 'INITIAL' }, + where: { applicationId: request.applicationId, depositType: 'SECURITY_DEPOSIT' }, defaults: { amount: 200000, // 2 Lakhs Advance status: 'Pending' diff --git a/src/modules/onboarding/onboarding.controller.ts b/src/modules/onboarding/onboarding.controller.ts index 7786ff3..fd88a44 100644 --- a/src/modules/onboarding/onboarding.controller.ts +++ b/src/modules/onboarding/onboarding.controller.ts @@ -870,7 +870,7 @@ export const generateDealerCodes = async (req: AuthRequest, res: Response) => { // Create Final Security Deposit record (Blocker for LOA) await db.SecurityDeposit.findOrCreate({ - where: { applicationId: application.id, depositType: 'FINAL' }, + where: { applicationId: application.id, depositType: 'FIRST_FILL' }, defaults: { amount: 1500000, // 15 Lakhs Final status: 'Pending' diff --git a/src/modules/self-service/constitutional.controller.ts b/src/modules/self-service/constitutional.controller.ts index f70c8bd..8c85ee0 100644 --- a/src/modules/self-service/constitutional.controller.ts +++ b/src/modules/self-service/constitutional.controller.ts @@ -100,7 +100,29 @@ export const getRequestById = async (req: AuthRequest, res: Response) => { where: isUUID ? { [Op.or]: [{ id: idStr }, { requestId: idStr }] } : { requestId: idStr }, include: [ { model: Outlet, as: 'outlet' }, - { model: User, as: 'dealer', attributes: ['fullName', 'email'] }, + { + model: User, + as: 'dealer', + attributes: ['id', 'fullName', 'email', 'roleCode'], + include: [ + { + model: db.Dealer, + as: 'dealerProfile', + include: [ + { model: db.DealerCode, as: 'dealerCode' }, + { + model: db.Application, + as: 'application', + include: [ + { model: db.District, as: 'district' }, + { model: db.LoiRequest, as: 'loiRequests', where: { status: 'approved' }, required: false }, + { model: db.LoaRequest, as: 'loaRequests', where: { status: 'approved' }, required: false } + ] + } + ] + } + ] + }, { model: Worknote, as: 'worknotes' }, { model: db.RequestParticipant, diff --git a/src/modules/self-service/resignation.controller.ts b/src/modules/self-service/resignation.controller.ts index 084baaa..f646de5 100644 --- a/src/modules/self-service/resignation.controller.ts +++ b/src/modules/self-service/resignation.controller.ts @@ -120,7 +120,29 @@ export const getResignationById = async (req: AuthRequest, res: Response, next: where: isUUID ? { [Op.or]: [{ id: idStr }, { resignationId: idStr }] } : { resignationId: idStr }, include: [ { model: db.Outlet, as: 'outlet' }, - { model: db.User, as: 'dealer', attributes: ['id', 'fullName', 'email'] }, + { + model: db.User, + as: 'dealer', + attributes: ['id', 'fullName', 'email', 'roleCode'], + include: [ + { + model: db.Dealer, + as: 'dealerProfile', + include: [ + { model: db.DealerCode, as: 'dealerCode' }, + { + model: db.Application, + as: 'application', + include: [ + { model: db.District, as: 'district' }, + { model: db.LoiRequest, as: 'loiRequests', where: { status: 'approved' }, required: false }, + { model: db.LoaRequest, as: 'loaRequests', where: { status: 'approved' }, required: false } + ] + } + ] + } + ] + }, { model: db.ResignationDocument, as: 'uploadedDocuments', diff --git a/src/modules/termination/termination.controller.ts b/src/modules/termination/termination.controller.ts index 1aa9d44..9bb6101 100644 --- a/src/modules/termination/termination.controller.ts +++ b/src/modules/termination/termination.controller.ts @@ -2,7 +2,7 @@ import { Response, NextFunction } from 'express'; import db from '../../database/models/index.js'; import logger from '../../common/utils/logger.js'; import { TERMINATION_STAGES, AUDIT_ACTIONS, ROLES } from '../../common/config/constants.js'; -import { Transaction } from 'sequelize'; +import { Op, Transaction } from 'sequelize'; import { AuthRequest } from '../../types/express.types.js'; import ExternalMocksService from '../../common/utils/externalMocks.service.js'; @@ -94,20 +94,36 @@ export const getTerminations = async (req: AuthRequest, res: Response, next: Nex export const getTerminationById = async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { id } = req.params; - const termination = await db.TerminationRequest.findByPk(id, { + const idStr = String(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(idStr); + + const termination = await db.TerminationRequest.findOne({ + where: isUUID ? { [Op.or]: [{ id: idStr }, { requestId: idStr }] } : { requestId: idStr }, include: [ { model: db.Dealer, as: 'dealer', include: [ - { model: db.DealerCode, as: 'dealerCode' }, { model: db.Application, as: 'application', include: [ - { model: db.District, as: 'district' } + { model: db.District, as: 'district' }, + { + model: db.LoiRequest, + as: 'loiRequests', + where: { status: 'approved' }, + required: false + }, + { + model: db.LoaRequest, + as: 'loaRequests', + where: { status: 'approved' }, + required: false + } ] - } + }, + { model: db.DealerCode, as: 'dealerCode' } ] }, { model: db.User, as: 'initiator', attributes: ['id', 'fullName', 'email'] }, diff --git a/src/services/ParticipantService.ts b/src/services/ParticipantService.ts index 209769e..17f03a3 100644 --- a/src/services/ParticipantService.ts +++ b/src/services/ParticipantService.ts @@ -81,18 +81,27 @@ export class ParticipantService { const termination = await TerminationRequest.findByPk(terminationId); if (!termination) return; - // TerminationRequest already uses Dealer ID - const managers = await this.getDealerLocationManagers(termination.dealerId); const participantIds = new Set(); + // 0. The Dealer (Requester) should be a participant + if (termination.dealerId) { + // Find user account for this dealer + const dealerUser = await User.findOne({ where: { dealerId: termination.dealerId } }); + if (dealerUser) participantIds.add(dealerUser.id); + } + + // The Initiator (Admin who started termination) + if (termination.initiatedBy) participantIds.add(termination.initiatedBy); + // 1. Location based managers + const managers = await this.getDealerLocationManagers(termination.dealerId); if (managers) { if (managers.rbmId) participantIds.add(managers.rbmId); if (managers.zbhId) participantIds.add(managers.zbhId); } - // 2. National roles - const nationalRoles = [ROLES.DD_LEAD, ROLES.NBH, ROLES.CCO, ROLES.CEO, ROLES.LEGAL_ADMIN]; + // 2. National roles - Crucial for Termination Review + const nationalRoles = [ROLES.DD_LEAD, ROLES.NBH, ROLES.CCO, ROLES.CEO, ROLES.LEGAL_ADMIN, ROLES.SUPER_ADMIN]; const nationalUsers = await User.findAll({ where: { roleCode: { [Op.in]: nationalRoles }, @@ -104,11 +113,13 @@ export class ParticipantService { nationalUsers.forEach((u: any) => participantIds.add(u.id)); // 3. Add all unique participants + let addedCount = 0; for (const userId of participantIds) { await this.addParticipant(termination.id, REQUEST_TYPES.TERMINATION, userId); + addedCount++; } - console.log(`[ParticipantService] Added ${participantIds.size} participants to termination ${terminationId}`); + console.log(`[ParticipantService] Added ${addedCount} participants to termination ${terminationId}`); } catch (error) { console.error('Error assigning termination participants:', error); } @@ -122,26 +133,26 @@ export class ParticipantService { const request = await ConstitutionalChange.findByPk(requestId); if (!request) return; - // In ConstitutionalChange model, dealerId is the User ID - const user = await User.findByPk(request.dealerId); - if (!user || !user.dealerId) { - console.error(`[ParticipantService] No Dealer ID found for user ${request.dealerId}`); - return; - } - - const managers = await this.getDealerLocationManagers(user.dealerId); const participantIds = new Set(); - // 1. Location based managers - if (managers) { - if (managers.asmId) participantIds.add(managers.asmId); - if (managers.zmId) participantIds.add(managers.zmId); - if (managers.rbmId) participantIds.add(managers.rbmId); - if (managers.zbhId) participantIds.add(managers.zbhId); + // 0. The Dealer (Requester) should be a participant + if (request.dealerId) participantIds.add(request.dealerId); + + // In ConstitutionalChange model, dealerId is the User ID + const user = await User.findByPk(request.dealerId); + if (user && user.dealerId) { + const managers = await this.getDealerLocationManagers(user.dealerId); + // 1. Location based managers + if (managers) { + if (managers.asmId) participantIds.add(managers.asmId); + if (managers.zmId) participantIds.add(managers.zmId); + if (managers.rbmId) participantIds.add(managers.rbmId); + if (managers.zbhId) participantIds.add(managers.zbhId); + } } // 2. National roles - const nationalRoles = [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.NBH, ROLES.LEGAL_ADMIN]; + const nationalRoles = [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.NBH, ROLES.LEGAL_ADMIN, ROLES.SUPER_ADMIN]; const nationalUsers = await User.findAll({ where: { roleCode: { [Op.in]: nationalRoles }, @@ -153,11 +164,13 @@ export class ParticipantService { nationalUsers.forEach((u: any) => participantIds.add(u.id)); // 3. Add all unique participants + let addedCount = 0; for (const userId of participantIds) { await this.addParticipant(request.id, REQUEST_TYPES.CONSTITUTIONAL, userId); + addedCount++; } - console.log(`[ParticipantService] Added ${participantIds.size} participants to constitutional change ${requestId}`); + console.log(`[ParticipantService] Added ${addedCount} participants to constitutional change ${requestId}`); } catch (error) { console.error('Error assigning constitutional participants:', error); } @@ -169,27 +182,43 @@ export class ParticipantService { static async assignResignationParticipants(requestId: string) { try { const resignation = await db.Resignation.findByPk(requestId); - if (!resignation) return; - - // In Resignation model, dealerId is the User ID - const user = await User.findByPk(resignation.dealerId); - if (!user || !user.dealerId) { - console.error(`[ParticipantService] No Dealer ID found for user ${resignation.dealerId}`); + if (!resignation) { + console.error(`[ParticipantService] Resignation not found: ${requestId}`); return; } - const managers = await this.getDealerLocationManagers(user.dealerId); const participantIds = new Set(); - - // 1. Location based managers - if (managers) { - if (managers.asmId) participantIds.add(managers.asmId); - if (managers.rbmId) participantIds.add(managers.rbmId); - if (managers.zbhId) participantIds.add(managers.zbhId); + + // 0. The Dealer themselves (Requester) should be a participant + if (resignation.dealerId) { + participantIds.add(resignation.dealerId); } - // 2. National roles - const nationalRoles = [ROLES.DD_LEAD, ROLES.NBH, ROLES.DD_ADMIN, ROLES.FINANCE, ROLES.LEGAL_ADMIN]; + // In Resignation model, dealerId is the User ID + const user = await User.findByPk(resignation.dealerId); + + // 1. Try to get Location based managers if dealer profile exists + if (user && user.dealerId) { + const managers = await this.getDealerLocationManagers(user.dealerId); + if (managers) { + if (managers.asmId) participantIds.add(managers.asmId); + if (managers.rbmId) participantIds.add(managers.rbmId); + if (managers.zbhId) participantIds.add(managers.zbhId); + } + } else { + console.warn(`[ParticipantService] No Dealer Profile link found for user ${resignation.dealerId}. Only adding national roles.`); + } + + // 2. National roles - Essential for workflow transparency + const nationalRoles = [ + ROLES.DD_LEAD, + ROLES.NBH, + ROLES.DD_ADMIN, + ROLES.FINANCE, + ROLES.LEGAL_ADMIN, + ROLES.SUPER_ADMIN // Added Super Admin as observer + ]; + const nationalUsers = await User.findAll({ where: { roleCode: { [Op.in]: nationalRoles }, @@ -201,11 +230,15 @@ export class ParticipantService { nationalUsers.forEach((u: any) => participantIds.add(u.id)); // 3. Add all unique participants + let addedCount = 0; for (const userId of participantIds) { - await this.addParticipant(resignation.id, REQUEST_TYPES.RESIGNATION, userId); + // Dealer gets 'owner' type, others get 'contributor' + const pType = userId === resignation.dealerId ? 'owner' : 'contributor'; + await this.addParticipant(resignation.id, REQUEST_TYPES.RESIGNATION, userId, pType); + addedCount++; } - console.log(`[ParticipantService] Added ${participantIds.size} participants to resignation ${requestId}`); + console.log(`[ParticipantService] Added ${addedCount} participants to resignation ${requestId}`); } catch (error) { console.error('Error assigning resignation participants:', error); } diff --git a/sync_participants.ts b/sync_participants.ts new file mode 100644 index 0000000..d3a2dd4 --- /dev/null +++ b/sync_participants.ts @@ -0,0 +1,30 @@ + +import db from './src/database/models/index.js'; +import { ParticipantService } from './src/services/ParticipantService'; + +async function syncAll() { + try { + const terminations = await db.TerminationRequest.findAll(); + console.log(`Found ${terminations.length} terminations to sync...`); + + for (const term of terminations) { + console.log(`Mapping participants for ${term.requestId} (${term.id})...`); + await ParticipantService.assignTerminationParticipants(term.id); + } + + const changes = await db.ConstitutionalChange.findAll(); + console.log(`Found ${changes.length} constitutional changes to sync...`); + for (const change of changes) { + console.log(`Mapping participants for ${change.requestId} (${change.id})...`); + await ParticipantService.assignConstitutionalParticipants(change.id); + } + + console.log('Sync completed.'); + process.exit(0); + } catch (error) { + console.error('Sync failed:', error); + process.exit(1); + } +} + +syncAll(); diff --git a/trigger-resignation.js b/trigger-resignation.js index 2c53859..d730bf3 100644 --- a/trigger-resignation.js +++ b/trigger-resignation.js @@ -31,7 +31,12 @@ async function apiRequest(endpoint, method = 'GET', body = null, token = null) { } async function login(email) { - const data = await apiRequest('/auth/login', 'POST', { email, password: (email === 'dealer@royalenfield.com' ? 'Admin@123' : PASSWORD) }); + const isInternal = email.endsWith('@royalenfield.com') || + email === 'lince@gmail.com' || + email === 'yashwin@gmail.com'; + const password = isInternal ? 'Admin@123' : 'Dealer@123'; + + const data = await apiRequest('/auth/login', 'POST', { email, password }); return data.token; } diff --git a/trigger-workflow.js b/trigger-workflow.js index 012ee2c..b815512 100644 --- a/trigger-workflow.js +++ b/trigger-workflow.js @@ -94,7 +94,7 @@ async function prospectLogin(phone) { async function mockUploadDocument(appId, token, docType) { const formData = new FormData(); - const fileBuffer = fs.readFileSync('C:/Users/BACKPACKERS/Pictures/claim_document_type.PNG'); + const fileBuffer = fs.readFileSync('/home/laxman-h/Pictures/Screenshots/Screenshot from 2026-03-26 10-08-00.png'); const blob = new Blob([fileBuffer], { type: 'image/png' }); formData.append('file', blob, 'screenshot.png'); formData.append('documentType', docType); @@ -364,17 +364,17 @@ async function triggerWorkflow() { applicationId: applicationUUID, amount: 500000, paymentReference: 'PAY-888999', - depositType: 'INITIAL', + depositType: 'SECURITY_DEPOSIT', status: 'Verified' }, financeToken); - log(9, 'Initial Security Deposit Verified.'); + log(9, 'Security Deposit Verified.'); - log(9.1, 'Finance Verifying FINAL Security Deposit (₹15L)...'); + log(9.1, 'Finance Verifying FIRST FILL (₹15L)...'); await apiRequest('/loa/security-deposit', 'POST', { applicationId: applicationUUID, amount: 1500000, paymentReference: 'PAY-FIN-999', - depositType: 'FINAL', + depositType: 'FIRST_FILL', status: 'Verified' }, financeToken); log(9.1, 'Final Security Deposit Verified.'); @@ -383,14 +383,14 @@ async function triggerWorkflow() { // 10. FINAL LOA APPROVAL log(10, 'NBH & Head Approving Final LOA...'); const loaRes = await apiRequest('/loa/request', 'POST', { applicationId: applicationUUID }, headToken); - loaRequestId = loaRes.data.id; + const finalLoaRequestId = loaRes.data.id; - await apiRequest(`/loa/request/${loaRequestId}/approve`, 'POST', { + await apiRequest(`/loa/request/${finalLoaRequestId}/approve`, 'POST', { action: 'Approved', remarks: 'Head Authorization (Level 1)' }, headToken); - await apiRequest(`/loa/request/${loaRequestId}/approve`, 'POST', { + await apiRequest(`/loa/request/${finalLoaRequestId}/approve`, 'POST', { action: 'Approved', remarks: 'NBH Approval (Level 2)' }, nbhToken);