import { Request, Response } from 'express'; import { Op } from 'sequelize'; import db from '../../database/models/index.js'; const { FddAssignment, FddReport, Application } = db; import { AuthRequest } from '../../types/express.types.js'; import { AUDIT_ACTIONS, APPLICATION_STATUS, APPLICATION_STAGES, ROLES } from '../../common/config/constants.js'; import { WorkflowService } from '../../services/WorkflowService.js'; import { pickApplicationAuditContext, safeAuditLogCreate } from '../../services/applicationAuditLog.service.js'; import { NotificationService } from '../../services/NotificationService.js'; export const getAssignment = async (req: Request, res: Response) => { try { const { applicationId } = req.params; const targetId = applicationId as string; // Resolve application first to get UUID 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(targetId); const application = await Application.findOne({ where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } }); if (!application) { return res.status(404).json({ success: false, message: 'Application not found' }); } const assignment = await FddAssignment.findOne({ where: { applicationId: application.id }, 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) { console.error('Get FDD assignment error:', error); res.status(500).json({ success: false, message: 'Error fetching FDD assignment' }); } }; export const assignAgency = async (req: AuthRequest, res: Response) => { try { const { applicationId, assignedToAgency } = req.body; const targetId = applicationId as string; // Resolve application first to get UUID 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(targetId); const application = await Application.findOne({ where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } }); if (!application) { return res.status(404).json({ success: false, message: 'Application not found' }); } const assignment = await FddAssignment.create({ applicationId: application.id, assignedToAgency, // Agency User ID status: 'Assigned' }); // Bridge: Transition application to active FDD stage await WorkflowService.transitionApplication(application, APPLICATION_STATUS.FDD_VERIFICATION, req.user?.id || null, { reason: 'FDD Agency assigned. Initiating financial due diligence.', stage: APPLICATION_STAGES.FDD, progressPercentage: 70 }); await safeAuditLogCreate({ userId: req.user?.id, action: AUDIT_ACTIONS.FDD_ASSIGNED, entityType: 'fdd_assignment', entityId: assignment.id, newData: { assignedToAgency, applicationId: application.id }, }); await safeAuditLogCreate({ userId: req.user?.id, action: AUDIT_ACTIONS.FDD_ASSIGNED, entityType: 'application', entityId: application.id, newData: { assignmentId: assignment.id, assignedToAgency, note: 'FDD agency assigned; application moved to FDD verification.', context: pickApplicationAuditContext(application), }, }); // SRS §6.15.3.1 — Notify assigned FDD agency user of their assignment const fddUser = await db.User.findByPk(assignedToAgency); if (fddUser) { const phone = (fddUser as any).mobileNumber || null; NotificationService.notify(fddUser.id, fddUser.email, { title: `FDD Assignment: ${application.applicationId}`, message: `You have been assigned to conduct Financial Due Diligence for application ${application.applicationId}.`, channels: phone ? ['system', 'email', 'whatsapp'] : ['system', 'email'], templateCode: 'USER_ASSIGNED', placeholders: { applicantName: application.applicantName || '', applicationId: application.applicationId, link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`, ctaLabel: 'View Assignment', phone: phone || '' } }).catch((e: any) => console.error('[FDD] Agency notify failed:', e)); } res.status(201).json({ success: true, message: 'FDD Agency assigned', data: assignment }); } catch (error) { console.error('Assign FDD agency error:', error); res.status(500).json({ success: false, message: 'Error assigning agency' }); } }; export const uploadReport = async (req: AuthRequest, res: Response) => { try { 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 } }); 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: finalAssignmentId } } ); // Fetch application to transition const assignment = await FddAssignment.findByPk(finalAssignmentId); if (assignment) { const application = await Application.findByPk(assignment.applicationId); if (application) { await WorkflowService.transitionApplication(application, APPLICATION_STATUS.FDD_VERIFICATION, req.user?.id || null, { reason: 'FDD Report uploaded. Pending review to proceed to LOI stage.', forceLog: true // Log even if status is same }); // SRS §6.15.3.1 — Notify Finance + DD-Admin that FDD report is submitted const fddReportRoles = [ROLES.FINANCE, ROLES.DD_ADMIN]; for (const role of fddReportRoles) { const roleUsers = await db.User.findAll({ where: { roleCode: role } }); for (const u of roleUsers) { NotificationService.notify(u.id, u.email, { title: `FDD Report Submitted: ${application.applicationId}`, message: `The FDD agency has submitted their financial due diligence report for ${application.applicationId}. Review is required before LOI stage.`, channels: ['system', 'email'], templateCode: 'WORKFLOW_ACTION_REQUIRED', placeholders: { dealerName: application.applicantName || '', requestId: application.applicationId, targetStage: 'FDD Review', link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`, phone: '' } }).catch((e: any) => console.error('[FDD] Finance/Admin notify failed:', e)); } } } } res.status(201).json({ success: true, message: 'FDD Report uploaded successfully. Pending Admin review.', data: report }); } catch (error) { console.error('Upload FDD report error:', error); res.status(500).json({ success: false, message: 'Error uploading report' }); } }; export const flagNonResponsive = async (req: AuthRequest, res: Response) => { try { const { applicationId, remarks } = req.body; const targetId = applicationId as string; // Resolve application first to get UUID 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(targetId); const application = await Application.findOne({ where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } }); if (!application) { return res.status(404).json({ success: false, message: 'Application not found' }); } const previousStatutory = application.statutoryStatus; // 1. Update Application status at model level await application.update({ statutoryStatus: 'Flagged' }); console.log(`[FDDController] Flagging application ${application.id} as non-responsive (FDD)`); await safeAuditLogCreate({ userId: req.user?.id, action: AUDIT_ACTIONS.FDD_FLAGGED_NON_RESPONSIVE, entityType: 'application', entityId: application.id, oldData: { statutoryStatus: previousStatutory, overallStatus: application.overallStatus, currentStage: application.currentStage, }, newData: { statutoryStatus: 'Flagged', remarks: remarks || 'Applicant is non-responsive to FDD queries.', context: pickApplicationAuditContext(application), }, }); res.json({ success: true, message: 'Application flagged successfully' }); } catch (error) { console.error('Flag non-responsive error:', error); res.status(500).json({ success: false, message: 'Error highlighting non-responsiveness' }); } };