247 lines
9.1 KiB
TypeScript
247 lines
9.1 KiB
TypeScript
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;
|
|
|
|
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 - Crucial for Termination Review
|
|
const nationalRoles = [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.NBH, ROLES.CCO, ROLES.CEO, ROLES.LEGAL_ADMIN, ROLES.SUPER_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
|
|
let addedCount = 0;
|
|
for (const userId of participantIds) {
|
|
await this.addParticipant(termination.id, REQUEST_TYPES.TERMINATION, userId);
|
|
addedCount++;
|
|
}
|
|
|
|
console.log(`[ParticipantService] Added ${addedCount} 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;
|
|
|
|
const participantIds = new Set<string>();
|
|
|
|
// 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, ROLES.SUPER_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
|
|
let addedCount = 0;
|
|
for (const userId of participantIds) {
|
|
await this.addParticipant(request.id, REQUEST_TYPES.CONSTITUTIONAL, userId);
|
|
addedCount++;
|
|
}
|
|
|
|
console.log(`[ParticipantService] Added ${addedCount} 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) {
|
|
console.error(`[ParticipantService] Resignation not found: ${requestId}`);
|
|
return;
|
|
}
|
|
|
|
const participantIds = new Set<string>();
|
|
|
|
// 0. The Dealer themselves (Requester) should be a participant
|
|
if (resignation.dealerId) {
|
|
participantIds.add(resignation.dealerId);
|
|
}
|
|
|
|
// 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 },
|
|
status: 'active'
|
|
},
|
|
attributes: ['id']
|
|
});
|
|
|
|
nationalUsers.forEach((u: any) => participantIds.add(u.id));
|
|
|
|
// 3. Add all unique participants
|
|
let addedCount = 0;
|
|
for (const userId of participantIds) {
|
|
// 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 ${addedCount} participants to resignation ${requestId}`);
|
|
} catch (error) {
|
|
console.error('Error assigning resignation participants:', error);
|
|
}
|
|
}
|
|
}
|