participants mappig done for other modules

This commit is contained in:
laxmanhalaki 2026-04-10 09:20:04 +05:30
parent bd7bdef46f
commit 934fd7a907
17 changed files with 424 additions and 5 deletions

BIN
all_ids.txt Normal file

Binary file not shown.

16
drop_constraint.ts Normal file
View File

@ -0,0 +1,16 @@
import 'dotenv/config';
import db from './src/database/models/index.js';
async function run() {
try {
console.log('Attempting to drop constraint request_participants_requestId_fkey...');
await db.sequelize.query('ALTER TABLE request_participants DROP CONSTRAINT IF EXISTS "request_participants_requestId_fkey"');
console.log('Constraint dropped successfully.');
process.exit(0);
} catch (error) {
console.error('Error dropping constraint:', error);
process.exit(1);
}
}
run();

View File

@ -0,0 +1,32 @@
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();

23
quick_query.ts Normal file
View File

@ -0,0 +1,23 @@
import 'dotenv/config';
import db from './src/database/models/index.js';
async function run() {
try {
const participants = await db.RequestParticipant.findAll({
limit: 100,
include: [{ model: db.User, as: 'user', attributes: ['fullName', 'roleCode', 'email'] }]
});
console.log(`Found ${participants.length} participants in total:`);
participants.forEach((p: any) => {
console.log(`- RequestId: ${p.requestId}, Type: ${p.requestType}, User: ${p.user.fullName}`);
});
process.exit(0);
} catch (error) {
console.error('Error querying participants:', error);
process.exit(1);
}
}
run();

View File

@ -408,7 +408,8 @@ export const REQUEST_TYPES = {
APPLICATION: 'application',
RESIGNATION: 'resignation',
CONSTITUTIONAL: 'constitutional',
RELOCATION: 'relocation'
RELOCATION: 'relocation',
TERMINATION: 'termination'
} as const;
// Module List for Document Management

View File

@ -123,6 +123,11 @@ export default (sequelize: Sequelize) => {
scope: { requestType: 'constitutional' },
constraints: false
});
ConstitutionalChange.hasMany(models.RequestParticipant, {
foreignKey: 'requestId',
as: 'participants',
scope: { requestType: 'constitutional' }
});
};
return ConstitutionalChange;

View File

@ -147,6 +147,11 @@ export default (sequelize: Sequelize) => {
foreignKey: 'resignationId',
as: 'settlement'
});
Resignation.hasMany(models.RequestParticipant, {
foreignKey: 'requestId',
as: 'participants',
scope: { requestType: 'resignation' }
});
};
return Resignation;

View File

@ -111,6 +111,11 @@ export default (sequelize: Sequelize) => {
scope: { requestType: 'termination' },
constraints: false
});
TerminationRequest.hasMany(models.RequestParticipant, {
foreignKey: 'requestId',
as: 'participants',
scope: { requestType: 'termination' }
});
};
return TerminationRequest;

View File

