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(); // 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(); // 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(); // 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); } } }