end to end flow testing for all modules paralley enhancing profile schema to gather all info
This commit is contained in:
parent
934fd7a907
commit
19c766c999
@ -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();
|
||||
|
||||
@ -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();
|
||||
@ -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;
|
||||
|
||||
@ -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>, 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',
|
||||
|
||||
@ -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>, 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;
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -150,7 +150,8 @@ export default (sequelize: Sequelize) => {
|
||||
Resignation.hasMany(models.RequestParticipant, {
|
||||
foreignKey: 'requestId',
|
||||
as: 'participants',
|
||||
scope: { requestType: 'resignation' }
|
||||
scope: { requestType: 'resignation' },
|
||||
constraints: false
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -114,7 +114,8 @@ export default (sequelize: Sequelize) => {
|
||||
TerminationRequest.hasMany(models.RequestParticipant, {
|
||||
foreignKey: 'requestId',
|
||||
as: 'participants',
|
||||
scope: { requestType: 'termination' }
|
||||
scope: { requestType: 'termination' },
|
||||
constraints: false
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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' });
|
||||
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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'] },
|
||||
|
||||
@ -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<string>();
|
||||
|
||||
// 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<string>();
|
||||
|
||||
// 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<string>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
30
sync_participants.ts
Normal file
30
sync_participants.ts
Normal file
@ -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();
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user