Dealer_Onboarding_Backend/controllers/resignationController.js

418 lines
11 KiB
JavaScript

const db = require('../models');
const logger = require('../utils/logger');
const { RESIGNATION_STAGES, AUDIT_ACTIONS, ROLES } = require('../config/constants');
const { Op } = require('sequelize');
// Generate unique resignation ID
const generateResignationId = async () => {
const count = await db.Resignation.count();
return `RES-${String(count + 1).padStart(3, '0')}`;
};
// Calculate progress percentage based on stage
const calculateProgress = (stage) => {
const stageProgress = {
[RESIGNATION_STAGES.ASM]: 15,
[RESIGNATION_STAGES.RBM]: 30,
[RESIGNATION_STAGES.ZBH]: 45,
[RESIGNATION_STAGES.NBH]: 60,
[RESIGNATION_STAGES.DD_ADMIN]: 70,
[RESIGNATION_STAGES.LEGAL]: 80,
[RESIGNATION_STAGES.FINANCE]: 90,
[RESIGNATION_STAGES.FNF_INITIATED]: 95,
[RESIGNATION_STAGES.COMPLETED]: 100,
[RESIGNATION_STAGES.REJECTED]: 0
};
return stageProgress[stage] || 0;
};
// Create resignation request (Dealer only)
exports.createResignation = async (req, res, next) => {
const transaction = await db.sequelize.transaction();
try {
const { outletId, resignationType, lastOperationalDateSales, lastOperationalDateServices, reason, additionalInfo } = req.body;
const dealerId = req.user.id;
// Verify outlet belongs to dealer
const outlet = await db.Outlet.findOne({
where: { id: outletId, dealerId }
});
if (!outlet) {
await transaction.rollback();
return res.status(404).json({
success: false,
message: 'Outlet not found or does not belong to you'
});
}
// Check if outlet already has active resignation
const existingResignation = await db.Resignation.findOne({
where: {
outletId,
status: { [Op.notIn]: ['Completed', 'Rejected'] }
}
});
if (existingResignation) {
await transaction.rollback();
return res.status(400).json({
success: false,
message: 'This outlet already has an active resignation request'
});
}
// Generate resignation ID
const resignationId = await generateResignationId();
// Create resignation
const resignation = await db.Resignation.create({
resignationId,
outletId,
dealerId,
resignationType,
lastOperationalDateSales,
lastOperationalDateServices,
reason,
additionalInfo,
currentStage: RESIGNATION_STAGES.ASM,
status: 'ASM Review',
progressPercentage: 15,
timeline: [{
stage: 'Submitted',
timestamp: new Date(),
user: req.user.name,
action: 'Resignation request submitted'
}]
}, { transaction });
// Update outlet status
await outlet.update({
status: 'Pending Resignation'
}, { transaction });
// Create audit log
await db.AuditLog.create({
userId: req.user.id,
userName: req.user.name,
userRole: req.user.role,
action: AUDIT_ACTIONS.CREATED,
entityType: 'resignation',
entityId: resignation.id,
changes: { created: resignation.toJSON() }
}, { transaction });
await transaction.commit();
logger.info(`Resignation request created: ${resignationId} by dealer: ${req.user.email}`);
// TODO: Send email notification to ASM/DD Admin
res.status(201).json({
success: true,
message: 'Resignation request submitted successfully',
resignationId: resignation.resignationId,
resignation: resignation.toJSON()
});
} catch (error) {
await transaction.rollback();
logger.error('Error creating resignation:', error);
next(error);
}
};
// Get resignations list (role-based filtering)
exports.getResignations = async (req, res, next) => {
try {
const { status, region, zone, page = 1, limit = 10 } = req.query;
const offset = (page - 1) * limit;
// Build where clause based on user role
let where = {};
if (req.user.role === ROLES.DEALER) {
// Dealers see only their resignations
where.dealerId = req.user.id;
} else if (req.user.region && ![ROLES.NBH, ROLES.DD_HEAD, ROLES.DD_LEAD, ROLES.SUPER_ADMIN].includes(req.user.role)) {
// Regional users see resignations in their region
where['$outlet.region$'] = req.user.region;
}
if (status) {
where.status = status;
}
// Get resignations
const { count, rows: resignations } = await db.Resignation.findAndCountAll({
where,
include: [
{
model: db.Outlet,
as: 'outlet',
attributes: ['id', 'code', 'name', 'type', 'city', 'state', 'region', 'zone']
},
{
model: db.User,
as: 'dealer',
attributes: ['id', 'name', 'email', 'phone']
}
],
order: [['submittedOn', 'DESC']],
limit: parseInt(limit),
offset: parseInt(offset)
});
res.json({
success: true,
resignations,
pagination: {
total: count,
page: parseInt(page),
pages: Math.ceil(count / limit),
limit: parseInt(limit)
}
});
} catch (error) {
logger.error('Error fetching resignations:', error);
next(error);
}
};
// Get resignation details
exports.getResignationById = async (req, res, next) => {
try {
const { id } = req.params;
const resignation = await db.Resignation.findOne({
where: { id },
include: [
{
model: db.Outlet,
as: 'outlet',
include: [
{
model: db.User,
as: 'dealer',
attributes: ['id', 'name', 'email', 'phone']
}
]
},
{
model: db.Worknote,
as: 'worknotes',
order: [['timestamp', 'DESC']]
}
]
});
if (!resignation) {
return res.status(404).json({
success: false,
message: 'Resignation not found'
});
}
// Check access permissions
if (req.user.role === ROLES.DEALER && resignation.dealerId !== req.user.id) {
return res.status(403).json({
success: false,
message: 'Access denied'
});
}
res.json({
success: true,
resignation
});
} catch (error) {
logger.error('Error fetching resignation details:', error);
next(error);
}
};
// Approve resignation (move to next stage)
exports.approveResignation = async (req, res, next) => {
const transaction = await db.sequelize.transaction();
try {
const { id } = req.params;
const { remarks } = req.body;
const resignation = await db.Resignation.findByPk(id, {
include: [{ model: db.Outlet, as: 'outlet' }]
});
if (!resignation) {
await transaction.rollback();
return res.status(404).json({
success: false,
message: 'Resignation not found'
});
}
// Determine next stage based on current stage
const stageFlow = {
[RESIGNATION_STAGES.ASM]: RESIGNATION_STAGES.RBM,
[RESIGNATION_STAGES.RBM]: RESIGNATION_STAGES.ZBH,
[RESIGNATION_STAGES.ZBH]: RESIGNATION_STAGES.NBH,
[RESIGNATION_STAGES.NBH]: RESIGNATION_STAGES.DD_ADMIN,
[RESIGNATION_STAGES.DD_ADMIN]: RESIGNATION_STAGES.LEGAL,
[RESIGNATION_STAGES.LEGAL]: RESIGNATION_STAGES.FINANCE,
[RESIGNATION_STAGES.FINANCE]: RESIGNATION_STAGES.FNF_INITIATED,
[RESIGNATION_STAGES.FNF_INITIATED]: RESIGNATION_STAGES.COMPLETED
};
const nextStage = stageFlow[resignation.currentStage];
if (!nextStage) {
await transaction.rollback();
return res.status(400).json({
success: false,
message: 'Cannot approve from current stage'
});
}
// Update resignation
const timeline = [...resignation.timeline, {
stage: nextStage,
timestamp: new Date(),
user: req.user.name,
action: 'Approved',
remarks
}];
await resignation.update({
currentStage: nextStage,
status: nextStage === RESIGNATION_STAGES.COMPLETED ? 'Completed' : `${nextStage} Review`,
progressPercentage: calculateProgress(nextStage),
timeline
}, { transaction });
// If completed, update outlet status
if (nextStage === RESIGNATION_STAGES.COMPLETED) {
await resignation.outlet.update({
status: 'Closed'
}, { transaction });
}
// Create audit log
await db.AuditLog.create({
userId: req.user.id,
userName: req.user.name,
userRole: req.user.role,
action: AUDIT_ACTIONS.APPROVED,
entityType: 'resignation',
entityId: resignation.id,
changes: {
from: resignation.currentStage,
to: nextStage,
remarks
}
}, { transaction });
await transaction.commit();
logger.info(`Resignation ${resignation.resignationId} approved to ${nextStage} by ${req.user.email}`);
// TODO: Send email notification to next approver
res.json({
success: true,
message: 'Resignation approved successfully',
nextStage,
resignation
});
} catch (error) {
await transaction.rollback();
logger.error('Error approving resignation:', error);
next(error);
}
};
// Reject resignation
exports.rejectResignation = async (req, res, next) => {
const transaction = await db.sequelize.transaction();
try {
const { id } = req.params;
const { reason } = req.body;
if (!reason) {
await transaction.rollback();
return res.status(400).json({
success: false,
message: 'Rejection reason is required'
});
}
const resignation = await db.Resignation.findByPk(id, {
include: [{ model: db.Outlet, as: 'outlet' }]
});
if (!resignation) {
await transaction.rollback();
return res.status(404).json({
success: false,
message: 'Resignation not found'
});
}
// Update resignation
const timeline = [...resignation.timeline, {
stage: 'Rejected',
timestamp: new Date(),
user: req.user.name,
action: 'Rejected',
reason
}];
await resignation.update({
currentStage: RESIGNATION_STAGES.REJECTED,
status: 'Rejected',
progressPercentage: 0,
rejectionReason: reason,
timeline
}, { transaction });
// Update outlet status back to Active
await resignation.outlet.update({
status: 'Active'
}, { transaction });
// Create audit log
await db.AuditLog.create({
userId: req.user.id,
userName: req.user.name,
userRole: req.user.role,
action: AUDIT_ACTIONS.REJECTED,
entityType: 'resignation',
entityId: resignation.id,
changes: { reason }
}, { transaction });
await transaction.commit();
logger.info(`Resignation ${resignation.resignationId} rejected by ${req.user.email}`);
// TODO: Send email notification to dealer
res.json({
success: true,
message: 'Resignation rejected',
resignation
});
} catch (error) {
await transaction.rollback();
logger.error('Error rejecting resignation:', error);
next(error);
}
};
module.exports = exports;