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' }); } };