Dealer_Onboarding_Backend/src/modules/master/master.controller.ts

572 lines
22 KiB
TypeScript

import { Request, Response } from 'express';
import db from '../../database/models/index.js';
const { Region, Zone, State, District, Area, User, AreaManager } = db;
// --- Regions ---
export const getRegions = async (req: Request, res: Response) => {
try {
const regions = await Region.findAll({
include: [
{
model: State,
as: 'states',
attributes: ['id', 'stateName']
},
{
model: Zone,
as: 'zone',
attributes: ['id', 'zoneName']
},
{
model: User,
as: 'regionalManager',
attributes: ['id', 'fullName', 'email', 'mobileNumber']
}
],
order: [['regionName', 'ASC']]
});
res.json({ success: true, data: regions });
} catch (error) {
console.error('Get regions error:', error);
res.status(500).json({ success: false, message: 'Error fetching regions' });
}
};
export const createRegion = async (req: Request, res: Response) => {
try {
const { zoneId, regionCode, regionName, description, stateIds, regionalManagerId } = req.body;
if (!zoneId || !regionName || !regionCode) {
return res.status(400).json({ success: false, message: 'Zone ID, region name and code are required' });
}
const region = await Region.create({
zoneId,
regionCode,
regionName,
description,
regionalManagerId: regionalManagerId || null
});
// Assign states if provided
if (stateIds && Array.isArray(stateIds) && stateIds.length > 0) {
await State.update(
{ regionId: region.id, zoneId }, // Also ensure State belongs to the Zone (hierarchy)
{ where: { id: stateIds } }
);
}
res.status(201).json({ success: true, message: 'Region created successfully', data: region });
} catch (error) {
console.error('Create region error:', error);
res.status(500).json({ success: false, message: 'Error creating region' });
}
};
export const updateRegion = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { zoneId, regionCode, regionName, description, isActive, stateIds, regionalManagerId } = req.body;
const region = await Region.findByPk(id);
if (!region) {
return res.status(404).json({ success: false, message: 'Region not found' });
}
const updates: any = {};
if (zoneId) updates.zoneId = zoneId;
if (regionCode) updates.regionCode = regionCode;
if (regionName) updates.regionName = regionName;
if (description !== undefined) updates.description = description;
if (isActive !== undefined) updates.isActive = isActive;
if (regionalManagerId !== undefined) updates.regionalManagerId = regionalManagerId;
await region.update(updates);
// Handle State reassignment
if (stateIds && Array.isArray(stateIds)) {
// 1. Unassign states currently assigned to this region but NOT in the new list?
// Or just simpler: Assign the new ones. Old ones stay?
// Standard behavior for "List of items in a container": Sync list.
// We should set regionId=null for states previously in this region but not in stateIds.
// But let's check safety. If I uncheck a state, I want it removed from the region.
// First, find states currently in this region
// Actually, simplest 'Reset and Set' approach:
// 1. Set regionId=null for all states where regionId = this.id
// 2. Set regionId=this.id for states in stateIds.
// Note: We should probably also enforce zoneId match?
// If a user moves a state to this Region, the State must conceptually belong to the Region's Zone.
// So we update both regionId and zoneId for the target states.
// Step 1: Remove States from this Region (if they are NOT in the new list)
// We can do this by:
// await State.update({ regionId: null }, { where: { regionId: id } });
// But wait, if I am only ADDING, I don't want to nuke everything.
// But "update" implies "this is the new state of the world".
// Assuming frontend sends the FULL list of selected states.
await State.update({ regionId: null }, { where: { regionId: id } });
if (stateIds.length > 0) {
await State.update(
{
regionId: id,
zoneId: zoneId || region.zoneId // Ensure state moves to the region's zone
},
{ where: { id: stateIds } }
);
}
}
res.json({ success: true, message: 'Region updated successfully' });
} catch (error) {
console.error('Update region error:', error);
res.status(500).json({ success: false, message: 'Error updating region' });
}
};
// --- Zones ---
export const getZones = async (req: Request, res: Response) => {
try {
const { regionId } = req.query as { regionId?: string };
const where: any = {};
if (regionId) {
where.regionId = regionId;
}
const zones = await Zone.findAll({
where,
include: [
{
model: Region,
as: 'regions',
attributes: ['regionName']
},
{
model: User,
as: 'zonalBusinessHead',
attributes: ['fullName', 'email', 'mobileNumber']
},
{
model: State,
as: 'states',
attributes: ['stateName']
}
],
order: [['zoneName', 'ASC']]
});
res.json({ success: true, data: zones });
} catch (error) {
console.error('Get zones error:', error);
res.status(500).json({ success: false, message: 'Error fetching zones' });
}
};
export const createZone = async (req: Request, res: Response) => {
try {
const { regionId, zoneName } = req.body;
if (!regionId || !zoneName) {
return res.status(400).json({ success: false, message: 'Region ID and zone name are required' });
}
const zone = await Zone.create({
regionId, // Wait, Zone Model doesn't have regionId. It's the other way around?
zoneName
});
res.status(201).json({ success: true, message: 'Zone created successfully', data: zone });
} catch (error) {
console.error('Create zone error:', error);
res.status(500).json({ success: false, message: 'Error creating zone' });
}
};
export const updateZone = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { zoneName, description, isActive, zonalBusinessHeadId, stateIds } = req.body;
const zone = await Zone.findByPk(id);
if (!zone) {
return res.status(404).json({ success: false, message: 'Zone not found' });
}
const updates: any = {};
if (zoneName) updates.zoneName = zoneName;
if (description !== undefined) updates.description = description;
if (isActive !== undefined) updates.isActive = isActive;
if (zonalBusinessHeadId !== undefined) updates.zonalBusinessHeadId = zonalBusinessHeadId;
await zone.update(updates);
// Handle State assignment
if (stateIds && Array.isArray(stateIds) && stateIds.length > 0) {
// Update all provided states to belong to this zone
// We can't easily "remove" states because zoneId is non-nullable.
// States must be moved TO another zone to be removed from this one.
// So we primarily handle "bringing states into this zone".
// However, we should check if they exist first.
await State.update(
{ zoneId: zone.id },
{ where: { id: stateIds } }
);
}
res.json({ success: true, message: 'Zone updated successfully' });
} catch (error) {
console.error('Update zone error:', error);
res.status(500).json({ success: false, message: 'Error updating zone' });
}
};
// --- States ---
export const getStates = async (req: Request, res: Response) => {
try {
const { zoneId } = req.query as { zoneId?: string };
const where: any = {};
if (zoneId) where.zoneId = zoneId;
const states = await State.findAll({
where,
include: [{ model: Zone, as: 'zone', attributes: ['zoneName'] }],
order: [['stateName', 'ASC']]
});
res.json({ success: true, states });
} catch (error) {
console.error('Get states error:', error);
res.status(500).json({ success: false, message: 'Error fetching states' });
}
};
export const createState = async (req: Request, res: Response) => {
try {
const { zoneId, stateName } = req.body;
if (!zoneId || !stateName) return res.status(400).json({ success: false, message: 'Zone ID and state name required' });
const state = await State.create({ zoneId, stateName });
res.status(201).json({ success: true, message: 'State created', data: state });
} catch (error) {
console.error('Create state error:', error);
res.status(500).json({ success: false, message: 'Error creating state' });
}
};
export const updateState = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { stateName, isActive } = req.body;
const state = await State.findByPk(id);
if (!state) return res.status(404).json({ success: false, message: 'State not found' });
await state.update({ stateName, isActive });
res.json({ success: true, message: 'State updated' });
} catch (error) {
console.error('Update state error:', error);
res.status(500).json({ success: false, message: 'Error updating state' });
}
};
// --- Districts ---
export const getDistricts = async (req: Request, res: Response) => {
try {
const { stateId } = req.query as { stateId?: string };
const where: any = {};
if (stateId) where.stateId = stateId;
const districts = await District.findAll({
where,
include: [{ model: State, as: 'state', attributes: ['stateName'] }],
order: [['districtName', 'ASC']]
});
res.json({ success: true, districts });
} catch (error) {
console.error('Get districts error:', error);
res.status(500).json({ success: false, message: 'Error fetching districts' });
}
};
export const createDistrict = async (req: Request, res: Response) => {
try {
const { stateId, districtName } = req.body;
if (!stateId || !districtName) return res.status(400).json({ success: false, message: 'State ID and district name required' });
const district = await District.create({ stateId, districtName });
res.status(201).json({ success: true, message: 'District created', data: district });
} catch (error) {
console.error('Create district error:', error);
res.status(500).json({ success: false, message: 'Error creating district' });
}
};
export const updateDistrict = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { districtName, isActive } = req.body;
const district = await District.findByPk(id);
if (!district) return res.status(404).json({ success: false, message: 'District not found' });
await district.update({ districtName, isActive });
res.json({ success: true, message: 'District updated' });
} catch (error) {
console.error('Update district error:', error);
res.status(500).json({ success: false, message: 'Error updating district' });
}
};
// --- Areas ---
export const getAreas = async (req: Request, res: Response) => {
try {
const { districtId } = req.query as { districtId?: string };
const where: any = {};
if (districtId) where.districtId = districtId;
const areas = await Area.findAll({
where,
include: [
{ model: District, as: 'district', attributes: ['districtName'] },
{ model: State, as: 'state', attributes: ['stateName'] },
{ model: Region, as: 'region', attributes: ['regionName'] },
{ model: Zone, as: 'zone', attributes: ['zoneName'] },
// Include explicit manager column (legacy/fallback)
{ model: User, as: 'manager', attributes: ['id', 'fullName', 'email', 'mobileNumber'] },
// Include active managers from dedicated table
{
model: AreaManager,
as: 'areaManagers',
where: { isActive: true },
required: false, // Left join, so we get areas even without managers
include: [{
model: User,
as: 'user',
attributes: ['id', 'fullName', 'email', 'mobileNumber']
}]
}
],
order: [['areaName', 'ASC']]
});
res.json({ success: true, areas });
} catch (error) {
console.error('Get areas error:', error);
res.status(500).json({ success: false, message: 'Error fetching areas' });
}
};
export const createArea = async (req: Request, res: Response) => {
try {
const { districtId, areaCode, areaName, city, pincode, managerId } = req.body;
if (!districtId || !areaName || !pincode) return res.status(400).json({ success: false, message: 'District ID, area name, and pincode required' });
// Need to fetch regionId from district -> state -> zone -> region?
// Or user provides it?
// The Area model has regionId, districtId.
// It's safer to fetch relationships.
const district = await District.findByPk(districtId, {
include: [{
model: State,
as: 'state',
include: [
{ model: Zone, as: 'zone' },
{ model: Region, as: 'region' }
]
}]
});
let regionId = null;
let zoneId = null;
let stateId = null;
if (district) {
stateId = district.stateId;
// Access associations using the logical structure (District -> State -> Zone/Region)
if (district.state) {
if (district.state.zone) {
zoneId = district.state.zone.id;
}
if (district.state.region) {
regionId = district.state.region.id;
}
}
}
const area = await Area.create({
districtId,
stateId,
zoneId,
regionId,
areaCode,
areaName,
city,
pincode,
managerId: managerId || null, // Legacy support
isActive: req.body.isActive ?? true,
activeFrom: req.body.activeFrom || null,
activeTo: req.body.activeTo || null
});
// Create AreaManager record if manager assigned
if (managerId) {
await AreaManager.create({
areaId: area.id,
userId: managerId,
managerType: 'ASM',
isActive: true,
assignedAt: new Date(),
asmCode: req.body.asmCode || null
});
}
res.status(201).json({ success: true, message: 'Area created', data: area });
} catch (error) {
console.error('Create area error:', error);
res.status(500).json({ success: false, message: 'Error creating area' });
}
};
// --- Area Managers ---
export const getAreaManagers = async (req: Request, res: Response) => {
try {
// Fetch Users who have active AreaManager assignments
// We use the User model as the primary so we get the User details naturally
const managers = await User.findAll({
attributes: ['id', 'fullName', 'email', 'mobileNumber', 'employeeId', 'roleCode', 'zoneId', 'regionId'],
include: [
{
model: AreaManager,
as: 'areaManagers',
where: { isActive: true },
required: true, // Only return users who ARE active managers
attributes: ['asmCode'],
include: [
{
model: Area,
as: 'area',
attributes: ['id', 'areaName', 'areaCode'],
include: [
{ model: District, as: 'district', attributes: ['districtName'] },
{ model: State, as: 'state', attributes: ['stateName'] },
{ model: Region, as: 'region', attributes: ['id', 'regionName'] },
{ model: Zone, as: 'zone', attributes: ['id', 'zoneName'] }
]
}
]
},
{ model: Zone, as: 'zone', attributes: ['id', 'zoneName'] },
{ model: Region, as: 'region', attributes: ['id', 'regionName'] }
],
order: [['fullName', 'ASC']]
});
// Transform if necessary to flatten the structure for the frontend
// But the user asked for "straightforward", so a clean nested JSON is usually best
// We can double check if they want a flat list of (User, Area) pairs or User -> [Areas]
// "Arean mangers" implies the People. So User -> [Areas] is the best entity representation.
res.json({ success: true, data: managers });
} catch (error) {
console.error('Get area managers error:', error);
res.status(500).json({ success: false, message: 'Error fetching area managers' });
}
};
export const updateArea = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { areaName, city, pincode, isActive, managerId, districtId, activeFrom, activeTo } = req.body;
const area = await Area.findByPk(id);
if (!area) return res.status(404).json({ success: false, message: 'Area not found' });
const updates: any = {};
if (areaName) updates.areaName = areaName;
if (city) updates.city = city;
if (pincode) updates.pincode = pincode;
if (isActive !== undefined) updates.isActive = isActive;
if (activeFrom !== undefined) updates.activeFrom = activeFrom || null;
if (activeTo !== undefined) updates.activeTo = activeTo || null;
if (managerId !== undefined) updates.managerId = managerId; // Legacy support
// If district is changed, update the entire hierarchy (State, Zone, Region)
if (districtId && districtId !== area.districtId) {
updates.districtId = districtId;
const district = await District.findByPk(districtId, {
include: [{
model: State,
as: 'state',
include: [
{ model: Zone, as: 'zone' },
{ model: Region, as: 'region' }
]
}]
});
if (district) {
updates.stateId = district.stateId;
if (district.state) {
if (district.state.zone) {
updates.zoneId = district.state.zone.id;
}
if (district.state.region) {
updates.regionId = district.state.region.id;
}
}
}
}
await area.update(updates);
// Handle AreaManager Table Update
if (managerId !== undefined) {
const asmCode = req.body.asmCode;
// 1. Find currently active manager for this area
const currentActiveManager = await AreaManager.findOne({
where: {
areaId: id,
isActive: true
}
});
// If there is an active manager
if (currentActiveManager) {
// If the new managerId is different (or null, meaning unassign), deactivate the old one
if (currentActiveManager.userId !== managerId) {
await currentActiveManager.update({ isActive: false });
} else {
// If SAME user, update asmCode if provided
if (asmCode !== undefined) {
await currentActiveManager.update({ asmCode });
}
}
}
// 2. If a new manager is being assigned (and it's not null)
if (managerId) {
// Check if this specific user is already active (to avoid duplicates if logic above missed it)
const isAlreadyActive = currentActiveManager && currentActiveManager.userId === managerId;
if (!isAlreadyActive) {
await AreaManager.create({
areaId: id,
userId: managerId,
managerType: 'ASM', // Default type
isActive: true,
assignedAt: new Date(),
asmCode: asmCode || null
});
}
}
}
res.json({ success: true, message: 'Area updated successfully' });
} catch (error) {
console.error('Update area error:', error);
res.status(500).json({ success: false, message: 'Error updating area' });
}
};