205 lines
8.4 KiB
TypeScript
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' });
|
|
}
|
|
};
|