Dealer_Onboarding_Backend/src/modules/self-service/constitutional.controller.ts

205 lines
8.4 KiB
TypeScript

import { Response } from 'express';
import db from '../../database/models/index.js';
const { ConstitutionalChange, Outlet, User, Worknote } = db;
import { Op, Transaction } from 'sequelize';
import { v4 as uuidv4 } from 'uuid';
import { AuthRequest } from '../../types/express.types.js';
import { CONSTITUTIONAL_STAGES, AUDIT_ACTIONS, ROLES } from '../../common/config/constants.js';
import { ConstitutionalWorkflowService } from '../../services/ConstitutionalWorkflowService.js';
export const submitRequest = async (req: AuthRequest, res: Response) => {
try {
if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' });
const { outletId, changeType, reason, currentConstitution, newPartnersDetails, shareholdingPattern } = req.body;
const requestId = `CC-${Date.now()}-${uuidv4().substring(0, 4).toUpperCase()}`;
// Store extra details in metadata
const metadata = {
newPartnersDetails,
shareholdingPattern,
currentConstitution
};
const request = await ConstitutionalChange.create({
requestId,
outletId: outletId || null, // Optional for dealer-level changes
dealerId: req.user.id,
changeType,
description: reason,
currentConstitution: currentConstitution || null,
currentStage: CONSTITUTIONAL_STAGES.SUBMITTED,
status: 'Submitted',
progressPercentage: ConstitutionalWorkflowService.calculateProgress(CONSTITUTIONAL_STAGES.SUBMITTED),
metadata,
documents: [],
timeline: [{
stage: 'Submitted',
timestamp: new Date(),
user: req.user.fullName,
action: 'Request submitted',
remarks: reason
}]
});
await db.AuditLog.create({
userId: req.user.id,
action: AUDIT_ACTIONS.CREATED,
entityType: 'constitutional_change',
entityId: request.id
});
res.status(201).json({
success: true,
message: 'Constitutional change request submitted successfully',
requestId: request.requestId
});
} catch (error) {
console.error('Submit constitutional change error:', error);
res.status(500).json({ success: false, message: 'Error submitting request' });
}
};
export const getRequests = async (req: AuthRequest, res: Response) => {
try {
if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' });
const where: any = {};
if (req.user.roleCode === 'Dealer') {
where.dealerId = req.user.id;
}
const requests = await ConstitutionalChange.findAll({
where,
include: [
{ model: Outlet, as: 'outlet', attributes: ['code', 'name'] },
{ model: User, as: 'dealer', attributes: ['fullName'] }
],
order: [['createdAt', 'DESC']]
});
res.json({ success: true, requests });
} catch (error) {
console.error('Get constitutional changes error:', error);
res.status(500).json({ success: false, message: 'Error fetching requests' });
}
};
export const getRequestById = async (req: AuthRequest, res: Response) => {
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 request = await ConstitutionalChange.findOne({
where: isUUID ? { [Op.or]: [{ id: idStr }, { requestId: idStr }] } : { requestId: idStr },
include: [
{ model: Outlet, as: 'outlet' },
{ model: User, as: 'dealer', attributes: ['fullName', 'email'] },
{ model: Worknote, as: 'worknotes' }
]
});
if (!request) return res.status(404).json({ success: false, message: 'Request not found' });
res.json({ success: true, request });
} catch (error) {
console.error('Get constitutional change details error:', error);
res.status(500).json({ success: false, message: 'Error fetching details' });
}
};
export const takeAction = async (req: AuthRequest, res: Response) => {
try {
if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' });
const { id } = req.params;
const idStr = String(id);
const { action, comments } = req.body; // Approve, Reject, Send Back
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 request = await ConstitutionalChange.findOne({
where: isUUID ? { [Op.or]: [{ id: idStr }, { requestId: idStr }] } : { requestId: idStr }
});
if (!request) return res.status(404).json({ success: false, message: 'Request not found' });
if (action === 'Reject') {
await ConstitutionalWorkflowService.transitionRequest(request, CONSTITUTIONAL_STAGES.REJECTED, req.user.id, {
action: 'Rejected',
status: 'Rejected',
remarks: comments,
userFullName: req.user.fullName
});
} else {
// Multi-level approval flow as per SRS 12.2.4
const stageFlow: Record<string, string> = {
[CONSTITUTIONAL_STAGES.SUBMITTED]: CONSTITUTIONAL_STAGES.ASM_REVIEW,
[CONSTITUTIONAL_STAGES.ASM_REVIEW]: CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW,
[CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW]: CONSTITUTIONAL_STAGES.ZBH_REVIEW,
[CONSTITUTIONAL_STAGES.ZBH_REVIEW]: CONSTITUTIONAL_STAGES.LEAD_REVIEW,
[CONSTITUTIONAL_STAGES.LEAD_REVIEW]: CONSTITUTIONAL_STAGES.HEAD_REVIEW,
[CONSTITUTIONAL_STAGES.HEAD_REVIEW]: CONSTITUTIONAL_STAGES.NBH_APPROVAL,
[CONSTITUTIONAL_STAGES.NBH_APPROVAL]: CONSTITUTIONAL_STAGES.LEGAL_REVIEW,
[CONSTITUTIONAL_STAGES.LEGAL_REVIEW]: CONSTITUTIONAL_STAGES.COMPLETED
};
const nextStage = stageFlow[request.currentStage];
if (!nextStage) return res.status(400).json({ success: false, message: 'Cannot move forward from current stage' });
await ConstitutionalWorkflowService.transitionRequest(request, nextStage, req.user.id, {
action: action === 'Approve' ? `Approved to ${nextStage}` : action,
remarks: comments,
userFullName: req.user.fullName
});
}
res.json({ success: true, message: `Request ${action.toLowerCase()}ed successfully` });
} catch (error) {
console.error('Take action error:', error);
res.status(500).json({ success: false, message: 'Error processing action' });
}
};
/**
* Returns document checklist based on target constitution
*/
export const getChecklist = async (req: AuthRequest, res: Response) => {
try {
const { targetConstitution } = req.query;
if (!targetConstitution) return res.status(400).json({ success: false, message: 'targetConstitution is required' });
const checklist = ConstitutionalWorkflowService.getDocumentChecklist(targetConstitution as string);
res.json({ success: true, checklist });
} catch (error) {
res.status(500).json({ success: false, message: 'Error fetching checklist' });
}
};
export const uploadDocuments = async (req: AuthRequest, res: Response) => {
try {
const { id } = req.params;
const idStr = String(id);
const { documents } = req.body;
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 request = await ConstitutionalChange.findOne({
where: isUUID ? { [Op.or]: [{ id: idStr }, { requestId: idStr }] } : { requestId: idStr }
});
if (!request) {
return res.status(404).json({ success: false, message: 'Request not found' });
}
await request.update({
documents: documents,
updatedAt: new Date()
});
res.json({ success: true, message: 'Documents uploaded successfully' });
} catch (error) {
console.error('Upload documents error:', error);
res.status(500).json({ success: false, message: 'Error uploading documents' });
}
};