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 { Op, Transaction } from 'sequelize'; import { AuthRequest } from '../../types/express.types.js'; 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) => { const transaction: Transaction = await db.sequelize.transaction(); try { if (!req.user) throw new Error('Unauthorized'); const { dealerId, category, reason, proposedLwd, comments } = req.body; const requestId = NomenclatureService.generateTerminationId(); const termination = await db.TerminationRequest.create({ requestId, dealerId, category, reason, proposedLwd, comments, initiatedBy: req.user.id, currentStage: TERMINATION_STAGES.SUBMITTED, status: 'Submitted', progressPercentage: TerminationWorkflowService.calculateProgress(TERMINATION_STAGES.SUBMITTED), timeline: [{ stage: 'Submitted', timestamp: new Date(), user: req.user.fullName, action: 'Termination request initiated', remarks: comments }] }, { transaction }); await db.AuditLog.create({ userId: req.user.id, action: AUDIT_ACTIONS.CREATED, entityType: 'termination', entityId: termination.id }, { 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(); logger.error('Error creating termination:', error); next(error); } }; // Get all termination requests export const getTerminations = async (req: AuthRequest, res: Response, next: NextFunction) => { try { if (!req.user) throw new Error('Unauthorized'); const { dealerId } = req.query; const where: any = {}; if (dealerId) where.dealerId = dealerId; if (req.user.roleCode === ROLES.DEALER) { const dealer = await db.Dealer.findOne({ where: { userId: req.user.id } }); if (dealer) where.dealerId = dealer.id; } const terminations = await db.TerminationRequest.findAll({ where, include: [ { model: db.Dealer, as: 'dealer', include: [{ model: db.DealerCode, as: 'dealerCode' }] } ], order: [['createdAt', 'DESC']] }); res.json({ success: true, terminations }); } catch (error) { logger.error('Error fetching terminations:', error); next(error); } }; // Get termination request by ID export const getTerminationById = async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { id } = req.params; 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.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.DealerCode, as: 'dealerCode' } ] }, { model: db.User, as: 'initiator', attributes: ['id', 'fullName', 'email'] }, { model: db.TerminationDocument, as: 'uploadedDocuments', include: [{ model: db.User, as: 'uploader', attributes: ['fullName'] }] }, { model: db.FnF, as: 'fnfSettlement' }, { model: db.RequestParticipant, as: 'participants', include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email', 'roleCode'] }] } ] }); if (!termination) { return res.status(404).json({ success: false, message: 'Termination request not found' }); } res.json({ success: true, termination }); } catch (error) { logger.error('Error fetching termination:', error); next(error); } }; // Update termination status (Approve/Reject) export const updateTerminationStatus = async (req: AuthRequest, res: Response, next: NextFunction) => { const transaction: Transaction = await db.sequelize.transaction(); try { if (!req.user) throw new Error('Unauthorized'); const { id } = req.params; const { action, remarks } = req.body; const termination = await db.TerminationRequest.findByPk(id); if (!termination) { await transaction.rollback(); return res.status(404).json({ success: false, message: 'Termination not found' }); } if (action === 'reject') { await TerminationWorkflowService.transitionTermination(termination, TERMINATION_STAGES.REJECTED, req.user.id, { action: 'Rejected', status: 'Rejected', remarks }); } else { const stageFlow: Record = { [TERMINATION_STAGES.SUBMITTED]: TERMINATION_STAGES.RBM_REVIEW, [TERMINATION_STAGES.RBM_REVIEW]: TERMINATION_STAGES.ZBH_REVIEW, [TERMINATION_STAGES.ZBH_REVIEW]: TERMINATION_STAGES.DD_LEAD_REVIEW, [TERMINATION_STAGES.DD_LEAD_REVIEW]: TERMINATION_STAGES.LEGAL_VERIFICATION, [TERMINATION_STAGES.LEGAL_VERIFICATION]: TERMINATION_STAGES.NBH_EVALUATION, [TERMINATION_STAGES.NBH_EVALUATION]: TERMINATION_STAGES.SCN_ISSUED, [TERMINATION_STAGES.SCN_ISSUED]: TERMINATION_STAGES.PERSONAL_HEARING, [TERMINATION_STAGES.PERSONAL_HEARING]: TERMINATION_STAGES.NBH_FINAL_APPROVAL, [TERMINATION_STAGES.NBH_FINAL_APPROVAL]: TERMINATION_STAGES.CCO_APPROVAL, [TERMINATION_STAGES.CCO_APPROVAL]: TERMINATION_STAGES.CEO_APPROVAL, [TERMINATION_STAGES.CEO_APPROVAL]: TERMINATION_STAGES.LEGAL_LETTER, [TERMINATION_STAGES.LEGAL_LETTER]: TERMINATION_STAGES.TERMINATED }; const nextStage = stageFlow[termination.currentStage]; if (!nextStage) { await transaction.rollback(); return res.status(400).json({ success: false, message: 'Cannot approve from current stage' }); } await TerminationWorkflowService.transitionTermination(termination, nextStage, req.user.id, { remarks, status: nextStage === TERMINATION_STAGES.TERMINATED ? 'Terminated' : `${nextStage}` }); // If Terminated, create F&F record and clearances if (nextStage === TERMINATION_STAGES.TERMINATED) { const dealer = await db.Dealer.findByPk(termination.dealerId); const sapDues = await ExternalMocksService.mockGetFinancialDuesFromSap(dealer?.dealerCode || 'MOCK-001'); const fnf = await db.FnF.create({ terminationRequestId: termination.id, dealerId: termination.dealerId, status: 'Initiated', totalReceivables: sapDues.data.outstandingInvoices, totalPayables: sapDues.data.securityDeposit, netAmount: sapDues.data.securityDeposit - sapDues.data.outstandingInvoices }, { transaction }); await db.FnFLineItem.bulkCreate([ { fnfId: fnf.id, itemType: 'Receivable', description: 'Outstanding Invoices from SAP', department: 'Finance', amount: sapDues.data.outstandingInvoices, addedBy: req.user.id }, { fnfId: fnf.id, itemType: 'Payable', description: 'Security Deposit from SAP', department: 'Finance', amount: sapDues.data.securityDeposit, addedBy: req.user.id } ], { transaction }); const { FNF_DEPARTMENTS } = await import('../../common/config/constants.js'); await db.FffClearance.bulkCreate( FNF_DEPARTMENTS.map(dept => ({ fnfId: fnf.id, department: dept, status: 'Pending' })), { transaction } ); if (dealer) { ExternalMocksService.mockSyncDealerStatusToSap(dealer.dealerCode, 'Inactive') .catch(err => logger.error('Error syncing termination to SAP:', err)); } } } await transaction.commit(); res.json({ success: true, message: 'Termination updated', termination }); } catch (error) { if (transaction) await transaction.rollback(); logger.error('Error updating termination:', error); next(error); } }; // Submit SCN Response (Dealer Principal) export const submitScnResponse = async (req: AuthRequest, res: Response, next: NextFunction) => { const transaction: Transaction = await db.sequelize.transaction(); try { if (!req.user) throw new Error('Unauthorized'); const { terminationRequestId, responseBody, documents } = req.body; const termination = await db.TerminationRequest.findByPk(terminationRequestId); if (!termination) throw new Error('Termination request not found'); const response = await TerminationWorkflowService.handleScnResponse(termination, { responseBody, documents }, req.user.id); await transaction.commit(); res.status(201).json({ success: true, message: 'SCN Response submitted successfully', response }); } catch (error) { if (transaction) await transaction.rollback(); logger.error('Error submitting SCN response:', error); next(error); } }; // Record Personal Hearing Outcome export const recordPersonalHearing = async (req: AuthRequest, res: Response, next: NextFunction) => { const transaction: Transaction = await db.sequelize.transaction(); try { if (!req.user) throw new Error('Unauthorized'); const { terminationRequestId, attendees, summary, recommendation, momDocumentId } = req.body; const termination = await db.TerminationRequest.findByPk(terminationRequestId); if (!termination) throw new Error('Termination request not found'); const hearing = await TerminationWorkflowService.handleHearingOutcome(termination, { attendees, summary, recommendation, momDocumentId }, req.user.id); await transaction.commit(); res.status(201).json({ success: true, message: 'Hearing record saved', recommendation }); } catch (error) { if (transaction) await transaction.rollback(); logger.error('Error recording hearing:', error); next(error); } };