@ -6,6 +6,7 @@ import { AuthRequest } from '../../types/express.types.js';
import { CONSTITUTIONAL_STAGES, AUDIT_ACTIONS, ROLES } from '../../common/config/constants.js';
import { ConstitutionalWorkflowService } from '../../services/ConstitutionalWorkflowService.js';
import { NomenclatureService } from '../../common/utils/nomenclature.js';
import { ParticipantService } from '../../services/ParticipantService.js';
export const submitRequest = async (req: AuthRequest, res: Response) => {
try {
@ -49,6 +50,10 @@ export const submitRequest = async (req: AuthRequest, res: Response) => {
entityId: request.id
});
// Add as chat participants (Async)
ParticipantService.assignConstitutionalParticipants(request.id)
.catch(err => console.error('Error assigning participants to constitutional change:', err));
res.status(201).json({
success: true,
message: 'Constitutional change request submitted successfully',
@ -96,7 +101,12 @@ export const getRequestById = async (req: AuthRequest, res: Response) => {
include: [
{ model: Outlet, as: 'outlet' },
{ model: User, as: 'dealer', attributes: ['fullName', 'email'] },
{ model: Worknote, as: 'worknotes' }
{ model: Worknote, as: 'worknotes' },
{
model: db.RequestParticipant,
as: 'participants',
include: [{ model: User, as: 'user', attributes: ['id', 'fullName', 'email', 'roleCode'] }]
}
]
});

View File

@ -8,6 +8,7 @@ import ExternalMocksService from '../../common/utils/externalMocks.service.js';
import { ResignationWorkflowService } from '../../services/ResignationWorkflowService.js';
import { NomenclatureService } from '../../common/utils/nomenclature.js';
import { ParticipantService } from '../../services/ParticipantService.js';
// Removed generateResignationId and moved to NomenclatureService
@ -73,6 +74,11 @@ export const createResignation = async (req: AuthRequest, res: Response, next: N
await transaction.commit();
logger.info(`Resignation request created: ${resignationId} by dealer: ${req.user.email}`);
// Add as chat participants (Async)
ParticipantService.assignResignationParticipants(resignation.id)
.catch(err => logger.error('Error assigning participants to resignation:', err));
res.status(201).json({ success: true, message: 'Resignation request submitted successfully', resignationId, resignation });
} catch (error) {
if (transaction) await transaction.rollback();
@ -127,6 +133,11 @@ export const getResignationById = async (req: AuthRequest, res: Response, next:
{ model: db.FnFLineItem, as: 'lineItems' },
{ model: db.FffClearance, as: 'clearances' }
]
},
{
model: db.RequestParticipant,
as: 'participants',
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email', 'roleCode'] }]
}
]
});
@ -505,3 +516,33 @@ export const updateClearance = async (req: AuthRequest, res: Response, next: Nex
next(error);
}
};
// Unified status update handler for frontend compatibility
export const updateResignationStatus = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const { action } = req.body;
switch (action) {
case 'approve':
return approveResignation(req, res, next);
case 'reject':
return rejectResignation(req, res, next);
case 'withdrawal':
case 'withdraw':
return withdrawResignation(req, res, next);
case 'sendback':
return sendBackResignation(req, res, next);
case 'pushfnf':
// For push to F&F, we can trigger the same logic as Legal -> FNF_INITIATED approve
// But specifically for the pushfnf button action
return approveResignation(req, res, next);
default:
return res.status(400).json({
success: false,
message: `Invalid or unsupported resignation action: ${action}`
});
}
} catch (error) {
logger.error('Error in updateResignationStatus:', error);
next(error);
}
};

View File

@ -10,6 +10,7 @@ router.post('/', authenticate as any, resignationController.createResignation);
router.get('/', authenticate as any, resignationController.getResignations);
router.get('/:id', authenticate as any, resignationController.getResignationById);
router.put('/:id/approve', authenticate as any, resignationController.approveResignation);
router.post('/:id/status', authenticate as any, resignationController.updateResignationStatus);
router.put('/:id/reject', authenticate as any, resignationController.rejectResignation);
router.put('/:id/withdraw', authenticate as any, resignationController.withdrawResignation);
router.put('/:id/sendback', authenticate as any, resignationController.sendBackResignation);

View File

@ -8,6 +8,7 @@ import ExternalMocksService from '../../common/utils/externalMocks.service.js';
import { TerminationWorkflowService } from '../../services/TerminationWorkflowService.js';
import { NomenclatureService } from '../../common/utils/nomenclature.js';
import { ParticipantService } from '../../services/ParticipantService.js';
// Create termination request
export const createTermination = async (req: AuthRequest, res: Response, next: NextFunction) => {
@ -45,6 +46,11 @@ export const createTermination = async (req: AuthRequest, res: Response, next: N
}, { transaction });
await transaction.commit();
// Add as chat participants (Async)
ParticipantService.assignTerminationParticipants(termination.id)
.catch(err => logger.error('Error assigning participants to termination:', err));
res.status(201).json({ success: true, message: 'Termination request created', termination });
} catch (error) {
if (transaction) await transaction.rollback();
@ -110,7 +116,12 @@ export const getTerminationById = async (req: AuthRequest, res: Response, next:
as: 'uploadedDocuments',
include: [{ model: db.User, as: 'uploader', attributes: ['fullName'] }]
},
{ model: db.FnF, as: 'fnfSettlement' }
{ model: db.FnF, as: 'fnfSettlement' },
{
model: db.RequestParticipant,
as: 'participants',
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email', 'roleCode'] }]
}
]
});

