import { Request, Response } from 'express'; import { Op } from 'sequelize'; import { syncLocationManagers, syncRegionManager, syncZoneManager } from './syncHierarchy.service.js'; import db from '../../database/models/index.js'; const { User } = db; // --- Areas (Granular Locations) --- export const getAreas = async (req: Request, res: Response) => { try { let search = req.query.search as string; let page = (req.query.page || 1) as any; let limit = (req.query.limit || 10) as any; const checkNested = (obj: any) => { if (!obj || typeof obj !== 'object') return; if (!search && obj.search) search = obj.search; if (page === 1 && obj.page) page = obj.page; if (limit === 10 && obj.limit) limit = obj.limit; }; checkNested(req.query.params); const isAll = limit === 'all' || limit === -1 || limit === '-1'; page = Number(page || 1); limit = isAll ? null : Number(limit || 10); const offset = isAll ? null : (page - 1) * limit; const where: any = {}; if (search) { where[Op.or] = [ { name: { [Op.iLike]: `%${search}%` } }, { city: { [Op.iLike]: `%${search}%` } }, { '$district.name$': { [Op.iLike]: `%${search}%` } } ]; } const { count, rows: areas } = await db.Location.findAndCountAll({ where, include: [ { model: db.District, as: 'district', include: [ { model: db.Zone, as: 'zone', attributes: ['name'] }, { model: db.Region, as: 'region', attributes: ['name'] }, { model: db.State, as: 'state', attributes: ['name'] } ] } ], order: [['name', 'ASC']], limit: limit === null ? undefined : Number(limit), offset: offset === null ? undefined : Number(offset), distinct: true, subQuery: false }); const result = areas.map((a: any) => { const d = a.district || {}; return { ...a.toJSON(), districtName: d.name || 'N/A', zoneName: d.zone?.name || 'UNKNOWN', regionName: d.region?.name || 'UNKNOWN', stateName: d.state?.name || 'UNKNOWN' }; }); res.json({ success: true, data: result, pagination: { total: count, page: Number(page), limit: isAll ? count : Number(limit), totalPages: isAll ? 1 : Math.ceil(count / Number(limit)) } }); } catch (error) { console.error('Get areas error:', error); res.status(500).json({ success: false, message: 'Error fetching areas' }); } }; // --- Districts (Territory Entities) --- export const getDistricts = async (req: Request, res: Response) => { try { let search = req.query.search as string; let limit = (req.query.limit || 10) as any; const stateId = req.query.stateId as string; const zoneId = req.query.zoneId as string; const regionId = req.query.regionId as string; const isAll = limit === 'all' || limit === -1 || limit === '-1'; const where: any = {}; if (search) { where.name = { [Op.iLike]: `%${search}%` }; } if (stateId) where.stateId = stateId; if (zoneId) where.zoneId = zoneId; if (regionId) where.regionId = regionId; const { count, rows: districts } = await db.District.findAndCountAll({ where, include: [ { model: db.Zone, as: 'zone', attributes: ['id', 'name'] }, { model: db.Region, as: 'region', attributes: ['id', 'name'] }, { model: db.State, as: 'state', attributes: ['id', 'name'] }, { model: db.User, as: 'asm', attributes: ['id', 'fullName', 'email', 'employeeId'] }, { model: db.User, as: 'zonalManager', attributes: ['id', 'fullName', 'email', 'employeeId'] } ], order: [['name', 'ASC']], limit: isAll ? undefined : Number(limit), distinct: true }); const result = districts.map((d: any) => ({ ...d.toJSON(), zoneName: d.zone?.name || 'UNKNOWN', regionName: d.region?.name || 'UNKNOWN', stateName: d.state?.name || 'UNKNOWN', asmName: d.asm?.fullName || 'UNASSIGNED', zmName: d.zonalManager?.fullName || 'UNASSIGNED' })); res.json({ success: true, data: result, total: count }); } 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 { name, code, stateName, city, openFrom, openTo, status, isActive, description } = req.body; if (!name) return res.status(400).json({ success: false, message: 'District name is required' }); // Find or Create state if stateName provided let stateId = req.body.stateId; if (stateName && !stateId) { const [state] = await db.State.findOrCreate({ where: { name: stateName }, defaults: { name: stateName } }); stateId = state.id; } const district = await db.District.create({ name, code, stateId, isActive: isActive !== undefined ? isActive : true }); const area = await db.Location.create({ name, districtId: district.id, city: city || name, isActive: true, openFrom: openFrom || null, openTo: openTo || null, description: description || null }); // Create associated Opportunity for "Active Period" and "City" await db.Opportunity.create({ districtId: district.id, areaId: area.id, city: city || name, openFrom: openFrom || null, openTo: openTo || null, status: status || 'inactive', opportunityType: 'New Dealership', capacity: 'Standard', priority: 'Medium' }); res.status(201).json({ success: true, data: area }); } catch (error) { console.error('Create area error:', error); res.status(500).json({ success: false, message: 'Error creating area' }); } }; // --- Regions --- export const getRegions = async (req: Request, res: Response) => { try { const regions = await db.Region.findAll({ include: [ { model: db.Zone, as: 'zone', attributes: ['name'] }, { model: db.District, as: 'districts', attributes: ['id', 'name', 'stateId'], include: [{ model: db.State, as: 'state', attributes: ['id', 'name'] }] } ], order: [['name', 'ASC']] }); const roles = await db.Role.findAll({ where: { roleCode: { [db.Sequelize.Op.in]: ['ASM', 'RM', 'RBM'] } } }); const asmRoleIds = roles.filter((r: any) => r.roleCode === 'ASM').map((r: any) => r.id); const rmRoleIds = roles.filter((r: any) => ['RM', 'RBM'].includes(r.roleCode)).map((r: any) => r.id); const result = await Promise.all(regions.map(async (region: any) => { const regionJson = region.toJSON(); regionJson.zoneName = region.zone?.name || 'UNKNOWN'; const districtIds = (region.districts || []).map((d: any) => d.id); const [asmCount, rmCount, rmAssignment] = await Promise.all([ db.UserRole.count({ where: { roleId: { [db.Sequelize.Op.in]: asmRoleIds }, districtId: { [db.Sequelize.Op.in]: districtIds }, isActive: true }, distinct: true, col: 'userId' }), db.UserRole.count({ where: { roleId: { [db.Sequelize.Op.in]: rmRoleIds }, regionId: region.id, isActive: true }, distinct: true, col: 'userId' }), db.UserRole.findOne({ where: { roleId: { [db.Sequelize.Op.in]: rmRoleIds }, regionId: region.id, isActive: true }, include: [{ model: db.User, as: 'user' }] }) ]); regionJson.asmCount = asmCount; regionJson.regionalOfficerCount = rmCount; regionJson.regionalManager = rmAssignment?.user || null; regionJson.rbmCode = region.rbmCode || 'N/A'; // Extract unique states for this region const statesMap = new Map(); (region.districts || []).forEach((d: any) => { if (d.state) { statesMap.set(d.state.id, d.state.name); } }); regionJson.states = Array.from(statesMap.values()); regionJson.districts = (region.districts || []).map((d: any) => ({ id: d.id, name: d.name.toUpperCase(), stateId: d.stateId })); return regionJson; })); res.json({ success: true, data: result }); } 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 { name, code, parentId, zoneId, managerId, districts, districtIds } = req.body; const targetZoneId = zoneId || parentId; if (!name) return res.status(400).json({ success: false, message: 'Region name is required' }); const region = await db.Region.create({ name, code, zoneId: targetZoneId }); // 1. Assign Manager if (managerId) { const rmRole = await db.Role.findOne({ where: { roleCode: 'RM' } }); if (rmRole) { await db.UserRole.update({ isActive: false }, { where: { regionId: region.id, roleId: rmRole.id } }); await db.UserRole.create({ userId: managerId, roleId: rmRole.id, regionId: region.id, zoneId: targetZoneId, isActive: true, isPrimary: true }); } } // 2. Assign Districts (with conflict check) const targetDistrictIds = districts || districtIds; if (Array.isArray(targetDistrictIds) && targetDistrictIds.length > 0) { const conflicts = await db.District.findAll({ where: { id: { [db.Sequelize.Op.in]: targetDistrictIds }, regionId: { [db.Sequelize.Op.and]: [{ [db.Sequelize.Op.ne]: null }, { [db.Sequelize.Op.ne]: region.id }] } } }); if (conflicts.length > 0) { return res.status(409).json({ success: false, message: `Districts already assigned to another region: ${conflicts.map((c: any) => c.name).join(', ')}` }); } await db.District.update( { regionId: region.id, zoneId: targetZoneId }, { where: { id: { [db.Sequelize.Op.in]: targetDistrictIds } } } ); } res.status(201).json({ success: true, message: 'Region created', data: region }); } catch (error: any) { console.error('Create region error:', error); if (error.name === 'SequelizeUniqueConstraintError') { const field = error.errors?.[0]?.path || 'field'; const value = error.errors?.[0]?.value || ''; return res.status(409).json({ success: false, message: `A region with this ${field} "${value}" already exists. Please use a different name.` }); } res.status(500).json({ success: false, message: 'Error creating region' }); } }; export const updateRegion = async (req: Request, res: Response) => { try { const { id } = req.params; const { name, code, parentId, zoneId, managerId, districts, districtIds } = req.body; const targetZoneId = zoneId || parentId; const region = await db.Region.findByPk(id); if (!region) return res.status(404).json({ success: false, message: 'Region not found' }); await region.update({ name, code, zoneId: targetZoneId || region.zoneId }); // 1. Update Manager if (managerId) { const rmRole = await db.Role.findOne({ where: { roleCode: 'RM' } }); if (rmRole) { // Deactivate old RMs for this region await db.UserRole.update({ isActive: false }, { where: { regionId: id, roleId: rmRole.id } }); // Assign new RM await db.UserRole.create({ userId: managerId, roleId: rmRole.id, regionId: id, zoneId: region.zoneId, isActive: true, isPrimary: true }); } } // 2. Update Districts (with conflict check) const targetDistrictIds = districts || districtIds; if (Array.isArray(targetDistrictIds)) { if (targetDistrictIds.length > 0) { const conflicts = await db.District.findAll({ where: { id: { [db.Sequelize.Op.in]: targetDistrictIds }, regionId: { [db.Sequelize.Op.and]: [{ [db.Sequelize.Op.ne]: null }, { [db.Sequelize.Op.ne]: id }] } } }); if (conflicts.length > 0) { return res.status(409).json({ success: false, message: `Districts already assigned to another region: ${conflicts.map((c: any) => c.name).join(', ')}` }); } } await db.District.update({ regionId: null }, { where: { regionId: id } }); if (targetDistrictIds.length > 0) { await db.District.update( { regionId: id, zoneId: region.zoneId }, { where: { id: { [db.Sequelize.Op.in]: targetDistrictIds } } } ); } } res.json({ success: true, message: 'Region updated' }); } catch (error: any) { console.error('Update region error:', error); if (error.name === 'SequelizeUniqueConstraintError') { const field = error.errors?.[0]?.path || 'field'; const value = error.errors?.[0]?.value || ''; return res.status(409).json({ success: false, message: `A region with this ${field} "${value}" already exists. Please use a different name.` }); } res.status(500).json({ success: false, message: 'Error updating region' }); } }; // --- Zones --- export const getZones = async (req: Request, res: Response) => { try { const zones = await db.Zone.findAll({ include: [ { model: db.Region, as: 'regions', attributes: ['id', 'name'] }, { model: db.State, as: 'states', attributes: ['id', 'name'] }, { model: db.District, as: 'districts', attributes: ['id'] } ], order: [['name', 'ASC']] }); const roles = await db.Role.findAll({ where: { roleCode: { [db.Sequelize.Op.in]: ['ZBH', 'DD-ZM'] } } }); const zbhRoleIds = roles.filter((r: any) => r.roleCode === 'ZBH').map((r: any) => r.id); const zmRoleIds = roles.filter((r: any) => r.roleCode === 'DD-ZM').map((r: any) => r.id); const result = await Promise.all(zones.map(async (zone: any) => { const zoneJson = zone.toJSON(); const [zbhAssignment, zms] = await Promise.all([ db.UserRole.findOne({ where: { roleId: { [db.Sequelize.Op.in]: zbhRoleIds }, zoneId: zone.id, isPrimary: true, isActive: true }, include: [{ model: db.User, as: 'user' }] }), db.UserRole.findAll({ where: { roleId: { [db.Sequelize.Op.in]: zmRoleIds }, zoneId: zone.id, isActive: true }, include: [{ model: db.User, as: 'user' }] }) ]); // For each ZM, fetch their assigned districts in this zone const zonalManagers = await Promise.all(zms.map(async (zmRole: any) => { const districts = await db.District.findAll({ where: { zmId: zmRole.user.id, zoneId: zone.id }, // ZMs usually managed regions or specific district sets attributes: ['name'] }); return { id: zmRole.user.id, name: zmRole.user.fullName || zmRole.user.name, email: zmRole.user.email, phone: zmRole.user.mobileNumber || 'N/A', code: zmRole.managerCode || zmRole.user.employeeId || 'N/A', districts: districts.map((d: any) => d.name) }; })); zoneJson.regionCount = (zone.regions || []).length; zoneJson.districtCount = (zone.districts || []).length; zoneJson.states = (zone.states || []).map((s: any) => s.name.toUpperCase()); zoneJson.zmCount = zms.length; zoneJson.zonalBusinessHead = zbhAssignment ? { id: zbhAssignment.user.id, name: zbhAssignment.user.fullName || zbhAssignment.user.name, email: zbhAssignment.user.email, phone: zbhAssignment.user.mobileNumber || 'N/A', code: zone.zbhCode || 'N/A' } : null; zoneJson.zonalManagers = zonalManagers; return zoneJson; })); res.json({ success: true, data: result }); } 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 { name, code, stateIds, managerId } = req.body; if (!name) return res.status(400).json({ success: false, message: 'Zone name is required' }); const zone = await db.Zone.create({ name, code }); // 1. Assign ZBH if (managerId) { const zbhRole = await db.Role.findOne({ where: { roleCode: 'ZBH' } }); if (zbhRole) { await db.UserRole.update({ isActive: false }, { where: { zoneId: zone.id, roleId: zbhRole.id } }); await db.UserRole.create({ userId: managerId, roleId: zbhRole.id, zoneId: zone.id, isActive: true, isPrimary: true }); } } if (Array.isArray(stateIds) && stateIds.length > 0) { await db.State.update({ zoneId: zone.id }, { where: { id: { [db.Sequelize.Op.in]: stateIds } } }); await db.District.update({ zoneId: zone.id }, { where: { stateId: { [db.Sequelize.Op.in]: stateIds } } }); } res.status(201).json({ success: true, message: 'Zone created', 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 { name, code, stateIds, managerId } = req.body; const zone = await db.Zone.findByPk(id); if (!zone) return res.status(404).json({ success: false, message: 'Zone not found' }); await zone.update({ name, code }); // 1. Update ZBH if (managerId) { const zbhRole = await db.Role.findOne({ where: { roleCode: 'ZBH' } }); if (zbhRole) { // Deactivate old ZBHs for this zone await db.UserRole.update({ isActive: false }, { where: { zoneId: id, roleId: zbhRole.id } }); // Assign new ZBH await db.UserRole.create({ userId: managerId, roleId: zbhRole.id, zoneId: id, isActive: true, isPrimary: true }); } } if (Array.isArray(stateIds)) { await db.State.update({ zoneId: null }, { where: { zoneId: id } }); await db.District.update({ zoneId: null }, { where: { zoneId: id } }); if (stateIds.length > 0) { await db.State.update({ zoneId: id }, { where: { id: { [db.Sequelize.Op.in]: stateIds } } }); await db.District.update({ zoneId: id }, { where: { stateId: { [db.Sequelize.Op.in]: stateIds } } }); } } res.json({ success: true, message: 'Zone updated' }); } 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 states = await db.State.findAll({ include: [{ model: db.Zone, as: 'zone', attributes: ['name'] }], order: [['name', 'ASC']] }); res.json({ success: true, data: 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 { name, zoneId } = req.body; if (!name) return res.status(400).json({ success: false, message: 'State name is required' }); const state = await db.State.create({ name, zoneId }); if (zoneId) { await db.District.update({ zoneId }, { where: { stateId: state.id } }); } res.status(201).json({ success: true, data: state }); } catch (error) { console.error('Create state error:', error); res.status(500).json({ success: false, message: 'Error creating state' }); } }; // --- Managers --- export const getManagersByRole = async (req: Request, res: Response) => { try { const { roleCode, locationId } = req.query as any; const managers = await User.findAll({ attributes: ['id', 'fullName', 'email', 'mobileNumber', 'roleCode'], include: [ { model: db.UserRole, as: 'userRoles', include: [{ model: db.Role, as: 'role' }] } ] }); const filteredManagers = managers.filter((m: any) => { const hasRole = !roleCode || m.roleCode === roleCode || (m.userRoles || []).some((a: any) => a.role?.roleCode === roleCode); const hasLocation = !locationId || (m.userRoles || []).some((a: any) => a.districtId === locationId || a.zoneId === locationId || a.regionId === locationId ); return hasRole && hasLocation; }); res.json({ success: true, data: filteredManagers }); } catch (error) { console.error('Get managers error:', error); res.status(500).json({ success: false, message: 'Error fetching managers' }); } }; export const getAreaManagers = async (req: Request, res: Response) => { req.query.roleCode = 'ASM'; return getManagersByRole(req, res); }; // --- Delete Area (Location) --- export const deleteLocation = async (req: Request, res: Response) => { try { const { id } = req.params; const area = await db.Location.findByPk(id); if (!area) return res.status(404).json({ success: false, message: 'Area not found' }); // Delete associated opportunities if they belong to this granular location await db.Opportunity.destroy({ where: { areaId: id } }); // Delete the location itself await area.destroy(); res.json({ success: true, message: 'Area deleted successfully' }); } catch (error) { console.error('Delete area error:', error); res.status(500).json({ success: false, message: 'Error deleting area' }); } }; export const updateLocation = async (req: Request, res: Response) => { try { const { id } = req.params; // This is the Area ID const { name, code, stateName, city, openFrom, openTo, status, isActive, description } = req.body; const area = await db.Location.findByPk(id, { include: [{ model: db.District, as: 'district' }] }); if (!area) return res.status(404).json({ success: false, message: 'Area not found' }); const district = area.district; // 1. Update District if (district) { let stateId = req.body.stateId; if (stateName && !stateId) { const [state] = await db.State.findOrCreate({ where: { name: stateName }, defaults: { name: stateName } }); stateId = state.id; } await district.update({ name: name || district.name, code: code || district.code, stateId: stateId || district.stateId, isActive: isActive !== undefined ? isActive : district.isActive }); } // 2. Update Area await area.update({ name: name || area.name, city: city || area.city, isActive: isActive !== undefined ? isActive : area.isActive, openFrom: openFrom !== undefined ? (openFrom || null) : area.openFrom, openTo: openTo !== undefined ? (openTo || null) : area.openTo, description: description || area.description }); // 3. Update or Create associated Opportunity const [opportunity] = await db.Opportunity.findOrBuild({ where: { areaId: id } }); opportunity.set({ districtId: district?.id || opportunity.districtId, city: city || opportunity.city || name || area.name, openFrom: openFrom !== undefined ? (openFrom || null) : opportunity.openFrom, openTo: openTo !== undefined ? (openTo || null) : opportunity.openTo, status: status || opportunity.status || 'inactive' }); await opportunity.save(); res.json({ success: true, message: 'Area updated' }); if (district && typeof district.id === 'string') { await syncLocationManagers(district.id); } } catch (error) { console.error('Update area error:', error); res.status(500).json({ success: false, message: 'Error updating area' }); } }; // --- Managers --- export const getASMs = async (req: Request, res: Response) => { try { const asms = await db.User.findAll({ where: { roleCode: { [db.Sequelize.Op.in]: ['ASM', 'AREA SALES MANAGER'] }, isActive: true }, include: [ { model: db.UserRole, as: 'userRoles', where: { isActive: true }, required: false, include: [{ model: db.Role, as: 'role', where: { roleCode: 'ASM' } }] }, { model: db.District, as: 'managedAsmDistricts', include: [ { model: db.State, as: 'state', attributes: ['id', 'name'] }, { model: db.Region, as: 'region', attributes: ['id', 'name'] }, { model: db.Zone, as: 'zone', attributes: ['id', 'name'] } ] } ], order: [['fullName', 'ASC']] }); const result = (asms || []).map((u: any) => { const districts = u.managedAsmDistricts || []; const asmRoleAssignment = (u.userRoles || []).find((r: any) => r.role?.roleCode === 'ASM'); const asmCode = asmRoleAssignment?.managerCode || u.employeeId; const zoneSet = new Set(); const regionSet = new Set(); const stateSet = new Set(); const territoryInfo = districts.map((d: any) => { if (d.zone) zoneSet.add(JSON.stringify({ id: d.zone.id, name: d.zone.name })); if (d.region) regionSet.add(JSON.stringify({ id: d.region.id, name: d.region.name })); if (d.state) stateSet.add(d.state.name); return { id: d.id, name: d.name, stateId: d.stateId, regionId: d.regionId, zoneId: d.zoneId }; }); const zones = Array.from(zoneSet).map((s: any) => JSON.parse(s)); const regions = Array.from(regionSet).map((s: any) => JSON.parse(s)); return { id: u.id, name: u.fullName, email: u.email, phone: u.mobileNumber, employeeId: u.employeeId, asmCode: asmCode || 'N/A', status: u.status, zoneId: zones[0]?.id || '', zoneName: zones[0]?.name || 'Unassigned', regionId: regions[0]?.id || '', regionName: regions[0]?.name || 'Unassigned', areasManaged: territoryInfo, stateNames: Array.from(stateSet), totalDistricts: territoryInfo.length }; }); res.json({ success: true, data: result }); } catch (error) { console.error('Get ASMs error:', error); res.status(500).json({ success: false, message: 'Error fetching ASMs' }); } }; export const getZonalManagers = async (req: Request, res: Response) => { try { const zms = await db.User.findAll({ attributes: ['id', 'fullName', 'email', 'employeeId', 'status'], include: [ { model: db.UserRole, as: 'userRoles', where: { isActive: true }, required: true, include: [{ model: db.Role, as: 'role', where: { roleCode: { [Op.in]: ['ZM', 'DD-ZM', 'ZBH'] } } }] }, { model: db.District, as: 'managedZmDistricts', include: [ { model: db.Zone, as: 'zone', attributes: ['id', 'name'] }, { model: db.Region, as: 'region', attributes: ['id', 'name'] }, { model: db.State, as: 'state', attributes: ['id', 'name'] } ] } ], order: [['fullName', 'ASC']] }); const result = (zms || []).map((u: any) => { const rolePriority = ['DD-ZM', 'ZM', 'ZBH']; const roleAssignment = (u.userRoles || []).sort((a: any, b: any) => { const aIndex = rolePriority.indexOf(a.role?.roleCode || ''); const bIndex = rolePriority.indexOf(b.role?.roleCode || ''); if (aIndex !== bIndex) return aIndex - bIndex; // If same role type, prefer the one with a code if (a.managerCode && !b.managerCode) return -1; if (!a.managerCode && b.managerCode) return 1; return 0; })[0]; const zmCode = roleAssignment?.managerCode || u.employeeId || 'N/A'; // Collect unique zones and states const zoneSet = new Set(); const stateSet = new Set(); let inferredZoneId = roleAssignment?.zoneId || null; (u.managedZmDistricts || []).forEach((d: any) => { if (d.zone) { zoneSet.add(d.zone.name); if (!inferredZoneId) inferredZoneId = d.zone.id; // Fallback to first district's zone if role zone is missing } if (d.state) stateSet.add(d.state.name); }); return { id: u.id, name: u.fullName, email: u.email, employeeId: u.employeeId, zmCode: zmCode, status: u.status, zoneId: inferredZoneId, zones: Array.from(zoneSet).length > 0 ? Array.from(zoneSet) : ["Assigned Zone"], stateNames: Array.from(stateSet), districts: (u.managedZmDistricts || []).map((d: any) => ({ id: d.id, name: d.name, state: d.state?.name })) }; }); res.json({ success: true, data: result }); } catch (error) { console.error('Get ZMs error:', error); res.status(500).json({ success: false, message: 'Error fetching Zonal Managers' }); } }; export const saveZM = async (req: Request, res: Response) => { try { const { userId, zmCode, zoneId, districts, status } = req.body; if (!userId) return res.status(400).json({ success: false, message: 'userId is required' }); // Find the ZM role (DD-ZM) const zmRole = await db.Role.findOne({ where: { roleCode: 'DD-ZM' } }); if (!zmRole) return res.status(404).json({ success: false, message: 'ZM role (DD-ZM) not found in roles table' }); // Update User status if provided if (status) { await db.User.update({ status }, { where: { id: userId } }); } // Deactivate existing ZM role assignments for this user await db.UserRole.update({ isActive: false }, { where: { userId, roleId: zmRole.id } }); // Create new active UserRole with managerCode = zmCode await db.UserRole.create({ userId, roleId: zmRole.id, zoneId: zoneId || null, managerCode: zmCode || null, isActive: true, isPrimary: true }); // Assign districts to this user if provided // First, clear this ZM from any other districts they might have had await db.District.update({ zmId: null, zmCode: null }, { where: { zmId: userId } }); if (Array.isArray(districts) && districts.length > 0) { // Then assign new ones const updateProps: any = { zmId: userId, zmCode: zmCode || null }; if (zoneId) updateProps.zoneId = zoneId; await db.District.update( updateProps, { where: { id: { [db.Sequelize.Op.in]: districts } } } ); } res.json({ success: true, message: 'Zonal Manager saved successfully' }); } catch (error) { console.error('Save ZM error:', error); res.status(500).json({ success: false, message: 'Error saving Zonal Manager' }); } }; export const createArea = createDistrict; export const deleteArea = deleteLocation; export const createDistrictLegacy = createDistrict;