differnt apprach aded for hirarchy
This commit is contained in:
parent
6edb5da84f
commit
2e1e96cc54
@ -10,6 +10,7 @@
|
||||
"build": "tsc",
|
||||
"type-check": "tsc --noEmit",
|
||||
"migrate": "tsx scripts/migrate.ts",
|
||||
"reset:stable": "tsx scripts/reset_db_stable.ts",
|
||||
"seed": "tsx scripts/seed_normalized_data.ts",
|
||||
"seed:approval-policies": "tsx scripts/seed-approval-policies.ts",
|
||||
"seed:all": "npm run seed && npm run seed:approval-policies && npm run seed:questionnaire",
|
||||
|
||||
90
scripts/reset_db_stable.ts
Normal file
90
scripts/reset_db_stable.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import 'dotenv/config';
|
||||
import db from '../src/database/models/index.js';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const { Role, Zone, Region, State, Location, User, UserRole } = db;
|
||||
|
||||
async function resetAndSeed() {
|
||||
console.log('--- RESETTING DATABASE TO DENORMALIZED DISTRICT MODEL ---');
|
||||
|
||||
try {
|
||||
await db.sequelize.authenticate();
|
||||
console.log('Database connected.');
|
||||
|
||||
// 1. Force Sync
|
||||
await db.sequelize.sync({ force: true });
|
||||
console.log('Database schema reset (force synced).');
|
||||
|
||||
const hashedPassword = await bcrypt.hash('Admin@123', 10);
|
||||
|
||||
// 2. Seed Roles
|
||||
const roles = [
|
||||
{ roleCode: 'NBH', roleName: 'National Business Head', category: 'NATIONAL' },
|
||||
{ roleCode: 'DD Head', roleName: 'DD Head', category: 'NATIONAL' },
|
||||
{ roleCode: 'ZBH', roleName: 'Zonal Business Head', category: 'ZONAL' },
|
||||
{ roleCode: 'DD-ZM', roleName: 'DD Zonal Manager', category: 'ZONAL' },
|
||||
{ roleCode: 'RM', roleName: 'Regional Manager', category: 'REGIONAL' },
|
||||
{ roleCode: 'ASM', roleName: 'Area Sales Manager', category: 'AREA' },
|
||||
{ roleCode: 'Super Admin', roleName: 'Super Admin', category: 'NATIONAL' },
|
||||
{ roleCode: 'Dealer', roleName: 'Dealer', category: 'EXTERNAL' }
|
||||
];
|
||||
|
||||
for (const r of roles) await Role.create(r);
|
||||
console.log('Roles seeded.');
|
||||
|
||||
// 3. Seed Hierarchy (Zone -> State & Region -> District)
|
||||
// Zone
|
||||
const northZone = await Zone.create({ name: 'North Zone', code: 'ZONE-N' });
|
||||
const southZone = await Zone.create({ name: 'South Zone', code: 'ZONE-S' });
|
||||
|
||||
// State
|
||||
const delhiState = await State.create({ name: 'Delhi', zoneId: northZone.id });
|
||||
const haryanaState = await State.create({ name: 'Haryana', zoneId: northZone.id });
|
||||
const karnatakaState = await State.create({ name: 'Karnataka', zoneId: southZone.id });
|
||||
|
||||
// Region
|
||||
const ncrRegion = await Region.create({ name: 'NCR Region', zoneId: northZone.id });
|
||||
const bangaloreRegion = await Region.create({ name: 'Bangalore Region', zoneId: southZone.id });
|
||||
|
||||
// District (Location)
|
||||
await Location.create({ name: 'Central Delhi', stateId: delhiState.id, regionId: ncrRegion.id, zoneId: northZone.id });
|
||||
await Location.create({ name: 'Gurgaon', stateId: haryanaState.id, regionId: ncrRegion.id, zoneId: northZone.id });
|
||||
await Location.create({ name: 'Bangalore Urban', stateId: karnatakaState.id, regionId: bangaloreRegion.id, zoneId: southZone.id });
|
||||
|
||||
console.log('Denormalized Hierarchy seeded.');
|
||||
|
||||
// 4. Seed Admin Users
|
||||
const adminUser = await User.create({
|
||||
fullName: 'Super Admin',
|
||||
email: 'admin@royalenfield.com',
|
||||
roleCode: 'Super Admin',
|
||||
password: hashedPassword,
|
||||
status: 'active'
|
||||
});
|
||||
const superAdminRole = await Role.findOne({ where: { roleCode: 'Super Admin' } });
|
||||
if (superAdminRole) {
|
||||
await UserRole.create({ userId: adminUser.id, roleId: superAdminRole.id, isActive: true, isPrimary: true });
|
||||
}
|
||||
|
||||
const zbhUser = await User.create({
|
||||
fullName: 'Yashwin (ZBH North)',
|
||||
email: 'yashwin@gmail.com',
|
||||
roleCode: 'ZBH',
|
||||
password: hashedPassword,
|
||||
status: 'active'
|
||||
});
|
||||
const zbhRole = await Role.findOne({ where: { roleCode: 'ZBH' } });
|
||||
if (zbhRole) {
|
||||
await UserRole.create({ userId: zbhUser.id, roleId: zbhRole.id, zoneId: northZone.id, isActive: true, isPrimary: true });
|
||||
}
|
||||
|
||||
console.log('Admin Users seeded.');
|
||||
console.log('--- DATABASE RESET & SEEDING COMPLETE ---');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Error during database reset/seed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
resetAndSeed();
|
||||
@ -2,17 +2,15 @@ import 'dotenv/config';
|
||||
import db from '../src/database/models/index.js';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const { Role, Location, LocationHierarchy, User, UserRole } = db;
|
||||
const { Role, Zone, Region, State, Location, User, UserRole } = db;
|
||||
|
||||
async function seed() {
|
||||
console.log('--- Seeding Normalized Graph Data ---');
|
||||
console.log('--- Seeding Normalized Denormalized Data ---');
|
||||
|
||||
// Ensure schema exists when seed is run on a fresh/empty database.
|
||||
// This is non-destructive (does not drop data).
|
||||
await db.sequelize.authenticate();
|
||||
// Use sync with alter false to match main app behavior
|
||||
await db.sequelize.sync({ alter: false });
|
||||
|
||||
// Hash default password for test users
|
||||
const hashedPassword = await bcrypt.hash('Admin@123', 10);
|
||||
|
||||
// 1. Create Roles
|
||||
@ -22,6 +20,7 @@ async function seed() {
|
||||
{ roleCode: 'ZBH', roleName: 'Zonal Business Head', category: 'ZONAL' },
|
||||
{ roleCode: 'DD Lead', roleName: 'DD Lead', category: 'ZONAL' },
|
||||
{ roleCode: 'RBM', roleName: 'Regional Business Manager', category: 'REGIONAL' },
|
||||
{ roleCode: 'RM', roleName: 'Regional Manager', category: 'REGIONAL' },
|
||||
{ roleCode: 'DD-ZM', roleName: 'DD Zonal Manager', category: 'ZONAL' },
|
||||
{ roleCode: 'ASM', roleName: 'Area Sales Manager', category: 'AREA' },
|
||||
{ roleCode: 'Super Admin', roleName: 'Super Admin', category: 'NATIONAL' },
|
||||
@ -36,76 +35,54 @@ async function seed() {
|
||||
}
|
||||
console.log('Roles seeded.');
|
||||
|
||||
// 2. Create Locations
|
||||
const existingZones = await Location.findAll({
|
||||
where: { type: 'zone' },
|
||||
order: [['createdAt', 'ASC']]
|
||||
// 2. Create Locations (Hierarchy)
|
||||
const [zone1] = await Zone.findOrCreate({
|
||||
where: { name: 'North Zone' },
|
||||
defaults: { name: 'North Zone', code: 'ZONE-N' }
|
||||
});
|
||||
|
||||
let zone1: any = existingZones[0];
|
||||
let zone2: any = existingZones[1];
|
||||
|
||||
if (!zone1) {
|
||||
const [createdZone1] = await Location.findOrCreate({
|
||||
where: { name: 'North Zone', type: 'zone' },
|
||||
defaults: { name: 'North Zone', type: 'zone' }
|
||||
});
|
||||
zone1 = createdZone1;
|
||||
}
|
||||
|
||||
if (!zone2) {
|
||||
const [createdZone2] = await Location.findOrCreate({
|
||||
where: { name: 'South Zone', type: 'zone' },
|
||||
defaults: { name: 'South Zone', type: 'zone' }
|
||||
});
|
||||
zone2 = createdZone2;
|
||||
}
|
||||
|
||||
const [region1] = await Location.findOrCreate({
|
||||
where: { name: 'Delhi Region', type: 'region' },
|
||||
defaults: { name: 'Delhi Region', type: 'region' }
|
||||
});
|
||||
const [area1] = await Location.findOrCreate({
|
||||
where: { name: 'South Delhi Area', type: 'area' },
|
||||
defaults: { name: 'South Delhi Area', type: 'area' }
|
||||
const [zone2] = await Zone.findOrCreate({
|
||||
where: { name: 'South Zone' },
|
||||
defaults: { name: 'South Zone', code: 'ZONE-S' }
|
||||
});
|
||||
|
||||
const [region2] = await Location.findOrCreate({
|
||||
where: { name: 'Bangalore Region', type: 'region' },
|
||||
defaults: { name: 'Bangalore Region', type: 'region' }
|
||||
const [state1] = await State.findOrCreate({
|
||||
where: { name: 'Delhi' },
|
||||
defaults: { name: 'Delhi', zoneId: zone1.id }
|
||||
});
|
||||
|
||||
console.log('Locations created.');
|
||||
|
||||
// 3. Create Hierarchies (Bridge Table)
|
||||
await LocationHierarchy.findOrCreate({
|
||||
where: { locationId: region1.id, parentId: zone1.id },
|
||||
defaults: { locationId: region1.id, parentId: zone1.id }
|
||||
});
|
||||
await LocationHierarchy.findOrCreate({
|
||||
where: { locationId: area1.id, parentId: region1.id },
|
||||
defaults: { locationId: area1.id, parentId: region1.id }
|
||||
});
|
||||
await LocationHierarchy.findOrCreate({
|
||||
where: { locationId: region2.id, parentId: zone2.id },
|
||||
defaults: { locationId: region2.id, parentId: zone2.id }
|
||||
const [region1] = await Region.findOrCreate({
|
||||
where: { name: 'NCR Region' },
|
||||
defaults: { name: 'NCR Region', zoneId: zone1.id }
|
||||
});
|
||||
|
||||
console.log('Hierarchies seeded.');
|
||||
const [region2] = await Region.findOrCreate({
|
||||
where: { name: 'Bangalore Region' },
|
||||
defaults: { name: 'Bangalore Region', zoneId: zone2.id }
|
||||
});
|
||||
|
||||
const mapUserRole = async (userRec: any, roleCode: string, locationId?: string) => {
|
||||
const [district1] = await Location.findOrCreate({
|
||||
where: { name: 'South Delhi District' },
|
||||
defaults: { name: 'South Delhi District', stateId: state1.id, regionId: region1.id, zoneId: zone1.id }
|
||||
});
|
||||
|
||||
console.log('Geographical Hierarchy seeded.');
|
||||
|
||||
const mapUserRole = async (userRec: any, roleCode: string, assignment: { zoneId?: string | null, regionId?: string | null, locationId?: string | null } = {}) => {
|
||||
const role = await Role.findOne({ where: { roleCode } });
|
||||
if (role) {
|
||||
await UserRole.findOrCreate({
|
||||
where: {
|
||||
userId: userRec.id,
|
||||
roleId: role.id,
|
||||
locationId: locationId || null
|
||||
...assignment
|
||||
},
|
||||
defaults: {
|
||||
userId: userRec.id,
|
||||
roleId: role.id,
|
||||
locationId: locationId || null
|
||||
...assignment,
|
||||
isActive: true,
|
||||
isPrimary: true
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -113,43 +90,43 @@ async function seed() {
|
||||
|
||||
// 4. Create Users and Map them
|
||||
// Custom Seed Users
|
||||
const nbhUser = await User.findOrCreate({
|
||||
const nbhResult = await User.findOrCreate({
|
||||
where: { email: 'nbh@example.com' },
|
||||
defaults: { fullName: 'National Head', roleCode: 'NBH', password: hashedPassword }
|
||||
});
|
||||
await mapUserRole(nbhUser[0], 'NBH');
|
||||
await mapUserRole(nbhResult[0], 'NBH');
|
||||
|
||||
const zbhUser = await User.findOrCreate({
|
||||
const zbhResult = await User.findOrCreate({
|
||||
where: { email: 'zbh.north@example.com' },
|
||||
defaults: { fullName: 'North Zonal Head', roleCode: 'ZBH', password: hashedPassword }
|
||||
});
|
||||
await mapUserRole(zbhUser[0], 'ZBH', zone1.id);
|
||||
await mapUserRole(zbhResult[0], 'ZBH', { zoneId: zone1.id });
|
||||
|
||||
const rbmUser = await User.findOrCreate({
|
||||
const rmResult = await User.findOrCreate({
|
||||
where: { email: 'rbm.delhi@example.com' },
|
||||
defaults: { fullName: 'Delhi Regional Manager', roleCode: 'RBM', password: hashedPassword }
|
||||
defaults: { fullName: 'Delhi Regional Manager', roleCode: 'RM', password: hashedPassword }
|
||||
});
|
||||
await mapUserRole(rbmUser[0], 'RBM', region1.id);
|
||||
await mapUserRole(rmResult[0], 'RM', { regionId: region1.id });
|
||||
|
||||
const asmUser = await User.findOrCreate({
|
||||
const asmResult = await User.findOrCreate({
|
||||
where: { email: 'asm.sdelhi@example.com' },
|
||||
defaults: { fullName: 'South Delhi ASM', roleCode: 'ASM', password: hashedPassword }
|
||||
});
|
||||
await mapUserRole(asmUser[0], 'ASM', area1.id);
|
||||
await mapUserRole(asmResult[0], 'ASM', { locationId: district1.id });
|
||||
|
||||
// Requested Mock Users
|
||||
// Mock Users alignment
|
||||
const mockUsers = [
|
||||
{ email: 'ddlead@royalenfield.com', name: 'Meera Iyer', roleCode: 'DD Lead', location: zone1.id },
|
||||
{ email: 'finance@royalenfield.com', name: 'Rahul Verma', roleCode: 'Finance', location: null },
|
||||
{ email: 'dealer@royalenfield.com', name: 'Amit Sharma', roleCode: 'Dealer', location: area1.id, isExt: true },
|
||||
{ email: 'admin@royalenfield.com', name: 'Laxman H', roleCode: 'DD Lead', location: zone2.id },
|
||||
{ email: 'yashwin@gmail.com', name: 'Yashwin', roleCode: 'ZBH', location: zone1.id },
|
||||
{ email: 'kenil@gmail.com', name: 'Kenil', roleCode: 'DD Lead', location: zone1.id },
|
||||
{ email: 'lince@gmail.com', name: 'Lince', roleCode: 'DD Admin', location: null }
|
||||
{ email: 'ddlead@royalenfield.com', name: 'Meera Iyer', roleCode: 'DD Lead', assignment: { zoneId: zone1.id } },
|
||||
{ email: 'finance@royalenfield.com', name: 'Rahul Verma', roleCode: 'Finance', assignment: {} },
|
||||
{ email: 'dealer@royalenfield.com', name: 'Amit Sharma', roleCode: 'Dealer', assignment: { locationId: district1.id }, isExt: true },
|
||||
{ email: 'admin@royalenfield.com', name: 'Laxman H', roleCode: 'DD Lead', assignment: { zoneId: zone2.id } },
|
||||
{ email: 'yashwin@gmail.com', name: 'Yashwin', roleCode: 'ZBH', assignment: { zoneId: zone1.id } },
|
||||
{ email: 'kenil@gmail.com', name: 'Kenil', roleCode: 'DD Lead', assignment: { zoneId: zone1.id } },
|
||||
{ email: 'lince@gmail.com', name: 'Lince', roleCode: 'DD Admin', assignment: {} }
|
||||
];
|
||||
|
||||
for (const m of mockUsers) {
|
||||
const u = await User.findOrCreate({
|
||||
const [u] = await User.findOrCreate({
|
||||
where: { email: m.email },
|
||||
defaults: {
|
||||
fullName: m.name,
|
||||
@ -159,7 +136,7 @@ async function seed() {
|
||||
status: 'active'
|
||||
}
|
||||
});
|
||||
await mapUserRole(u[0], m.roleCode, m.location);
|
||||
await mapUserRole(u, m.roleCode, m.assignment);
|
||||
}
|
||||
|
||||
console.log('Users and Mappings seeded.');
|
||||
|
||||
@ -7,78 +7,80 @@ import db from '../src/database/models/index.js';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const { Location, LocationHierarchy } = db;
|
||||
const { Zone, State, Location } = db;
|
||||
|
||||
async function run() {
|
||||
console.log('--- Migrating Real Geo Data to Normalized Location Models ---');
|
||||
console.log('--- Seeding Real Geo Data (Denormalized Model) ---');
|
||||
try {
|
||||
// Read the original seeder file as text so we don't have to duplicate the 350 items
|
||||
await db.sequelize.authenticate();
|
||||
|
||||
// Read the source seeder file
|
||||
const seederPath = path.join(__dirname, '../seeders/20240127-seed-geo-data.js');
|
||||
const content = fs.readFileSync(seederPath, 'utf8');
|
||||
|
||||
// Extract the arrays using eval since it's a known static JS file
|
||||
// Extract arrays using regex
|
||||
const zonesMatch = content.match(/const ZONES_DATA = \[([\s\S]*?)\];/);
|
||||
const statesMatch = content.match(/const STATES_DATA = \[([\s\S]*?)\];/);
|
||||
const citiesMatch = content.match(/const CITIES_DATA = \[([\s\S]*?)\];/);
|
||||
|
||||
if (!zonesMatch || !statesMatch || !citiesMatch) {
|
||||
throw new Error('Could not parse geo data arrays!');
|
||||
throw new Error('Could not parse geo data arrays from seeder file!');
|
||||
}
|
||||
|
||||
// Eval helper (data is trusted since it's our own seeder)
|
||||
const ZONES_DATA = eval(`[${zonesMatch[1]}]`);
|
||||
const STATES_DATA = eval(`[${statesMatch[1]}]`);
|
||||
const CITIES_DATA = eval(`[${citiesMatch[1]}]`);
|
||||
|
||||
console.log(`Extracted ${ZONES_DATA.length} Zones, ${STATES_DATA.length} States, and ${CITIES_DATA.length} Cities.`);
|
||||
console.log(`Extracted ${ZONES_DATA.length} Zones, ${STATES_DATA.length} States, and ${CITIES_DATA.length} Districts.`);
|
||||
|
||||
// 1. Insert Zones
|
||||
const zoneIdMap = new Map();
|
||||
// 1. Seed Zones
|
||||
const zoneIdMap = new Map(); // Name -> UUID
|
||||
for (const z of ZONES_DATA) {
|
||||
const [loc] = await Location.findOrCreate({
|
||||
where: { name: z.name, type: 'zone' },
|
||||
defaults: { name: z.name, type: 'zone' }
|
||||
const [zoneRecord] = await Zone.findOrCreate({
|
||||
where: { name: z.name },
|
||||
defaults: { name: z.name, code: z.code }
|
||||
});
|
||||
zoneIdMap.set(z.code, loc.id);
|
||||
z._dbId = loc.id;
|
||||
zoneIdMap.set(z.name, zoneRecord.id);
|
||||
// Attach states list for later lookup
|
||||
z._dbId = zoneRecord.id;
|
||||
}
|
||||
console.log('Zones seeded.');
|
||||
|
||||
// 2. Insert States and link to Zones
|
||||
const stateIdMap = new Map();
|
||||
// 2. Seed States and link to Zones
|
||||
const stateIdMap = new Map(); // Legacy ID -> { id, zoneId }
|
||||
for (const s of STATES_DATA) {
|
||||
const [loc] = await Location.findOrCreate({
|
||||
where: { name: s.name, type: 'state' },
|
||||
defaults: { name: s.name, type: 'state' }
|
||||
// Find parent zone by checking which zone's states array contains this state name
|
||||
const parentZoneData = ZONES_DATA.find((z: any) => z.states.includes(s.name));
|
||||
const zoneId = parentZoneData ? zoneIdMap.get(parentZoneData.name) : null;
|
||||
|
||||
const [stateRecord] = await State.findOrCreate({
|
||||
where: { name: s.name },
|
||||
defaults: { name: s.name, zoneId: zoneId }
|
||||
});
|
||||
stateIdMap.set(s.id, loc.id);
|
||||
|
||||
// Find which zone string array it belongs to
|
||||
const parentZone = ZONES_DATA.find((z: any) => z.states.includes(s.name));
|
||||
if (parentZone) {
|
||||
await LocationHierarchy.findOrCreate({
|
||||
where: { locationId: loc.id, parentId: parentZone._dbId },
|
||||
defaults: { locationId: loc.id, parentId: parentZone._dbId }
|
||||
});
|
||||
}
|
||||
stateIdMap.set(s.id, { id: stateRecord.id, zoneId: zoneId });
|
||||
}
|
||||
console.log('States seeded.');
|
||||
|
||||
// 3. Insert Cities (Districts) and link to States
|
||||
let cityCount = 0;
|
||||
// 3. Seed Districts (Locations)
|
||||
let districtCount = 0;
|
||||
for (const c of CITIES_DATA) {
|
||||
const stateDbId = stateIdMap.get(c.state_id);
|
||||
if (stateDbId) {
|
||||
const [loc] = await Location.findOrCreate({
|
||||
where: { name: c.name, type: 'district' },
|
||||
defaults: { name: c.name, type: 'district' }
|
||||
const parentStateData = stateIdMap.get(c.state_id);
|
||||
if (parentStateData) {
|
||||
await Location.findOrCreate({
|
||||
where: { name: c.name, stateId: parentStateData.id },
|
||||
defaults: {
|
||||
name: c.name,
|
||||
stateId: parentStateData.id,
|
||||
zoneId: parentStateData.zoneId
|
||||
}
|
||||
});
|
||||
await LocationHierarchy.findOrCreate({
|
||||
where: { locationId: loc.id, parentId: stateDbId },
|
||||
defaults: { locationId: loc.id, parentId: stateDbId }
|
||||
});
|
||||
cityCount++;
|
||||
districtCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Successfully seeded Real Geo Data! Created ${cityCount} districts tied to their respective states and zones.`);
|
||||
console.log(`✅ Successfully seeded Real Geo Data! Created/Verified ${districtCount} districts.`);
|
||||
process.exit(0);
|
||||
|
||||
} catch (e: any) {
|
||||
|
||||
@ -3,12 +3,13 @@ import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
export interface LocationAttributes {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'zone' | 'region' | 'area' | 'state' | 'district';
|
||||
code?: string;
|
||||
pincode?: string;
|
||||
stateId?: string | null;
|
||||
regionId?: string | null;
|
||||
zoneId?: string | null;
|
||||
asmId?: string | null;
|
||||
isActive?: boolean;
|
||||
activeFrom?: string | Date | null;
|
||||
activeTo?: string | Date | null;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface LocationInstance extends Model<LocationAttributes>, LocationAttributes { }
|
||||
@ -24,50 +25,68 @@ export default (sequelize: Sequelize) => {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('zone', 'region', 'area', 'state', 'district'),
|
||||
allowNull: false
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
allowNull: true,
|
||||
unique: true
|
||||
},
|
||||
pincode: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
stateId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'states',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'regions',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
zoneId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
asmId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
},
|
||||
activeFrom: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true
|
||||
},
|
||||
activeTo: {
|
||||
type: DataTypes.DATE,
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'locations',
|
||||
timestamps: true
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{ fields: ['stateId'] },
|
||||
{ fields: ['regionId'] },
|
||||
{ fields: ['zoneId'] },
|
||||
{ unique: true, fields: ['name', 'stateId'] }
|
||||
]
|
||||
});
|
||||
|
||||
(Location as any).associate = (models: any) => {
|
||||
// Many-to-Many hierarchy via LocationHierarchy bridge table
|
||||
Location.belongsToMany(models.Location, {
|
||||
through: models.LocationHierarchy,
|
||||
as: 'parents',
|
||||
foreignKey: 'locationId',
|
||||
otherKey: 'parentId'
|
||||
});
|
||||
Location.belongsToMany(models.Location, {
|
||||
through: models.LocationHierarchy,
|
||||
as: 'children',
|
||||
foreignKey: 'parentId',
|
||||
otherKey: 'locationId'
|
||||
});
|
||||
|
||||
Location.belongsTo(models.State, { foreignKey: 'stateId', as: 'state' });
|
||||
Location.belongsTo(models.Region, { foreignKey: 'regionId', as: 'region' });
|
||||
Location.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' });
|
||||
Location.belongsTo(models.User, { foreignKey: 'asmId', as: 'asm' });
|
||||
Location.hasMany(models.User, { foreignKey: 'locationId', as: 'users' });
|
||||
Location.hasMany(models.UserRole, { foreignKey: 'locationId', as: 'userRoles' });
|
||||
Location.hasMany(models.Application, { foreignKey: 'locationId', as: 'applications' });
|
||||
};
|
||||
|
||||
53
src/database/models/Region.ts
Normal file
53
src/database/models/Region.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface RegionAttributes {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
description?: string | null;
|
||||
zoneId?: string | null;
|
||||
}
|
||||
|
||||
export interface RegionInstance extends Model<RegionAttributes>, RegionAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const Region = sequelize.define<RegionInstance>('Region', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
unique: true
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
zoneId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
tableName: 'regions',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
(Region as any).associate = (models: any) => {
|
||||
Region.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' });
|
||||
Region.hasMany(models.Location, { foreignKey: 'regionId', as: 'districts' });
|
||||
};
|
||||
|
||||
return Region;
|
||||
};
|
||||
42
src/database/models/State.ts
Normal file
42
src/database/models/State.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface StateAttributes {
|
||||
id: string;
|
||||
name: string;
|
||||
zoneId?: string | null;
|
||||
}
|
||||
|
||||
export interface StateInstance extends Model<StateAttributes>, StateAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const State = sequelize.define<StateInstance>('State', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
zoneId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
tableName: 'states',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
(State as any).associate = (models: any) => {
|
||||
State.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' });
|
||||
State.hasMany(models.Location, { foreignKey: 'stateId', as: 'districts' });
|
||||
};
|
||||
|
||||
return State;
|
||||
};
|
||||
@ -4,7 +4,9 @@ export interface UserRoleAttributes {
|
||||
id: string;
|
||||
userId: string;
|
||||
roleId: string;
|
||||
locationId: string | null;
|
||||
locationId: string | null; // District
|
||||
zoneId: string | null;
|
||||
regionId: string | null;
|
||||
managerCode: string | null;
|
||||
isPrimary: boolean;
|
||||
isActive: boolean;
|
||||
@ -47,6 +49,22 @@ export default (sequelize: Sequelize) => {
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
zoneId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'regions',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
managerCode: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
@ -98,6 +116,14 @@ export default (sequelize: Sequelize) => {
|
||||
foreignKey: 'locationId',
|
||||
as: 'location'
|
||||
});
|
||||
UserRole.belongsTo(models.Zone, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'zone'
|
||||
});
|
||||
UserRole.belongsTo(models.Region, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'region'
|
||||
});
|
||||
UserRole.belongsTo(models.User, {
|
||||
foreignKey: 'assignedBy',
|
||||
as: 'assigner'
|
||||
|
||||
45
src/database/models/Zone.ts
Normal file
45
src/database/models/Zone.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface ZoneAttributes {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface ZoneInstance extends Model<ZoneAttributes>, ZoneAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const Zone = sequelize.define<ZoneInstance>('Zone', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
unique: true
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'zones',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
(Zone as any).associate = (models: any) => {
|
||||
Zone.hasMany(models.Region, { foreignKey: 'zoneId', as: 'regions' });
|
||||
Zone.hasMany(models.State, { foreignKey: 'zoneId', as: 'states' });
|
||||
Zone.hasMany(models.Location, { foreignKey: 'zoneId', as: 'districts' });
|
||||
};
|
||||
|
||||
return Zone;
|
||||
};
|
||||
@ -20,7 +20,9 @@ import createSLAEscalationConfig from './SLAEscalationConfig.js';
|
||||
import createWorkflowStageConfig from './WorkflowStageConfig.js';
|
||||
import createNotification from './Notification.js';
|
||||
import createLocation from './Location.js';
|
||||
import createLocationHierarchy from './LocationHierarchy.js';
|
||||
import createZone from './Zone.js';
|
||||
import createRegion from './Region.js';
|
||||
import createState from './State.js';
|
||||
|
||||
// Batch 1: Organizational Hierarchy & User Management
|
||||
import createRole from './Role.js';
|
||||
@ -122,7 +124,9 @@ db.SLAEscalationConfig = createSLAEscalationConfig(sequelize);
|
||||
db.WorkflowStageConfig = createWorkflowStageConfig(sequelize);
|
||||
db.Notification = createNotification(sequelize);
|
||||
db.Location = createLocation(sequelize);
|
||||
db.LocationHierarchy = createLocationHierarchy(sequelize);
|
||||
db.Zone = createZone(sequelize);
|
||||
db.Region = createRegion(sequelize);
|
||||
db.State = createState(sequelize);
|
||||
|
||||
// Batch 1: Organizational Hierarchy & User Management
|
||||
db.Role = createRole(sequelize);
|
||||
|
||||
@ -169,26 +169,25 @@ export const getAllUsers = async (req: Request, res: Response) => {
|
||||
(Array.isArray(roleCode) && roleCode.some(r => typeof r === 'string' && nationalRoles.includes(r)));
|
||||
|
||||
if (!isNationalRole && locationId) {
|
||||
// Find all ancestors of the given location (BFS for many-to-many support)
|
||||
let ancestorIds: Set<string> = new Set([locationId as string]);
|
||||
let queue: string[] = [locationId as string];
|
||||
const district: any = await db.Location.findByPk(locationId as string, {
|
||||
attributes: ['id', 'zoneId', 'regionId', 'stateId']
|
||||
});
|
||||
|
||||
while (queue.length > 0) {
|
||||
const currentId = queue.shift()!;
|
||||
const hierarchies = await db.LocationHierarchy.findAll({
|
||||
where: { locationId: currentId }
|
||||
});
|
||||
|
||||
for (const h of hierarchies) {
|
||||
if (!ancestorIds.has(h.parentId)) {
|
||||
ancestorIds.add(h.parentId);
|
||||
queue.push(h.parentId);
|
||||
}
|
||||
}
|
||||
if (district) {
|
||||
const relevantIds = [district.id, district.zoneId, district.regionId, district.stateId].filter(Boolean);
|
||||
whereClause.locationId = { [Op.in]: relevantIds };
|
||||
}
|
||||
whereClause.locationId = { [Op.in]: Array.from(ancestorIds) };
|
||||
}
|
||||
|
||||
// Fetch all locations to build a map if needed
|
||||
const allLocations = await db.Location.findAll({
|
||||
include: [
|
||||
{ model: db.Zone, as: 'zone', attributes: ['name'] },
|
||||
{ model: db.Region, as: 'region', attributes: ['name'] },
|
||||
{ model: db.State, as: 'state', attributes: ['name'] }
|
||||
]
|
||||
});
|
||||
|
||||
const users = await User.findAll({
|
||||
where: whereClause,
|
||||
attributes: { exclude: ['password'] },
|
||||
@ -216,7 +215,62 @@ export const getAllUsers = async (req: Request, res: Response) => {
|
||||
],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
res.json({ success: true, data: users });
|
||||
|
||||
const findAncestor = (locId: string, targetType: string): any => {
|
||||
const queue = [locId];
|
||||
const visited = new Set();
|
||||
while (queue.length > 0) {
|
||||
const id = queue.shift();
|
||||
if (visited.has(id)) continue;
|
||||
visited.add(id);
|
||||
const loc = allLocations.find((l: any) => l.id === id);
|
||||
if (!loc) continue;
|
||||
if (loc.type.toLowerCase() === targetType.toLowerCase()) return loc;
|
||||
if (loc.parents) queue.push(...loc.parents.map((p: any) => p.id));
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const result = users.map((u: any) => {
|
||||
const userJson = u.toJSON();
|
||||
const assignments = userJson.userRoles || [];
|
||||
|
||||
// Consolidate roles and territories with DEEP resolution
|
||||
const territories = assignments.map((a: any) => {
|
||||
const zone = findAncestor(a.locationId, 'zone');
|
||||
const region = findAncestor(a.locationId, 'region');
|
||||
const state = findAncestor(a.locationId, 'state');
|
||||
|
||||
return {
|
||||
role: a.role?.roleName,
|
||||
roleCode: a.role?.roleCode,
|
||||
locationId: a.locationId,
|
||||
locationName: a.location?.name,
|
||||
locationType: a.location?.type,
|
||||
managerCode: a.managerCode,
|
||||
zone: zone?.name,
|
||||
zoneId: zone?.id,
|
||||
region: region?.name,
|
||||
regionId: region?.id,
|
||||
state: state?.name,
|
||||
stateId: state?.id,
|
||||
isActive: a.isActive
|
||||
};
|
||||
});
|
||||
|
||||
userJson.territoryProfile = territories;
|
||||
userJson.allRoles = Array.from(new Set([
|
||||
u.role?.roleCode,
|
||||
u.role?.roleName,
|
||||
...assignments.flatMap((a: any) => [a.role?.roleCode, a.role?.roleName])
|
||||
].filter(Boolean)));
|
||||
userJson.allZones = Array.from(new Set([u.location?.zone?.name, ...territories.map((t: any) => t.zone)].filter(Boolean).map(z => z.toUpperCase())));
|
||||
userJson.allRegions = Array.from(new Set([u.location?.region?.name, ...territories.map((t: any) => t.region)].filter(Boolean).map(r => r.toUpperCase())));
|
||||
|
||||
return userJson;
|
||||
});
|
||||
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
console.error('Get users error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error fetching users' });
|
||||
@ -351,6 +405,8 @@ export const updateUser = async (req: AuthRequest, res: Response) => {
|
||||
mobileNumber, department, designation,
|
||||
locationId,
|
||||
assignments,
|
||||
districts, // New: ASM managed areas/districts
|
||||
asmCode, // New: ASM code to store in managerCode
|
||||
password // Optional password update
|
||||
} = req.body;
|
||||
|
||||
@ -380,7 +436,46 @@ export const updateUser = async (req: AuthRequest, res: Response) => {
|
||||
|
||||
if (Array.isArray(assignments)) {
|
||||
await upsertUserAssignments(id as string, assignments, req.user?.id);
|
||||
} else if (roleCode !== undefined || locationId !== undefined) {
|
||||
} else if (districts && Array.isArray(districts) && (roleCode === 'ASM' || roleCode === 'ZM')) {
|
||||
// Specialized logic for Manager level (ASM/ZM) territory management
|
||||
const targetRole = await Role.findOne({ where: { roleCode: roleCode } });
|
||||
if (!targetRole) throw new Error(`${roleCode} role not found`);
|
||||
|
||||
// 1. DUPLICATION CHECK: Ensure these districts aren't assigned to another active manager of the same role
|
||||
const duplicate = await db.UserRole.findOne({
|
||||
where: {
|
||||
roleId: targetRole.id,
|
||||
locationId: { [Op.in]: districts },
|
||||
userId: { [Op.ne]: id },
|
||||
isActive: true
|
||||
},
|
||||
include: [{ model: db.User, as: 'user', attributes: ['fullName'] }]
|
||||
});
|
||||
|
||||
if (duplicate) {
|
||||
const location = await db.Location.findByPk(duplicate.locationId);
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `Territory "${location?.name}" is already assigned to ${duplicate.user?.fullName}. Duplicate assignments for ${roleCode} are restricted.`
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Transactional Update: Clear old assignments for this role and add new ones
|
||||
await db.UserRole.destroy({ where: { userId: id, roleId: targetRole.id } });
|
||||
|
||||
for (const distId of districts) {
|
||||
await db.UserRole.create({
|
||||
userId: id,
|
||||
roleId: targetRole.id,
|
||||
locationId: distId,
|
||||
managerCode: asmCode || (req.body as any).zmCode || null,
|
||||
isPrimary: false,
|
||||
isActive: true,
|
||||
assignedBy: req.user?.id || null
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (roleCode !== undefined || locationId !== undefined) {
|
||||
const primaryRoleCode = roleCode || user.roleCode;
|
||||
if (primaryRoleCode) {
|
||||
const role = await Role.findOne({ where: { roleCode: primaryRoleCode } });
|
||||
|
||||
@ -2,352 +2,398 @@ import { Request, Response } from 'express';
|
||||
import db from '../../database/models/index.js';
|
||||
const { User } = db;
|
||||
|
||||
// --- Generic Location Fetching ---
|
||||
const getLocationsByType = async (type: string, req: Request, res: Response) => {
|
||||
// --- Districts (Locations) ---
|
||||
export const getDistricts = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const locations = await db.Location.findAll({
|
||||
where: { type },
|
||||
const districts = await db.Location.findAll({
|
||||
include: [
|
||||
{
|
||||
model: db.Location,
|
||||
as: 'parents',
|
||||
through: { attributes: [] }
|
||||
},
|
||||
{
|
||||
model: db.Location,
|
||||
as: 'children',
|
||||
through: { attributes: [] }
|
||||
}
|
||||
{ model: db.Zone, as: 'zone', attributes: ['name'] },
|
||||
{ model: db.Region, as: 'region', attributes: ['name'] },
|
||||
{ model: db.State, as: 'state', attributes: ['name'] },
|
||||
{ model: db.User, as: 'asm', attributes: ['id', 'fullName', 'email'] }
|
||||
],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
res.json({ success: true, data: locations });
|
||||
|
||||
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'
|
||||
}));
|
||||
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
console.error(`Get ${type} list error:`, error);
|
||||
res.status(500).json({ success: false, message: `Error fetching ${type} list` });
|
||||
console.error('Get districts error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error fetching districts' });
|
||||
}
|
||||
};
|
||||
|
||||
// --- Regions ---
|
||||
export const getRegions = async (req: Request, res: Response) => {
|
||||
return getLocationsByType('region', req, res);
|
||||
};
|
||||
|
||||
export const createRegion = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { parentIds, name, childrenIds, regionalManagerId } = req.body;
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({ success: false, message: 'Region name is required' });
|
||||
}
|
||||
|
||||
const region = await db.Location.create({
|
||||
name: name,
|
||||
type: 'region'
|
||||
});
|
||||
|
||||
if (parentIds && Array.isArray(parentIds)) {
|
||||
for (const pid of parentIds) {
|
||||
await db.LocationHierarchy.create({ locationId: region.id, parentId: pid });
|
||||
}
|
||||
}
|
||||
|
||||
if (childrenIds && Array.isArray(childrenIds)) {
|
||||
for (const cid of childrenIds) {
|
||||
await db.LocationHierarchy.create({ locationId: cid, parentId: region.id });
|
||||
}
|
||||
}
|
||||
|
||||
if (regionalManagerId) {
|
||||
await db.User.update({ locationId: region.id }, { where: { id: regionalManagerId } });
|
||||
}
|
||||
|
||||
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' });
|
||||
}
|
||||
};
|
||||
|
||||
// --- Zones ---
|
||||
export const getZones = async (req: Request, res: Response) => {
|
||||
return getLocationsByType('zone', req, res);
|
||||
};
|
||||
|
||||
export const createZone = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { name, childrenIds } = req.body;
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({ success: false, message: 'Zone name is required' });
|
||||
}
|
||||
|
||||
const zone = await db.Location.create({
|
||||
name: name,
|
||||
type: 'zone'
|
||||
});
|
||||
|
||||
if (childrenIds && Array.isArray(childrenIds)) {
|
||||
for (const childId of childrenIds) {
|
||||
await db.LocationHierarchy.create({
|
||||
locationId: childId,
|
||||
parentId: zone.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body.zonalBusinessHeadId) {
|
||||
await db.User.update({ locationId: zone.id }, { where: { id: req.body.zonalBusinessHeadId } });
|
||||
}
|
||||
|
||||
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 updateLocation = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, type, parentIds, childrenIds, zonalBusinessHeadId, regionalManagerId, areaName, pincode, isActive, activeFrom, activeTo, districtId } = req.body;
|
||||
|
||||
const location = await db.Location.findByPk(id);
|
||||
if (!location) {
|
||||
return res.status(404).json({ success: false, message: 'Location not found' });
|
||||
}
|
||||
|
||||
const updates: any = {};
|
||||
if (name) updates.name = name;
|
||||
if (areaName) updates.name = areaName; // Fallback mapping for Area dialog payloads
|
||||
if (type) updates.type = type;
|
||||
if (pincode !== undefined) updates.pincode = pincode;
|
||||
if (isActive !== undefined) updates.isActive = isActive;
|
||||
if (activeFrom !== undefined) updates.activeFrom = activeFrom;
|
||||
if (activeTo !== undefined) updates.activeTo = activeTo;
|
||||
|
||||
await location.update(updates);
|
||||
|
||||
if (parentIds && Array.isArray(parentIds)) {
|
||||
// Re-sync parents (Where this location is the child)
|
||||
await db.LocationHierarchy.destroy({ where: { locationId: id } });
|
||||
for (const pid of parentIds) {
|
||||
await db.LocationHierarchy.create({ locationId: id, parentId: pid });
|
||||
}
|
||||
}
|
||||
|
||||
if (childrenIds && Array.isArray(childrenIds)) {
|
||||
// Re-sync children (Where this location is the parent)
|
||||
await db.LocationHierarchy.destroy({ where: { parentId: id } });
|
||||
for (const cid of childrenIds) {
|
||||
await db.LocationHierarchy.create({ locationId: cid, parentId: id });
|
||||
}
|
||||
}
|
||||
|
||||
// Handling Area Dialog parentId mapping which passes exclusively districtId instead of parentIds array
|
||||
if (districtId) {
|
||||
await db.LocationHierarchy.destroy({ where: { locationId: id } });
|
||||
await db.LocationHierarchy.create({ locationId: id, parentId: districtId });
|
||||
}
|
||||
|
||||
if (zonalBusinessHeadId !== undefined) {
|
||||
const roleCodes = ['ZBH', 'Zonal Business Head'];
|
||||
await db.User.update({ locationId: null }, { where: { locationId: id, roleCode: { [db.Sequelize.Op.in]: roleCodes } } });
|
||||
if (zonalBusinessHeadId !== null) {
|
||||
await db.User.update({ locationId: id }, { where: { id: zonalBusinessHeadId } });
|
||||
}
|
||||
}
|
||||
|
||||
if (regionalManagerId !== undefined) {
|
||||
const roleCodes = ['RM', 'Regional Manager'];
|
||||
await db.User.update({ locationId: null }, { where: { locationId: id, roleCode: { [db.Sequelize.Op.in]: roleCodes } } });
|
||||
if (regionalManagerId !== null) {
|
||||
await db.User.update({ locationId: id }, { where: { id: regionalManagerId } });
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: true, message: 'Location updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Update location error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error updating location' });
|
||||
}
|
||||
};
|
||||
|
||||
// --- States ---
|
||||
export const getStates = async (req: Request, res: Response) => {
|
||||
return getLocationsByType('state', req, res);
|
||||
};
|
||||
|
||||
export const createState = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { zoneId, stateName } = req.body;
|
||||
if (!stateName) return res.status(400).json({ success: false, message: 'State name is required' });
|
||||
|
||||
const state = await db.Location.create({ name: stateName, type: 'state' });
|
||||
|
||||
if (zoneId) {
|
||||
await db.LocationHierarchy.create({ locationId: state.id, parentId: zoneId });
|
||||
}
|
||||
|
||||
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' });
|
||||
}
|
||||
};
|
||||
|
||||
// --- Districts ---
|
||||
export const getDistricts = async (req: Request, res: Response) => {
|
||||
return getLocationsByType('district', req, res);
|
||||
};
|
||||
|
||||
export const createDistrict = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { stateId, districtName } = req.body;
|
||||
if (!districtName) return res.status(400).json({ success: false, message: 'District name is required' });
|
||||
const { name, code, stateId, regionId, zoneId, asmId, description } = req.body;
|
||||
if (!name) return res.status(400).json({ success: false, message: 'District name is required' });
|
||||
|
||||
const district = await db.Location.create({ name: districtName, type: 'district' });
|
||||
const district = await db.Location.create({
|
||||
name,
|
||||
code,
|
||||
stateId,
|
||||
regionId,
|
||||
zoneId,
|
||||
asmId,
|
||||
description
|
||||
});
|
||||
|
||||
if (stateId) {
|
||||
await db.LocationHierarchy.create({ locationId: district.id, parentId: stateId });
|
||||
}
|
||||
|
||||
res.status(201).json({ success: true, message: 'District created', data: district });
|
||||
res.status(201).json({ success: true, data: district });
|
||||
} catch (error) {
|
||||
console.error('Create district error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error creating district' });
|
||||
}
|
||||
};
|
||||
|
||||
// --- Areas ---
|
||||
export const getAreas = async (req: Request, res: Response) => {
|
||||
return getLocationsByType('area', req, res);
|
||||
};
|
||||
|
||||
export const createArea = async (req: Request, res: Response) => {
|
||||
// --- Regions ---
|
||||
export const getRegions = async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Intercept all legacy property keys matching the MasterPage payload.
|
||||
const { districtId, areaName, city, pincode, areaCode, isActive, activeFrom, activeTo } = req.body;
|
||||
if (!areaName) return res.status(400).json({ success: false, message: 'Area name is required' });
|
||||
|
||||
const area = await db.Location.create({
|
||||
name: areaName,
|
||||
type: 'area',
|
||||
code: areaCode,
|
||||
pincode: pincode,
|
||||
isActive: isActive !== undefined ? isActive : true,
|
||||
activeFrom: activeFrom || null,
|
||||
activeTo: activeTo || null
|
||||
const regions = await db.Region.findAll({
|
||||
include: [
|
||||
{ model: db.Zone, as: 'zone', attributes: ['name'] },
|
||||
{ model: db.Location, as: 'districts', attributes: ['id', 'name'] }
|
||||
],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
|
||||
if (districtId) {
|
||||
await db.LocationHierarchy.create({ locationId: area.id, parentId: districtId });
|
||||
}
|
||||
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);
|
||||
|
||||
res.status(201).json({ success: true, message: 'Area created', data: area });
|
||||
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 }, locationId: { [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.districts = (region.districts || []).map((d: any) => ({ id: d.id, name: d.name.toUpperCase() }));
|
||||
return regionJson;
|
||||
}));
|
||||
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
console.error('Create area error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error creating area' });
|
||||
console.error('Get regions error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error fetching regions' });
|
||||
}
|
||||
};
|
||||
|
||||
// --- Managers (Consolidated) ---
|
||||
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
|
||||
const targetDistrictIds = districts || districtIds;
|
||||
if (Array.isArray(targetDistrictIds) && targetDistrictIds.length > 0) {
|
||||
await db.Location.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) {
|
||||
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 { 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
|
||||
const targetDistrictIds = districts || districtIds;
|
||||
if (Array.isArray(targetDistrictIds)) {
|
||||
await db.Location.update({ regionId: null }, { where: { regionId: id } });
|
||||
if (targetDistrictIds.length > 0) {
|
||||
await db.Location.update(
|
||||
{ regionId: id, zoneId: region.zoneId },
|
||||
{ where: { id: { [db.Sequelize.Op.in]: targetDistrictIds } } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: true, message: 'Region updated' });
|
||||
} 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 zones = await db.Zone.findAll({
|
||||
include: [
|
||||
{ model: db.Region, as: 'regions', attributes: ['id', 'name'] },
|
||||
{ model: db.State, as: 'states', attributes: ['id', 'name'] },
|
||||
{ model: db.Location, 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.Location.findAll({
|
||||
where: { zoneId: zone.id, regionId: zmRole.regionId || null }, // 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',
|
||||
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'
|
||||
} : 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.Location.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.Location.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.Location.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.Location.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', 'locationId'],
|
||||
include: [{
|
||||
model: db.Location,
|
||||
as: 'location',
|
||||
attributes: ['id', 'name', 'type'],
|
||||
include: [
|
||||
{
|
||||
model: db.Location,
|
||||
as: 'parents',
|
||||
through: { attributes: [] },
|
||||
attributes: ['id', 'name', 'type'],
|
||||
include: [
|
||||
{
|
||||
model: db.Location,
|
||||
as: 'parents',
|
||||
through: { attributes: [] },
|
||||
attributes: ['id', 'name', 'type'],
|
||||
include: [
|
||||
{
|
||||
model: db.Location,
|
||||
as: 'parents',
|
||||
through: { attributes: [] },
|
||||
attributes: ['id', 'name', 'type'],
|
||||
include: [
|
||||
{
|
||||
model: db.Location,
|
||||
as: 'parents',
|
||||
through: { attributes: [] },
|
||||
attributes: ['id', 'name', 'type']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: db.UserRole,
|
||||
as: 'userRoles',
|
||||
attributes: ['id', 'locationId', 'managerCode', 'isPrimary', 'isActive'],
|
||||
include: [
|
||||
{
|
||||
model: db.Role,
|
||||
as: 'role',
|
||||
attributes: ['id', 'roleCode', 'roleName']
|
||||
},
|
||||
{
|
||||
model: db.Location,
|
||||
as: 'location',
|
||||
attributes: ['id', 'name', 'type'],
|
||||
include: [
|
||||
{
|
||||
model: db.Location,
|
||||
as: 'parents',
|
||||
through: { attributes: [] },
|
||||
attributes: ['id', 'name', 'type']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
const filteredManagers = managers.filter((m: any) => {
|
||||
const assignments = Array.isArray(m.userRoles) ? m.userRoles : [];
|
||||
const hasRole = !roleCode || m.roleCode === roleCode || assignments.some((a: any) => a.role?.roleCode === roleCode);
|
||||
const hasLocation = !locationId || m.locationId === locationId || assignments.some((a: any) => a.locationId === locationId);
|
||||
return hasRole && hasLocation;
|
||||
}).map((m: any) => {
|
||||
const assignments = Array.isArray(m.userRoles) ? m.userRoles : [];
|
||||
const asmAssignments = assignments.filter((a: any) =>
|
||||
(a.role?.roleCode === 'ASM' || m.roleCode === 'ASM') && a.location?.type === 'area'
|
||||
);
|
||||
const asmCode = assignments.find((a: any) =>
|
||||
(a.role?.roleCode === 'ASM' || m.roleCode === 'ASM') && a.managerCode
|
||||
)?.managerCode || null;
|
||||
|
||||
const result = m.toJSON();
|
||||
result.asmCode = asmCode;
|
||||
result.areaManagers = asmAssignments.map((a: any) => ({
|
||||
area: {
|
||||
id: a.location.id,
|
||||
areaName: a.location.name,
|
||||
district: (a.location.parents || []).find((p: any) => p.type === 'district') || null,
|
||||
state: (a.location.parents || []).find((p: any) => p.type === 'state') || null
|
||||
attributes: ['id', 'fullName', 'email', 'mobileNumber', 'roleCode'],
|
||||
include: [
|
||||
{
|
||||
model: db.UserRole,
|
||||
as: 'userRoles',
|
||||
include: [{ model: db.Role, as: 'role' }]
|
||||
}
|
||||
}));
|
||||
return result;
|
||||
]
|
||||
});
|
||||
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.locationId === locationId ||
|
||||
a.zoneId === locationId ||
|
||||
a.regionId === locationId
|
||||
);
|
||||
return hasRole && hasLocation;
|
||||
});
|
||||
|
||||
res.json({ success: true, data: filteredManagers });
|
||||
} catch (error) {
|
||||
console.error('Get managers error:', error);
|
||||
@ -360,14 +406,45 @@ export const getAreaManagers = async (req: Request, res: Response) => {
|
||||
return getManagersByRole(req, res);
|
||||
};
|
||||
|
||||
// --- Delete ---
|
||||
export const deleteLocation = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await db.LocationHierarchy.destroy({ where: { [db.Sequelize.Op.or]: [{ locationId: id }, { parentId: id }] } });
|
||||
await db.Location.destroy({ where: { id } });
|
||||
res.json({ success: true, message: 'Location deleted' });
|
||||
res.json({ success: true, message: 'District deleted' });
|
||||
} catch (error) {
|
||||
console.error('Delete location error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error deleting location' });
|
||||
console.error('Delete district error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error deleting district' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateLocation = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, code, stateId, regionId, zoneId, asmId, isActive, description } = req.body;
|
||||
const district = await db.Location.findByPk(id);
|
||||
if (!district) return res.status(404).json({ success: false, message: 'District not found' });
|
||||
|
||||
await district.update({
|
||||
name,
|
||||
code,
|
||||
stateId,
|
||||
regionId,
|
||||
zoneId,
|
||||
asmId,
|
||||
isActive,
|
||||
description
|
||||
});
|
||||
|
||||
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' });
|
||||
}
|
||||
};
|
||||
|
||||
// --- Semantic Aliases for Backward Compatibility ---
|
||||
export const getAreas = getDistricts;
|
||||
export const createArea = createDistrict;
|
||||
export const deleteArea = deleteLocation;
|
||||
export const createDistrictLegacy = createDistrict; // Just in case
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
import express from 'express';
|
||||
const router = express.Router();
|
||||
import * as masterController from './master.controller.js';
|
||||
import * as outletController from './outlet.controller.js';
|
||||
import { authenticate } from '../../common/middleware/auth.js';
|
||||
import { checkRole } from '../../common/middleware/roleCheck.js';
|
||||
import { ROLES } from '../../common/config/constants.js';
|
||||
|
||||
// States
|
||||
router.get('/states', masterController.getStates);
|
||||
// Districts
|
||||
router.get('/districts', masterController.getDistricts);
|
||||
|
||||
// All routes require authentication
|
||||
router.use(authenticate as any);
|
||||
|
||||
// Regions
|
||||
router.get('/regions', masterController.getRegions);
|
||||
router.post('/regions', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN, ROLES.DD_LEAD]) as any, masterController.createRegion);
|
||||
router.put('/regions/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.updateLocation);
|
||||
|
||||
// Zones
|
||||
router.get('/zones', masterController.getZones);
|
||||
router.post('/zones', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN, ROLES.DD_LEAD]) as any, masterController.createZone);
|
||||
router.put('/zones/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.updateLocation);
|
||||
|
||||
// States (Update only)
|
||||
router.post('/states', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.createState);
|
||||
router.put('/states/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.updateLocation);
|
||||
|
||||
// Districts (Update only)
|
||||
router.post('/districts', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.createDistrict);
|
||||
router.put('/districts/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.updateLocation);
|
||||
|
||||
// Areas
|
||||
router.get('/areas', masterController.getAreas);
|
||||
router.post('/areas', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.createArea);
|
||||
router.put('/areas/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.updateLocation);
|
||||
router.delete('/locations/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, masterController.deleteLocation);
|
||||
|
||||
// Area Managers
|
||||
router.get('/area-managers', masterController.getAreaManagers);
|
||||
|
||||
// Outlets
|
||||
router.get('/outlets', outletController.getOutlets);
|
||||
router.get('/outlets/code/:code', outletController.getOutletByCode);
|
||||
router.get('/outlets/:id', outletController.getOutletById);
|
||||
router.post('/outlets', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, outletController.createOutlet);
|
||||
router.put('/outlets/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, outletController.updateOutlet);
|
||||
|
||||
export default router;
|
||||
Loading…
Reference in New Issue
Block a user