View File

@ -12,6 +12,7 @@ router.post('/', createTermination);
router.get('/', getTerminations);
router.get('/:id', getTerminationById);
router.put('/:id/status', updateTerminationStatus);
router.post('/:id/status', updateTerminationStatus);
router.post('/scn-response', submitScnResponse);
router.post('/hearing-record', recordPersonalHearing);

View File

@ -0,0 +1,213 @@
import db from '../database/models/index.js';
import { ROLES, REQUEST_TYPES } from '../common/config/constants.js';
import { Op } from 'sequelize';
const {
RequestParticipant,
User,
TerminationRequest,
ConstitutionalChange,
Dealer,
Application,
District,
Region,
Zone
} = db;
export class ParticipantService {
/**
* Common helper to add a participant
*/
private static async addParticipant(requestId: string, requestType: string, userId: string, participantType: string = 'contributor', metadata: any = {}) {
try {
await RequestParticipant.findOrCreate({
where: {
requestId,
requestType,
userId
},
defaults: {
participantType,
joinedMethod: 'auto',
metadata: {
...metadata,
autoMapped: true,
assignedAt: new Date()
}
}
});
} catch (error) {
console.error(`Error adding participant ${userId} to ${requestType} ${requestId}:`, error);
}
}
/**
* Resolves location-based managers for a dealer
*/
private static async getDealerLocationManagers(dealerId: string) {
const dealer = await Dealer.findByPk(dealerId, {
include: [{
model: Application,
as: 'application',
include: [{
model: District,
as: 'district',
include: [
{ model: Region, as: 'region' },
{ model: Zone, as: 'zone' }
]
}]
}]
});
if (!dealer || !dealer.application || !dealer.application.district) {
return null;
}
const district = dealer.application.district;
return {
asmId: district.asmId,
zmId: district.zmId,
rbmId: district.region?.rbmId,
zbhId: district.zone?.zbhId
};
}
/**
* Assign participants for Termination Request
*/
static async assignTerminationParticipants(terminationId: string) {
try {
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>();
// 1. Location based managers
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];
const nationalUsers = await User.findAll({
where: {
roleCode: { [Op.in]: nationalRoles },
status: 'active'
},
attributes: ['id']
});
nationalUsers.forEach((u: any) => participantIds.add(u.id));
// 3. Add all unique participants
for (const userId of participantIds) {
await this.addParticipant(termination.id, REQUEST_TYPES.TERMINATION, userId);
}
console.log(`[ParticipantService] Added ${participantIds.size} participants to termination ${terminationId}`);
} catch (error) {
console.error('Error assigning termination participants:', error);
}
}
/**
* Assign participants for Constitutional Change Request
*/
static async assignConstitutionalParticipants(requestId: string) {
try {
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);
}
// 2. National roles
const nationalRoles = [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.NBH, ROLES.LEGAL_ADMIN];
const nationalUsers = await User.findAll({
where: {
roleCode: { [Op.in]: nationalRoles },
status: 'active'
},
attributes: ['id']
});
nationalUsers.forEach((u: any) => participantIds.add(u.id));
// 3. Add all unique participants
for (const userId of participantIds) {
await this.addParticipant(request.id, REQUEST_TYPES.CONSTITUTIONAL, userId);
}
console.log(`[ParticipantService] Added ${participantIds.size} participants to constitutional change ${requestId}`);
} catch (error) {
console.error('Error assigning constitutional participants:', error);
}
}
/**
* Assign participants for Resignation Request
*/
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}`);
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);
}
// 2. National roles
const nationalRoles = [ROLES.DD_LEAD, ROLES.NBH, ROLES.DD_ADMIN, ROLES.FINANCE, ROLES.LEGAL_ADMIN];
const nationalUsers = await User.findAll({
where: {
roleCode: { [Op.in]: nationalRoles },
status: 'active'
},
attributes: ['id']
});
nationalUsers.forEach((u: any) => participantIds.add(u.id));
// 3. Add all unique participants
for (const userId of participantIds) {
await this.addParticipant(resignation.id, REQUEST_TYPES.RESIGNATION, userId);
}
console.log(`[ParticipantService] Added ${participantIds.size} participants to resignation ${requestId}`);
} catch (error) {
console.error('Error assigning resignation participants:', error);
}
}
}

View File

@ -94,7 +94,7 @@ async function prospectLogin(phone) {
async function mockUploadDocument(appId, token, docType) {
const formData = new FormData();
const fileBuffer = fs.readFileSync('/home/laxman-h/Pictures/Screenshots/Screenshot from 2026-03-27 09-48-22.png');
const fileBuffer = fs.readFileSync('C:/Users/BACKPACKERS/Pictures/claim_document_type.PNG');
const blob = new Blob([fileBuffer], { type: 'image/png' });
formData.append('file', blob, 'screenshot.png');
formData.append('documentType', docType);
@ -410,7 +410,7 @@ async function triggerWorkflow() {
// 12. FINAL ONBOARDING
log(12, 'Admin Finalizing Dealer Onboarding...');
await apiRequest('/dealers', 'POST', { applicationId: applicationUUID }, adminToken);
log(12, '--- WORKFLOW COMPLETED SUCCESSFULLY! ---');
log(12, `The application ${applicationId} is now at 'ONBOARDED' status.`);
}

BIN
verify_output.txt Normal file

Binary file not shown.

View File

@ -0,0 +1,55 @@
import db from './src/database/models/index.js';
import { ParticipantService } from './src/services/ParticipantService.js';
async function verify() {
try {
console.log('--- Verification Started ---');
// 1. Verify Termination Participants
const termination = await db.TerminationRequest.findOne();
if (termination) {
console.log(`Found termination ${termination.id}. Assigning participants...`);
await ParticipantService.assignTerminationParticipants(termination.id);
const participants = await db.RequestParticipant.findAll({
where: { requestId: termination.id, requestType: 'termination' }
});
console.log(`Termination Participants added: ${participants.length}`);
} else {
console.log('No termination records found to verify.');
}
// 2. Verify Constitutional Participants
const constitutional = await db.ConstitutionalChange.findOne();
if (constitutional) {
console.log(`Found constitutional change ${constitutional.id}. Assigning participants...`);
await ParticipantService.assignConstitutionalParticipants(constitutional.id);
const participants = await db.RequestParticipant.findAll({
where: { requestId: constitutional.id, requestType: 'constitutional' }
});
console.log(`Constitutional Participants added: ${participants.length}`);
} else {
console.log('No constitutional change records found to verify.');
}
// 3. Verify Resignation Participants
const resignation = await db.Resignation.findOne();
if (resignation) {
console.log(`Found resignation ${resignation.id}. Assigning participants...`);
await ParticipantService.assignResignationParticipants(resignation.id);
const participants = await db.RequestParticipant.findAll({
where: { requestId: resignation.id, requestType: 'resignation' }
});
console.log(`Resignation Participants added: ${participants.length}`);
} else {
console.log('No resignation records found to verify.');
}
console.log('--- Verification Completed ---');
process.exit(0);
} catch (error) {
console.error('Verification failed:', error);
process.exit(1);
}
}
verify();