hirarchchy changed
This commit is contained in:
parent
6dc506077f
commit
d20e573d69
16
check_db_roles.ts
Normal file
16
check_db_roles.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import db from './src/database/models/index';
|
||||
const { Role } = db;
|
||||
|
||||
async function check() {
|
||||
try {
|
||||
const roles = await Role.findAll();
|
||||
console.log('--- Database Roles Check ---');
|
||||
console.log('Roles Data:', JSON.stringify(roles, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Check failed:', error);
|
||||
} finally {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
|
||||
check();
|
||||
25
check_db_users.ts
Normal file
25
check_db_users.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import db from './src/database/models/index';
|
||||
const { User } = db;
|
||||
|
||||
async function check() {
|
||||
try {
|
||||
const users = await User.findAll({
|
||||
attributes: ['id', 'roleCode', 'zoneId', 'regionId', 'areaId']
|
||||
});
|
||||
console.log('--- Database User Check ---');
|
||||
console.log('Users Data:', JSON.stringify(users, null, 2));
|
||||
|
||||
const nbhUsers = users.filter((u: any) => u.roleCode === 'NBH');
|
||||
console.log('NBH Users count:', nbhUsers.length);
|
||||
|
||||
const zmUsers = users.filter((u: any) => u.roleCode === 'DD-ZM');
|
||||
console.log('DD-ZM Users count:', zmUsers.length);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Check failed:', error);
|
||||
} finally {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
|
||||
check();
|
||||
39
check_questions.ts
Normal file
39
check_questions.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import db from './src/database/models/index.js';
|
||||
|
||||
async function checkQuestions() {
|
||||
try {
|
||||
const questionnaire = await db.Questionnaire.findOne({
|
||||
where: { isActive: true },
|
||||
include: [{
|
||||
model: db.QuestionnaireQuestion,
|
||||
as: 'questions',
|
||||
include: [{ model: db.QuestionnaireOption, as: 'questionOptions' }]
|
||||
}]
|
||||
});
|
||||
|
||||
if (!questionnaire) {
|
||||
console.log('No active questionnaire found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Active Questionnaire: ${questionnaire.version} (${questionnaire.id})`);
|
||||
|
||||
questionnaire.questions.forEach((q: any) => {
|
||||
console.log(`- [${q.order}] ${q.questionText} (Weight: ${q.weight}, Type: ${q.inputType})`);
|
||||
if (q.questionOptions && q.questionOptions.length > 0) {
|
||||
q.questionOptions.forEach((opt: any) => {
|
||||
console.log(` * ${opt.optionText} (Score: ${opt.score})`);
|
||||
});
|
||||
} else {
|
||||
console.log(` (No options)`);
|
||||
}
|
||||
});
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
checkQuestions();
|
||||
@ -11,6 +11,7 @@
|
||||
"type-check": "tsc --noEmit",
|
||||
"migrate": "tsx scripts/migrate.ts",
|
||||
"seed": "tsx scripts/seed-geo.ts",
|
||||
"seed-normalized": "tsx scripts/seed_normalized_data.ts",
|
||||
"test": "jest",
|
||||
"test:coverage": "jest --coverage",
|
||||
"clear-logs": "rm -rf logs/*.log"
|
||||
|
||||
@ -15,6 +15,7 @@ const rolesToSeed = [
|
||||
{ roleCode: ROLES.FINANCE, roleName: 'Finance', category: 'DEPARTMENT', description: 'Finance Department' },
|
||||
{ roleCode: ROLES.LEGAL_ADMIN, roleName: 'Legal Admin', category: 'DEPARTMENT', description: 'Legal Department' },
|
||||
{ roleCode: ROLES.NBH, roleName: 'NBH', category: 'NATIONAL', description: 'National Business Head' },
|
||||
{ roleCode: ROLES.ASM, roleName: 'ASM', category: 'SALES', description: 'Area Sales Manager' },
|
||||
{ roleCode: ROLES.DEALER, roleName: 'Dealer', category: 'EXTERNAL', description: 'Dealer Principal' }
|
||||
];
|
||||
|
||||
|
||||
97
scripts/seed_normalized_data.ts
Normal file
97
scripts/seed_normalized_data.ts
Normal file
@ -0,0 +1,97 @@
|
||||
|
||||
import db from '../src/database/models/index.js';
|
||||
const { Role, Location, LocationHierarchy, User, UserRole, Permission } = db;
|
||||
|
||||
async function seed() {
|
||||
console.log('--- Seeding Normalized Graph Data ---');
|
||||
|
||||
// 1. Create 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 Lead', roleName: 'DD Lead', category: 'ZONAL' },
|
||||
{ roleCode: 'RBM', roleName: 'Regional Business 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' }
|
||||
];
|
||||
|
||||
for (const r of roles) {
|
||||
await Role.findOrCreate({ where: { roleCode: r.roleCode }, defaults: r });
|
||||
}
|
||||
console.log('Roles seeded.');
|
||||
|
||||
// 2. Create Locations
|
||||
const zone1 = await Location.create({ name: 'North Zone', type: 'zone' });
|
||||
const region1 = await Location.create({ name: 'Delhi Region', type: 'region' });
|
||||
const area1 = await Location.create({ name: 'South Delhi Area', type: 'area' });
|
||||
|
||||
const zone2 = await Location.create({ name: 'South Zone', type: 'zone' });
|
||||
const region2 = await Location.create({ name: 'Bangalore Region', type: 'region' });
|
||||
|
||||
console.log('Locations created.');
|
||||
|
||||
// 3. Create Hierarchies (Bridge Table)
|
||||
await LocationHierarchy.create({ locationId: region1.id, parentId: zone1.id });
|
||||
await LocationHierarchy.create({ locationId: area1.id, parentId: region1.id });
|
||||
|
||||
// Example of multiple parents if needed
|
||||
// await LocationHierarchy.create({ locationId: area1.id, parentId: someOtherParent.id });
|
||||
|
||||
await LocationHierarchy.create({ locationId: region2.id, parentId: zone2.id });
|
||||
|
||||
console.log('Hierarchies seeded.');
|
||||
|
||||
// 4. Create Users and Map them
|
||||
// NBH (Global)
|
||||
const nbhUser = await User.findOrCreate({
|
||||
where: { email: 'nbh@example.com' },
|
||||
defaults: { fullName: 'National Head', roleCode: 'NBH' }
|
||||
});
|
||||
await UserRole.create({ userId: nbhUser[0].id, roleId: (await Role.findOne({ where: { roleCode: 'NBH' } })).id });
|
||||
|
||||
// ZBH (North Zone)
|
||||
const zbhUser = await User.findOrCreate({
|
||||
where: { email: 'zbh.north@example.com' },
|
||||
defaults: { fullName: 'North Zonal Head', roleCode: 'ZBH' }
|
||||
});
|
||||
const zbhRole = await Role.findOne({ where: { roleCode: 'ZBH' } });
|
||||
await UserRole.create({
|
||||
userId: zbhUser[0].id,
|
||||
roleId: zbhRole.id,
|
||||
locationId: zone1.id
|
||||
});
|
||||
|
||||
// RBM (Delhi Region)
|
||||
const rbmUser = await User.findOrCreate({
|
||||
where: { email: 'rbm.delhi@example.com' },
|
||||
defaults: { fullName: 'Delhi Regional Manager', roleCode: 'RBM' }
|
||||
});
|
||||
const rbmRole = await Role.findOne({ where: { roleCode: 'RBM' } });
|
||||
await UserRole.create({
|
||||
userId: rbmUser[0].id,
|
||||
roleId: rbmRole.id,
|
||||
locationId: region1.id
|
||||
});
|
||||
|
||||
// ASM (South Delhi Area)
|
||||
const asmUser = await User.findOrCreate({
|
||||
where: { email: 'asm.sdelhi@example.com' },
|
||||
defaults: { fullName: 'South Delhi ASM', roleCode: 'ASM' }
|
||||
});
|
||||
const asmRole = await Role.findOne({ where: { roleCode: 'ASM' } });
|
||||
await UserRole.create({
|
||||
userId: asmUser[0].id,
|
||||
roleId: asmRole.id,
|
||||
locationId: area1.id
|
||||
});
|
||||
|
||||
console.log('Users and Mappings seeded.');
|
||||
console.log('--- Seeding Complete ---');
|
||||
}
|
||||
|
||||
seed().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}).then(() => process.exit(0));
|
||||
@ -10,8 +10,7 @@ export const generateToken = (user: any): string => {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
role: user.roleCode,
|
||||
region: user.regionId,
|
||||
zone: user.zoneId
|
||||
locationId: user.locationId
|
||||
};
|
||||
|
||||
return jwt.sign(payload, JWT_SECRET, {
|
||||
|
||||
@ -11,6 +11,7 @@ export const ROLES = {
|
||||
LEGAL_ADMIN: 'Legal Admin',
|
||||
SUPER_ADMIN: 'Super Admin',
|
||||
DD_AM: 'DD AM',
|
||||
ASM: 'ASM',
|
||||
FINANCE: 'Finance',
|
||||
DEALER: 'Dealer'
|
||||
} as const;
|
||||
|
||||
@ -34,9 +34,7 @@ export interface ApplicationAttributes {
|
||||
architectureAssignedTo: string | null;
|
||||
architectureStatus: string | null;
|
||||
submittedBy: string | null;
|
||||
zoneId: string | null;
|
||||
regionId: string | null;
|
||||
areaId: string | null;
|
||||
locationId: string | null;
|
||||
architectureAssignedDate: Date | null;
|
||||
architectureDocumentDate: Date | null;
|
||||
architectureCompletionDate: Date | null;
|
||||
@ -202,27 +200,11 @@ export default (sequelize: Sequelize) => {
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
zoneId: {
|
||||
locationId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'regions',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
areaId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'areas',
|
||||
model: 'locations',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
@ -263,9 +245,7 @@ export default (sequelize: Sequelize) => {
|
||||
Application.belongsTo(models.User, { foreignKey: 'assignedTo', as: 'assignee' });
|
||||
Application.belongsTo(models.User, { foreignKey: 'architectureAssignedTo', as: 'architectureAssignee' });
|
||||
Application.belongsTo(models.Opportunity, { foreignKey: 'opportunityId', as: 'opportunity' });
|
||||
Application.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' });
|
||||
Application.belongsTo(models.Region, { foreignKey: 'regionId', as: 'region' });
|
||||
Application.belongsTo(models.Area, { foreignKey: 'areaId', as: 'area' });
|
||||
Application.belongsTo(models.Location, { foreignKey: 'locationId', as: 'location' });
|
||||
|
||||
Application.hasMany(models.ApplicationStatusHistory, { foreignKey: 'applicationId', as: 'statusHistory' });
|
||||
Application.hasMany(models.ApplicationProgress, { foreignKey: 'applicationId', as: 'progressTracking' });
|
||||
|
||||
@ -1,141 +0,0 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface AreaAttributes {
|
||||
id: string;
|
||||
regionId: string;
|
||||
stateId: string;
|
||||
zoneId: string;
|
||||
districtId: string;
|
||||
managerId: string | null;
|
||||
areaCode: string;
|
||||
areaName: string;
|
||||
city: string | null;
|
||||
pincode: string | null;
|
||||
isActive: boolean;
|
||||
activeFrom?: string | null;
|
||||
activeTo?: string | null;
|
||||
}
|
||||
|
||||
export interface AreaInstance extends Model<AreaAttributes>, AreaAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const Area = sequelize.define<AreaInstance>('Area', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'regions',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
stateId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'states',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
zoneId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
districtId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'districts',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
managerId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
areaCode: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false
|
||||
},
|
||||
areaName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
city: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
pincode: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
},
|
||||
activeFrom: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true
|
||||
},
|
||||
activeTo: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'areas',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
(Area as any).associate = (models: any) => {
|
||||
Area.belongsTo(models.Region, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'region'
|
||||
});
|
||||
Area.belongsTo(models.State, {
|
||||
foreignKey: 'stateId',
|
||||
as: 'state'
|
||||
});
|
||||
Area.belongsTo(models.Zone, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'zone'
|
||||
});
|
||||
Area.belongsTo(models.District, {
|
||||
foreignKey: 'districtId',
|
||||
as: 'district'
|
||||
});
|
||||
Area.belongsTo(models.User, {
|
||||
foreignKey: 'managerId',
|
||||
as: 'manager'
|
||||
});
|
||||
Area.hasMany(models.Application, {
|
||||
foreignKey: 'areaId',
|
||||
as: 'applications'
|
||||
});
|
||||
// Dedicated Manager Table Associations
|
||||
Area.hasMany(models.AreaManager, {
|
||||
foreignKey: 'areaId',
|
||||
as: 'areaManagers'
|
||||
});
|
||||
Area.belongsToMany(models.User, {
|
||||
through: models.AreaManager,
|
||||
foreignKey: 'areaId',
|
||||
otherKey: 'userId',
|
||||
as: 'assignedManagers'
|
||||
});
|
||||
};
|
||||
|
||||
return Area;
|
||||
};
|
||||
@ -1,72 +0,0 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface AreaManagerAttributes {
|
||||
id: string;
|
||||
areaId: string;
|
||||
userId: string;
|
||||
managerType: string;
|
||||
asmCode?: string;
|
||||
isActive: boolean;
|
||||
assignedAt: Date;
|
||||
}
|
||||
|
||||
export interface AreaManagerInstance extends Model<AreaManagerAttributes>, AreaManagerAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const AreaManager = sequelize.define<AreaManagerInstance>('AreaManager', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
areaId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'areas',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
managerType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
asmCode: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
},
|
||||
assignedAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
tableName: 'area_managers',
|
||||
timestamps: true,
|
||||
updatedAt: false
|
||||
});
|
||||
|
||||
(AreaManager as any).associate = (models: any) => {
|
||||
AreaManager.belongsTo(models.Area, {
|
||||
foreignKey: 'areaId',
|
||||
as: 'area'
|
||||
});
|
||||
AreaManager.belongsTo(models.User, {
|
||||
foreignKey: 'userId',
|
||||
as: 'user'
|
||||
});
|
||||
};
|
||||
|
||||
return AreaManager;
|
||||
};
|
||||
@ -1,78 +0,0 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface DistrictAttributes {
|
||||
id: string;
|
||||
stateId: string;
|
||||
zoneId: string;
|
||||
regionId: string;
|
||||
districtName: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface DistrictInstance extends Model<DistrictAttributes>, DistrictAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const District = sequelize.define<DistrictInstance>('District', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
stateId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'states',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
zoneId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'regions',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
districtName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'districts',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
(District as any).associate = (models: any) => {
|
||||
District.belongsTo(models.State, {
|
||||
foreignKey: 'stateId',
|
||||
as: 'state'
|
||||
});
|
||||
District.belongsTo(models.Zone, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'zone'
|
||||
});
|
||||
District.belongsTo(models.Region, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'region'
|
||||
});
|
||||
District.hasMany(models.Area, {
|
||||
foreignKey: 'districtId',
|
||||
as: 'areas'
|
||||
});
|
||||
};
|
||||
|
||||
return District;
|
||||
};
|
||||
@ -1,67 +0,0 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface DistrictManagerAttributes {
|
||||
id: string;
|
||||
districtId: string;
|
||||
userId: string;
|
||||
managerType: string;
|
||||
isActive: boolean;
|
||||
assignedAt: Date;
|
||||
}
|
||||
|
||||
export interface DistrictManagerInstance extends Model<DistrictManagerAttributes>, DistrictManagerAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const DistrictManager = sequelize.define<DistrictManagerInstance>('DistrictManager', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
districtId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'districts',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
managerType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
},
|
||||
assignedAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
tableName: 'district_managers',
|
||||
timestamps: true,
|
||||
updatedAt: false
|
||||
});
|
||||
|
||||
(DistrictManager as any).associate = (models: any) => {
|
||||
DistrictManager.belongsTo(models.District, {
|
||||
foreignKey: 'districtId',
|
||||
as: 'district'
|
||||
});
|
||||
DistrictManager.belongsTo(models.User, {
|
||||
foreignKey: 'userId',
|
||||
as: 'user'
|
||||
});
|
||||
};
|
||||
|
||||
return DistrictManager;
|
||||
};
|
||||
51
src/database/models/Location.ts
Normal file
51
src/database/models/Location.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface LocationAttributes {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'zone' | 'region' | 'area' | 'state' | 'district';
|
||||
}
|
||||
|
||||
export interface LocationInstance extends Model<LocationAttributes>, LocationAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const Location = sequelize.define<LocationInstance>('Location', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('zone', 'region', 'area', 'state', 'district'),
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
tableName: 'locations',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
(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.hasMany(models.UserRole, { foreignKey: 'locationId', as: 'userRoles' });
|
||||
Location.hasMany(models.Application, { foreignKey: 'locationId', as: 'applications' });
|
||||
};
|
||||
|
||||
return Location;
|
||||
};
|
||||
51
src/database/models/LocationHierarchy.ts
Normal file
51
src/database/models/LocationHierarchy.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface LocationHierarchyAttributes {
|
||||
id: string;
|
||||
locationId: string;
|
||||
parentId: string;
|
||||
relationshipType: string | null;
|
||||
}
|
||||
|
||||
export interface LocationHierarchyInstance extends Model<LocationHierarchyAttributes>, LocationHierarchyAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const LocationHierarchy = sequelize.define<LocationHierarchyInstance>('LocationHierarchy', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
locationId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'locations',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
parentId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'locations',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
relationshipType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: 'direct'
|
||||
}
|
||||
}, {
|
||||
tableName: 'location_hierarchies',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
(LocationHierarchy as any).associate = (models: any) => {
|
||||
LocationHierarchy.belongsTo(models.Location, { foreignKey: 'locationId', as: 'location' });
|
||||
LocationHierarchy.belongsTo(models.Location, { foreignKey: 'parentId', as: 'parent' });
|
||||
};
|
||||
|
||||
return LocationHierarchy;
|
||||
};
|
||||
@ -2,10 +2,7 @@ import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface OpportunityAttributes {
|
||||
id: string;
|
||||
zoneId: string;
|
||||
regionId: string;
|
||||
stateId: string | null;
|
||||
districtId: string | null;
|
||||
locationId: string;
|
||||
city: string;
|
||||
opportunityType: string;
|
||||
capacity: string;
|
||||
@ -26,35 +23,11 @@ export default (sequelize: Sequelize) => {
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
zoneId: {
|
||||
locationId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'regions',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
stateId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'states',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
districtId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'districts',
|
||||
model: 'locations',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
@ -104,10 +77,7 @@ export default (sequelize: Sequelize) => {
|
||||
});
|
||||
|
||||
(Opportunity as any).associate = (models: any) => {
|
||||
Opportunity.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' });
|
||||
Opportunity.belongsTo(models.Region, { foreignKey: 'regionId', as: 'region' });
|
||||
Opportunity.belongsTo(models.State, { foreignKey: 'stateId', as: 'state' });
|
||||
Opportunity.belongsTo(models.District, { foreignKey: 'districtId', as: 'district' });
|
||||
Opportunity.belongsTo(models.Location, { foreignKey: 'locationId', as: 'location' });
|
||||
Opportunity.belongsTo(models.User, { foreignKey: 'createdBy', as: 'creator' });
|
||||
Opportunity.hasMany(models.Application, { foreignKey: 'opportunityId', as: 'applications' });
|
||||
};
|
||||
|
||||
@ -15,8 +15,7 @@ export interface OutletAttributes {
|
||||
status: typeof OUTLET_STATUS[keyof typeof OUTLET_STATUS];
|
||||
establishedDate: string;
|
||||
dealerId: string;
|
||||
region: typeof REGIONS[keyof typeof REGIONS];
|
||||
zone: string;
|
||||
locationId: string;
|
||||
}
|
||||
|
||||
export interface OutletInstance extends Model<OutletAttributes>, OutletAttributes { }
|
||||
@ -81,13 +80,13 @@ export default (sequelize: Sequelize) => {
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
region: {
|
||||
type: DataTypes.ENUM(...Object.values(REGIONS)),
|
||||
allowNull: false
|
||||
},
|
||||
zone: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
locationId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'locations',
|
||||
key: 'id'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
tableName: 'outlets',
|
||||
@ -97,8 +96,7 @@ export default (sequelize: Sequelize) => {
|
||||
{ fields: ['dealerId'] },
|
||||
{ fields: ['type'] },
|
||||
{ fields: ['status'] },
|
||||
{ fields: ['region'] },
|
||||
{ fields: ['zone'] }
|
||||
{ fields: ['locationId'] }
|
||||
]
|
||||
});
|
||||
|
||||
@ -107,6 +105,10 @@ export default (sequelize: Sequelize) => {
|
||||
foreignKey: 'dealerId',
|
||||
as: 'dealer'
|
||||
});
|
||||
Outlet.belongsTo(models.Location, {
|
||||
foreignKey: 'locationId',
|
||||
as: 'location'
|
||||
});
|
||||
Outlet.hasMany(models.Resignation, {
|
||||
foreignKey: 'outletId',
|
||||
as: 'resignations'
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface RegionAttributes {
|
||||
id: string;
|
||||
zoneId: string;
|
||||
// stateId: string | null; // Removed as Region covers multiple states
|
||||
regionalManagerId: string | null;
|
||||
regionCode: string;
|
||||
regionName: string;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
zoneId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
// stateId: {
|
||||
// type: DataTypes.UUID,
|
||||
// allowNull: true,
|
||||
// references: {
|
||||
// model: 'states',
|
||||
// key: 'id'
|
||||
// }
|
||||
// },
|
||||
regionalManagerId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
regionCode: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false
|
||||
},
|
||||
regionName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'regions',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
(Region as any).associate = (models: any) => {
|
||||
Region.belongsTo(models.Zone, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'zone'
|
||||
});
|
||||
// Region.belongsTo(models.State, {
|
||||
// foreignKey: 'stateId',
|
||||
// as: 'state'
|
||||
// });
|
||||
Region.hasMany(models.State, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'states'
|
||||
});
|
||||
Region.hasMany(models.Area, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'areas'
|
||||
});
|
||||
Region.hasMany(models.RegionManager, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'managers'
|
||||
});
|
||||
Region.hasMany(models.Application, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'applications'
|
||||
});
|
||||
Region.belongsTo(models.User, {
|
||||
foreignKey: 'regionalManagerId',
|
||||
as: 'regionalManager'
|
||||
});
|
||||
};
|
||||
|
||||
return Region;
|
||||
};
|
||||
@ -1,67 +0,0 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface RegionManagerAttributes {
|
||||
id: string;
|
||||
regionId: string;
|
||||
userId: string;
|
||||
managerType: string;
|
||||
isActive: boolean;
|
||||
assignedAt: Date;
|
||||
}
|
||||
|
||||
export interface RegionManagerInstance extends Model<RegionManagerAttributes>, RegionManagerAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const RegionManager = sequelize.define<RegionManagerInstance>('RegionManager', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'regions',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
managerType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
},
|
||||
assignedAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
tableName: 'region_managers',
|
||||
timestamps: true,
|
||||
updatedAt: false
|
||||
});
|
||||
|
||||
(RegionManager as any).associate = (models: any) => {
|
||||
RegionManager.belongsTo(models.Region, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'region'
|
||||
});
|
||||
RegionManager.belongsTo(models.User, {
|
||||
foreignKey: 'userId',
|
||||
as: 'user'
|
||||
});
|
||||
};
|
||||
|
||||
return RegionManager;
|
||||
};
|
||||
@ -1,70 +0,0 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface StateAttributes {
|
||||
id: string;
|
||||
stateName: string;
|
||||
zoneId: string;
|
||||
regionId: string | null;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
stateName: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false
|
||||
},
|
||||
zoneId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'regions',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'states',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
(State as any).associate = (models: any) => {
|
||||
State.belongsTo(models.Zone, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'zone'
|
||||
});
|
||||
State.belongsTo(models.Region, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'region'
|
||||
});
|
||||
State.hasMany(models.District, {
|
||||
foreignKey: 'stateId',
|
||||
as: 'districts'
|
||||
});
|
||||
State.hasMany(models.Region, {
|
||||
foreignKey: 'stateId',
|
||||
as: 'regions'
|
||||
});
|
||||
};
|
||||
|
||||
return State;
|
||||
};
|
||||
@ -11,11 +11,7 @@ export interface UserAttributes {
|
||||
department: string | null;
|
||||
designation: string | null;
|
||||
roleCode: string | null;
|
||||
zoneId: string | null;
|
||||
regionId: string | null;
|
||||
stateId: string | null;
|
||||
districtId: string | null;
|
||||
areaId: string | null;
|
||||
locationId: string | null;
|
||||
dealerId: string | null;
|
||||
isActive: boolean;
|
||||
isExternal: boolean;
|
||||
@ -70,43 +66,11 @@ export default (sequelize: Sequelize) => {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
zoneId: {
|
||||
locationId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'regions',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
stateId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'states',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
districtId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'districts',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
areaId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'areas',
|
||||
model: 'locations',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
@ -152,14 +116,9 @@ export default (sequelize: Sequelize) => {
|
||||
});
|
||||
User.hasMany(models.UserRole, { foreignKey: 'userId', as: 'userRoles' });
|
||||
User.hasMany(models.UserRole, { foreignKey: 'assignedBy', as: 'assignedRoles' });
|
||||
User.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' });
|
||||
User.belongsTo(models.Region, { foreignKey: 'regionId', as: 'region' });
|
||||
User.belongsTo(models.State, { foreignKey: 'stateId', as: 'state' });
|
||||
User.belongsTo(models.District, { foreignKey: 'districtId', as: 'district' });
|
||||
User.belongsTo(models.Area, { foreignKey: 'areaId', as: 'area' });
|
||||
User.belongsTo(models.Location, { foreignKey: 'locationId', as: 'location' });
|
||||
|
||||
User.hasMany(models.AuditLog, { foreignKey: 'userId', as: 'auditLogs' });
|
||||
User.hasMany(models.AreaManager, { foreignKey: 'userId', as: 'areaManagers' });
|
||||
User.belongsTo(models.Dealer, { foreignKey: 'dealerId', as: 'dealerProfile' });
|
||||
};
|
||||
|
||||
|
||||
@ -4,9 +4,7 @@ export interface UserRoleAttributes {
|
||||
id: string;
|
||||
userId: string;
|
||||
roleId: string;
|
||||
zoneId: string | null;
|
||||
regionId: string | null;
|
||||
areaId: string | null;
|
||||
locationId: string | null;
|
||||
assignedAt: Date;
|
||||
assignedBy: string | null;
|
||||
}
|
||||
@ -36,27 +34,11 @@ export default (sequelize: Sequelize) => {
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
zoneId: {
|
||||
locationId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'regions',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
areaId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'areas',
|
||||
model: 'locations',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
@ -87,17 +69,9 @@ export default (sequelize: Sequelize) => {
|
||||
foreignKey: 'roleId',
|
||||
as: 'role'
|
||||
});
|
||||
UserRole.belongsTo(models.Zone, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'zone'
|
||||
});
|
||||
UserRole.belongsTo(models.Region, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'region'
|
||||
});
|
||||
UserRole.belongsTo(models.Area, {
|
||||
foreignKey: 'areaId',
|
||||
as: 'area'
|
||||
UserRole.belongsTo(models.Location, {
|
||||
foreignKey: 'locationId',
|
||||
as: 'location'
|
||||
});
|
||||
UserRole.belongsTo(models.User, {
|
||||
foreignKey: 'assignedBy',
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface ZoneAttributes {
|
||||
id: string;
|
||||
zoneCode: string;
|
||||
zoneName: string;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
zonalBusinessHeadId: 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
|
||||
},
|
||||
zoneCode: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false
|
||||
},
|
||||
zoneName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
},
|
||||
zonalBusinessHeadId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
tableName: 'zones',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
(Zone as any).associate = (models: any) => {
|
||||
Zone.belongsTo(models.User, {
|
||||
foreignKey: 'zonalBusinessHeadId',
|
||||
as: 'zonalBusinessHead'
|
||||
});
|
||||
Zone.hasMany(models.Region, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'regions'
|
||||
});
|
||||
Zone.hasMany(models.State, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'states'
|
||||
});
|
||||
Zone.hasMany(models.ZoneManager, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'managers'
|
||||
});
|
||||
Zone.hasMany(models.Application, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'applications'
|
||||
});
|
||||
};
|
||||
|
||||
return Zone;
|
||||
};
|
||||
@ -1,67 +0,0 @@
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface ZoneManagerAttributes {
|
||||
id: string;
|
||||
zoneId: string;
|
||||
userId: string;
|
||||
managerType: string;
|
||||
isActive: boolean;
|
||||
assignedAt: Date;
|
||||
}
|
||||
|
||||
export interface ZoneManagerInstance extends Model<ZoneManagerAttributes>, ZoneManagerAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const ZoneManager = sequelize.define<ZoneManagerInstance>('ZoneManager', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
zoneId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'zones',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
managerType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
},
|
||||
assignedAt: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
tableName: 'zone_managers',
|
||||
timestamps: true,
|
||||
updatedAt: false
|
||||
});
|
||||
|
||||
(ZoneManager as any).associate = (models: any) => {
|
||||
ZoneManager.belongsTo(models.Zone, {
|
||||
foreignKey: 'zoneId',
|
||||
as: 'zone'
|
||||
});
|
||||
ZoneManager.belongsTo(models.User, {
|
||||
foreignKey: 'userId',
|
||||
as: 'user'
|
||||
});
|
||||
};
|
||||
|
||||
return ZoneManager;
|
||||
};
|
||||
@ -14,26 +14,19 @@ import createAuditLog from './AuditLog.js';
|
||||
import createFinancePayment from './FinancePayment.js';
|
||||
import createFnF from './FnF.js';
|
||||
import createFnFLineItem from './FnFLineItem.js';
|
||||
import createRegion from './Region.js';
|
||||
import createZone from './Zone.js';
|
||||
import createSLAConfiguration from './SLAConfiguration.js';
|
||||
import createSLAReminder from './SLAReminder.js';
|
||||
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';
|
||||
|
||||
// Batch 1: Organizational Hierarchy & User Management
|
||||
import createRole from './Role.js';
|
||||
import createPermission from './Permission.js';
|
||||
import createRolePermission from './RolePermission.js';
|
||||
import createState from './State.js';
|
||||
import createDistrict from './District.js';
|
||||
import createArea from './Area.js';
|
||||
import createUserRole from './UserRole.js';
|
||||
import createZoneManager from './ZoneManager.js';
|
||||
import createRegionManager from './RegionManager.js';
|
||||
import createAreaManager from './AreaManager.js';
|
||||
import createDistrictManager from './DistrictManager.js';
|
||||
|
||||
// Batch 2: Opportunity & Application Framework
|
||||
import createOpportunity from './Opportunity.js';
|
||||
@ -121,26 +114,19 @@ db.AuditLog = createAuditLog(sequelize);
|
||||
db.FinancePayment = createFinancePayment(sequelize);
|
||||
db.FnF = createFnF(sequelize);
|
||||
db.FnFLineItem = createFnFLineItem(sequelize);
|
||||
db.Region = createRegion(sequelize);
|
||||
db.Zone = createZone(sequelize);
|
||||
db.SLAConfiguration = createSLAConfiguration(sequelize);
|
||||
db.SLAReminder = createSLAReminder(sequelize);
|
||||
db.SLAEscalationConfig = createSLAEscalationConfig(sequelize);
|
||||
db.WorkflowStageConfig = createWorkflowStageConfig(sequelize);
|
||||
db.Notification = createNotification(sequelize);
|
||||
db.Location = createLocation(sequelize);
|
||||
db.LocationHierarchy = createLocationHierarchy(sequelize);
|
||||
|
||||
// Batch 1: Organizational Hierarchy & User Management
|
||||
db.Role = createRole(sequelize);
|
||||
db.Permission = createPermission(sequelize);
|
||||
db.RolePermission = createRolePermission(sequelize);
|
||||
db.State = createState(sequelize);
|
||||
db.District = createDistrict(sequelize);
|
||||
db.Area = createArea(sequelize);
|
||||
db.UserRole = createUserRole(sequelize);
|
||||
db.ZoneManager = createZoneManager(sequelize);
|
||||
db.RegionManager = createRegionManager(sequelize);
|
||||
db.AreaManager = createAreaManager(sequelize);
|
||||
db.DistrictManager = createDistrictManager(sequelize);
|
||||
|
||||
// Batch 2: Opportunity & Application Framework
|
||||
db.Opportunity = createOpportunity(sequelize);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { Op } from 'sequelize';
|
||||
import db from '../../database/models/index.js';
|
||||
const { Role, Permission, RolePermission, User, DealerCode, AuditLog } = db;
|
||||
import { AUDIT_ACTIONS } from '../../common/config/constants.js';
|
||||
@ -120,7 +121,45 @@ export const getPermissions = async (req: Request, res: Response) => {
|
||||
|
||||
export const getAllUsers = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { roleCode, locationId } = req.query;
|
||||
const whereClause: any = {};
|
||||
|
||||
if (roleCode) {
|
||||
// Handle both single string and array of role codes (if passed as multiple params)
|
||||
if (Array.isArray(roleCode)) {
|
||||
whereClause.roleCode = { [Op.in]: roleCode };
|
||||
} else {
|
||||
whereClause.roleCode = roleCode;
|
||||
}
|
||||
}
|
||||
|
||||
const nationalRoles = ['NBH', 'DD Head', 'Super Admin'];
|
||||
const isNationalRole = (typeof roleCode === 'string' && nationalRoles.includes(roleCode)) ||
|
||||
(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];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
whereClause.locationId = { [Op.in]: Array.from(ancestorIds) };
|
||||
}
|
||||
|
||||
const users = await User.findAll({
|
||||
where: whereClause,
|
||||
attributes: { exclude: ['password'] },
|
||||
include: [
|
||||
{
|
||||
@ -134,9 +173,7 @@ export const getAllUsers = async (req: Request, res: Response) => {
|
||||
}
|
||||
]
|
||||
},
|
||||
{ model: db.Zone, as: 'zone' },
|
||||
{ model: db.Region, as: 'region' },
|
||||
{ model: db.Area, as: 'area' }
|
||||
{ model: db.Location, as: 'location' }
|
||||
],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
@ -152,9 +189,10 @@ export const createUser = async (req: AuthRequest, res: Response) => {
|
||||
const {
|
||||
fullName, email, roleCode,
|
||||
employeeId, mobileNumber, department, designation,
|
||||
zoneId, regionId, stateId, districtId, areaId
|
||||
locationId
|
||||
} = req.body;
|
||||
|
||||
|
||||
// Validate required fields
|
||||
if (!fullName || !email || !roleCode) {
|
||||
return res.status(400).json({
|
||||
@ -198,11 +236,7 @@ export const createUser = async (req: AuthRequest, res: Response) => {
|
||||
mobileNumber,
|
||||
department,
|
||||
designation,
|
||||
zoneId,
|
||||
regionId,
|
||||
stateId,
|
||||
districtId,
|
||||
areaId
|
||||
locationId
|
||||
});
|
||||
|
||||
await AuditLog.create({
|
||||
@ -259,7 +293,7 @@ export const updateUser = async (req: AuthRequest, res: Response) => {
|
||||
const {
|
||||
fullName, email, roleCode, status, isActive, employeeId,
|
||||
mobileNumber, department, designation,
|
||||
zoneId, regionId, stateId, districtId, areaId,
|
||||
locationId,
|
||||
password // Optional password update
|
||||
} = req.body;
|
||||
|
||||
@ -277,11 +311,7 @@ export const updateUser = async (req: AuthRequest, res: Response) => {
|
||||
mobileNumber: mobileNumber || user.mobileNumber,
|
||||
department: department || user.department,
|
||||
designation: designation || user.designation,
|
||||
zoneId: zoneId !== undefined ? zoneId : user.zoneId,
|
||||
regionId: regionId !== undefined ? regionId : user.regionId,
|
||||
stateId: stateId !== undefined ? stateId : user.stateId,
|
||||
districtId: districtId !== undefined ? districtId : user.districtId,
|
||||
areaId: areaId !== undefined ? areaId : user.areaId
|
||||
locationId: (locationId === '' ? null : (locationId !== undefined ? locationId : user.locationId))
|
||||
};
|
||||
|
||||
// If password is provided, hash it and update
|
||||
@ -319,7 +349,7 @@ export const updateUser = async (req: AuthRequest, res: Response) => {
|
||||
|
||||
export const generateDealerCode = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { regionId, stateId, channel } = req.body;
|
||||
const { locationId, channel } = req.body;
|
||||
// Logic to generate unique code based on format (e.g., RE-[Region]-[State]-[Seq])
|
||||
// This is a placeholder for the actual business logic
|
||||
|
||||
|
||||
@ -587,35 +587,34 @@ export const updateInterviewDecision = async (req: AuthRequest, res: Response) =
|
||||
});
|
||||
}
|
||||
|
||||
// Always mark interview as completed when a decision is made
|
||||
// This ensures action buttons hide for the user
|
||||
await interview.update({ status: 'Completed' });
|
||||
// --- Multi-Interviewer Synchronization ---
|
||||
// Fetch all assigned participants for this interview
|
||||
const participants = await db.InterviewParticipant.findAll({
|
||||
where: { interviewId }
|
||||
});
|
||||
|
||||
// Update Application Status
|
||||
if (decision === 'Rejected') {
|
||||
await db.Application.update({
|
||||
overallStatus: 'Rejected',
|
||||
currentStage: 'Rejected'
|
||||
}, { where: { id: interview.applicationId } });
|
||||
// Fetch all evaluations submitted for this interview
|
||||
const evaluations = await db.InterviewEvaluation.findAll({
|
||||
where: { interviewId }
|
||||
});
|
||||
|
||||
// Log Status History
|
||||
await db.ApplicationStatusHistory.create({
|
||||
applicationId: interview.applicationId,
|
||||
previousStatus: 'Interview Pending',
|
||||
newStatus: 'Rejected',
|
||||
changedBy: req.user?.id,
|
||||
reason: remarks || 'Interview Rejected'
|
||||
});
|
||||
} else {
|
||||
// Determine next status based on current level
|
||||
const isFullyEvaluated = evaluations.length >= participants.length;
|
||||
|
||||
if (isFullyEvaluated) {
|
||||
// All interviewers have responded
|
||||
await interview.update({ status: 'Completed' });
|
||||
|
||||
// Determine next status based on level
|
||||
const nextStatusMap: any = {
|
||||
1: 'Level 1 Approved',
|
||||
2: 'Level 2 Approved',
|
||||
3: 'Level 3 Approved'
|
||||
};
|
||||
|
||||
// Check if any interviewer rejected (for logging/metadata, though we still move forward as requested)
|
||||
const hasRejection = evaluations.some((e: any) => e.decision === 'Rejected');
|
||||
|
||||
const newStatus = nextStatusMap[interview.level] || 'Approved';
|
||||
|
||||
// Also update currentStage for better tracking
|
||||
const stageMapping: any = {
|
||||
1: 'Level 1 Approved',
|
||||
2: 'Level 2 Approved',
|
||||
@ -633,8 +632,13 @@ export const updateInterviewDecision = async (req: AuthRequest, res: Response) =
|
||||
previousStatus: 'Interview Pending',
|
||||
newStatus: newStatus,
|
||||
changedBy: req.user?.id,
|
||||
reason: remarks || 'Interview Approved'
|
||||
reason: hasRejection ? 'Interview completed with mixed recommendations' : 'Interview Approved by all'
|
||||
});
|
||||
} else {
|
||||
// Still waiting for other interviewers
|
||||
// We can mark the status as 'In Progress' or keep it as 'Scheduled'
|
||||
// But we do NOT update the Application status yet.
|
||||
console.log(`Interview ${interviewId}: Waiting for more evaluations. (${evaluations.length}/${participants.length})`);
|
||||
}
|
||||
|
||||
await db.AuditLog.create({
|
||||
|
||||
@ -9,7 +9,7 @@ import { AuthRequest } from '../../types/express.types.js';
|
||||
// Register new user
|
||||
export const register = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { email, password, fullName, role, phone, region, zone } = req.body;
|
||||
const { email, password, fullName, role, phone, locationId } = req.body;
|
||||
|
||||
// Validate input
|
||||
if (!email || !password || !fullName || !role) {
|
||||
@ -38,8 +38,7 @@ export const register = async (req: Request, res: Response) => {
|
||||
fullName,
|
||||
roleCode: role,
|
||||
mobileNumber: phone,
|
||||
regionId: region,
|
||||
zoneId: zone,
|
||||
locationId,
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
@ -127,8 +126,7 @@ export const login = async (req: Request, res: Response) => {
|
||||
email: user.email,
|
||||
fullName: user.fullName,
|
||||
role: user.roleCode,
|
||||
region: user.regionId,
|
||||
zone: user.zoneId
|
||||
locationId: user.locationId
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@ -163,7 +161,7 @@ export const getProfile = async (req: AuthRequest, res: Response) => {
|
||||
}
|
||||
|
||||
const user = await User.findByPk(req.user.id, {
|
||||
attributes: ['id', 'email', 'fullName', 'roleCode', 'regionId', 'zoneId', 'mobileNumber', 'createdAt']
|
||||
attributes: ['id', 'email', 'fullName', 'roleCode', 'locationId', 'mobileNumber', 'createdAt']
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@ -180,8 +178,7 @@ export const getProfile = async (req: AuthRequest, res: Response) => {
|
||||
email: user.email,
|
||||
fullName: user.fullName,
|
||||
role: user.roleCode,
|
||||
region: user.regionId,
|
||||
zone: user.zoneId,
|
||||
locationId: user.locationId,
|
||||
phone: user.mobileNumber,
|
||||
createdAt: (user as any).createdAt
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ const {
|
||||
} = db;
|
||||
import { AuthRequest } from '../../types/express.types.js';
|
||||
import { AUDIT_ACTIONS } from '../../common/config/constants.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
export const getDealers = async (req: Request, res: Response) => {
|
||||
try {
|
||||
@ -110,8 +111,7 @@ export const createDealer = async (req: AuthRequest, res: Response) => {
|
||||
status: 'active',
|
||||
isActive: true,
|
||||
isExternal: true, // Dealers are external users
|
||||
zoneId: application.zoneId,
|
||||
regionId: application.regionId
|
||||
locationId: application.locationId
|
||||
});
|
||||
console.log(`[Dealer Onboarding] Created new Dealer user account for ${user.email}.`);
|
||||
}
|
||||
@ -124,29 +124,18 @@ export const createDealer = async (req: AuthRequest, res: Response) => {
|
||||
newData: { roleCode: 'Dealer', dealerId: dealer.id }
|
||||
});
|
||||
|
||||
// --- Create Primary Outlet for the Dealer ---
|
||||
// Check if outlet already exists
|
||||
let outlet = await Outlet.findOne({ where: { dealerId: user.id } });
|
||||
// --- Create or Link Outlet for this specific application ---
|
||||
// We use the application's unique ID to ensure we don't duplicate THE SAME application's outlet,
|
||||
// but we allow multiple outlets for the same User ID.
|
||||
let outlet = await Outlet.findOne({
|
||||
where: {
|
||||
dealerId: user.id,
|
||||
name: { [Op.like]: `%${application.city || 'Primary'}%` }, // Rough check for same location
|
||||
createdAt: { [Op.gte]: new Date(new Date().getTime() - 60000) } // Prevent double-clicks in same minute
|
||||
}
|
||||
});
|
||||
|
||||
if (!outlet) {
|
||||
let regionName = 'Central';
|
||||
let zoneName = 'National';
|
||||
|
||||
if (application.regionId) {
|
||||
const reg = await Region.findByPk(application.regionId);
|
||||
if (reg) regionName = reg.regionName;
|
||||
}
|
||||
|
||||
if (application.zoneId) {
|
||||
const zon = await Zone.findByPk(application.zoneId);
|
||||
if (zon) zoneName = zon.zoneName;
|
||||
}
|
||||
|
||||
// Map region name to valid ENUM values if necessary
|
||||
const validRegions = ['East', 'West', 'North', 'South', 'Central'];
|
||||
if (!validRegions.includes(regionName)) {
|
||||
regionName = 'Central'; // Fallback
|
||||
}
|
||||
|
||||
const dealerCodeRecord = await DealerCode.findOne({ where: { applicationId: application.id } });
|
||||
const outletCode = `OUT-${dealerCodeRecord?.dealerCode || Date.now().toString().slice(-6)}`;
|
||||
|
||||
@ -161,11 +150,10 @@ export const createDealer = async (req: AuthRequest, res: Response) => {
|
||||
status: 'Active',
|
||||
establishedDate: new Date(),
|
||||
dealerId: user.id,
|
||||
region: regionName as any,
|
||||
zone: zoneName
|
||||
locationId: application.locationId
|
||||
});
|
||||
|
||||
console.log(`[Dealer Onboarding] Created primary outlet ${outlet.code} for dealer ${user.email}.`);
|
||||
console.log(`[Dealer Onboarding] Created outlet ${outlet.code} for application ${application.applicationId} linked to user ${user.email}.`);
|
||||
|
||||
await AuditLog.create({
|
||||
userId: req.user?.id,
|
||||
|
||||
@ -1,60 +1,56 @@
|
||||
import { Request, Response } from 'express';
|
||||
import db from '../../database/models/index.js';
|
||||
const { Region, Zone, State, District, Area, User, AreaManager } = db;
|
||||
const { User } = db;
|
||||
|
||||
// --- Generic Location Fetching ---
|
||||
const getLocationsByType = async (type: string, req: Request, res: Response) => {
|
||||
try {
|
||||
const locations = await db.Location.findAll({
|
||||
where: { type },
|
||||
include: [
|
||||
{
|
||||
model: db.Location,
|
||||
as: 'parents',
|
||||
through: { attributes: [] }
|
||||
},
|
||||
{
|
||||
model: db.Location,
|
||||
as: 'children',
|
||||
through: { attributes: [] }
|
||||
}
|
||||
],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
res.json({ success: true, data: locations });
|
||||
} catch (error) {
|
||||
console.error(`Get ${type} list error:`, error);
|
||||
res.status(500).json({ success: false, message: `Error fetching ${type} list` });
|
||||
}
|
||||
};
|
||||
|
||||
// --- Regions ---
|
||||
export const getRegions = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const regions = await Region.findAll({
|
||||
include: [
|
||||
{
|
||||
model: State,
|
||||
as: 'states',
|
||||
attributes: ['id', 'stateName']
|
||||
},
|
||||
{
|
||||
model: Zone,
|
||||
as: 'zone',
|
||||
attributes: ['id', 'zoneName']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'regionalManager',
|
||||
attributes: ['id', 'fullName', 'email', 'mobileNumber']
|
||||
}
|
||||
],
|
||||
order: [['regionName', 'ASC']]
|
||||
});
|
||||
|
||||
res.json({ success: true, data: regions });
|
||||
} catch (error) {
|
||||
console.error('Get regions error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error fetching regions' });
|
||||
}
|
||||
return getLocationsByType('region', req, res);
|
||||
};
|
||||
|
||||
export const createRegion = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { zoneId, regionCode, regionName, description, stateIds, regionalManagerId } = req.body;
|
||||
const { zoneId, regionName } = req.body;
|
||||
|
||||
if (!zoneId || !regionName || !regionCode) {
|
||||
return res.status(400).json({ success: false, message: 'Zone ID, region name and code are required' });
|
||||
if (!regionName) {
|
||||
return res.status(400).json({ success: false, message: 'Region name is required' });
|
||||
}
|
||||
|
||||
const region = await Region.create({
|
||||
zoneId,
|
||||
regionCode,
|
||||
regionName,
|
||||
description,
|
||||
regionalManagerId: regionalManagerId || null
|
||||
const region = await db.Location.create({
|
||||
name: regionName,
|
||||
type: 'region'
|
||||
});
|
||||
|
||||
// Assign states if provided
|
||||
if (stateIds && Array.isArray(stateIds) && stateIds.length > 0) {
|
||||
await State.update(
|
||||
{ regionId: region.id, zoneId }, // Also ensure State belongs to the Zone (hierarchy)
|
||||
{ where: { id: stateIds } }
|
||||
);
|
||||
if (zoneId) {
|
||||
await db.LocationHierarchy.create({
|
||||
locationId: region.id,
|
||||
parentId: zoneId
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({ success: true, message: 'Region created successfully', data: region });
|
||||
@ -64,122 +60,31 @@ export const createRegion = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateRegion = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { zoneId, regionCode, regionName, description, isActive, stateIds, regionalManagerId } = req.body;
|
||||
|
||||
const region = await Region.findByPk(id);
|
||||
if (!region) {
|
||||
return res.status(404).json({ success: false, message: 'Region not found' });
|
||||
}
|
||||
|
||||
const updates: any = {};
|
||||
if (zoneId) updates.zoneId = zoneId;
|
||||
if (regionCode) updates.regionCode = regionCode;
|
||||
if (regionName) updates.regionName = regionName;
|
||||
if (description !== undefined) updates.description = description;
|
||||
if (isActive !== undefined) updates.isActive = isActive;
|
||||
if (regionalManagerId !== undefined) updates.regionalManagerId = regionalManagerId;
|
||||
|
||||
await region.update(updates);
|
||||
|
||||
// Handle State reassignment
|
||||
if (stateIds && Array.isArray(stateIds)) {
|
||||
// 1. Unassign states currently assigned to this region but NOT in the new list?
|
||||
// Or just simpler: Assign the new ones. Old ones stay?
|
||||
// Standard behavior for "List of items in a container": Sync list.
|
||||
// We should set regionId=null for states previously in this region but not in stateIds.
|
||||
// But let's check safety. If I uncheck a state, I want it removed from the region.
|
||||
|
||||
// First, find states currently in this region
|
||||
// Actually, simplest 'Reset and Set' approach:
|
||||
// 1. Set regionId=null for all states where regionId = this.id
|
||||
// 2. Set regionId=this.id for states in stateIds.
|
||||
|
||||
// Note: We should probably also enforce zoneId match?
|
||||
// If a user moves a state to this Region, the State must conceptually belong to the Region's Zone.
|
||||
// So we update both regionId and zoneId for the target states.
|
||||
|
||||
// Step 1: Remove States from this Region (if they are NOT in the new list)
|
||||
// We can do this by:
|
||||
// await State.update({ regionId: null }, { where: { regionId: id } });
|
||||
// But wait, if I am only ADDING, I don't want to nuke everything.
|
||||
// But "update" implies "this is the new state of the world".
|
||||
// Assuming frontend sends the FULL list of selected states.
|
||||
|
||||
await State.update({ regionId: null }, { where: { regionId: id } });
|
||||
|
||||
if (stateIds.length > 0) {
|
||||
await State.update(
|
||||
{
|
||||
regionId: id,
|
||||
zoneId: zoneId || region.zoneId // Ensure state moves to the region's zone
|
||||
},
|
||||
{ where: { id: stateIds } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: true, message: 'Region updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Update region error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error updating region' });
|
||||
}
|
||||
};
|
||||
|
||||
// --- Zones ---
|
||||
export const getZones = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { regionId } = req.query as { regionId?: string };
|
||||
|
||||
const where: any = {};
|
||||
if (regionId) {
|
||||
where.regionId = regionId;
|
||||
}
|
||||
|
||||
const zones = await Zone.findAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: Region,
|
||||
as: 'regions',
|
||||
attributes: ['regionName']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'zonalBusinessHead',
|
||||
attributes: ['fullName', 'email', 'mobileNumber']
|
||||
},
|
||||
{
|
||||
model: State,
|
||||
as: 'states',
|
||||
attributes: ['stateName']
|
||||
}
|
||||
],
|
||||
order: [['zoneName', 'ASC']]
|
||||
});
|
||||
|
||||
res.json({ success: true, data: zones });
|
||||
} catch (error) {
|
||||
console.error('Get zones error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error fetching zones' });
|
||||
}
|
||||
return getLocationsByType('zone', req, res);
|
||||
};
|
||||
|
||||
export const createZone = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { regionId, zoneName } = req.body;
|
||||
|
||||
if (!regionId || !zoneName) {
|
||||
return res.status(400).json({ success: false, message: 'Region ID and zone name are required' });
|
||||
if (!zoneName) {
|
||||
return res.status(400).json({ success: false, message: 'Zone name is required' });
|
||||
}
|
||||
|
||||
const zone = await Zone.create({
|
||||
regionId, // Wait, Zone Model doesn't have regionId. It's the other way around?
|
||||
zoneName
|
||||
const zone = await db.Location.create({
|
||||
name: zoneName,
|
||||
type: 'zone'
|
||||
});
|
||||
|
||||
if (regionId) {
|
||||
await db.LocationHierarchy.create({
|
||||
locationId: zone.id,
|
||||
parentId: regionId
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({ success: true, message: 'Zone created successfully', data: zone });
|
||||
} catch (error) {
|
||||
console.error('Create zone error:', error);
|
||||
@ -187,69 +92,53 @@ export const createZone = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateZone = async (req: Request, res: Response) => {
|
||||
export const updateLocation = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { zoneName, description, isActive, zonalBusinessHeadId, stateIds } = req.body;
|
||||
const { name, type, parentIds } = req.body;
|
||||
|
||||
const zone = await Zone.findByPk(id);
|
||||
if (!zone) {
|
||||
return res.status(404).json({ success: false, message: 'Zone not found' });
|
||||
const location = await db.Location.findByPk(id);
|
||||
if (!location) {
|
||||
return res.status(404).json({ success: false, message: 'Location not found' });
|
||||
}
|
||||
|
||||
const updates: any = {};
|
||||
if (zoneName) updates.zoneName = zoneName;
|
||||
if (description !== undefined) updates.description = description;
|
||||
if (isActive !== undefined) updates.isActive = isActive;
|
||||
if (zonalBusinessHeadId !== undefined) updates.zonalBusinessHeadId = zonalBusinessHeadId;
|
||||
if (name) updates.name = name;
|
||||
if (type) updates.type = type;
|
||||
|
||||
await zone.update(updates);
|
||||
await location.update(updates);
|
||||
|
||||
// Handle State assignment
|
||||
if (stateIds && Array.isArray(stateIds) && stateIds.length > 0) {
|
||||
// Update all provided states to belong to this zone
|
||||
// We can't easily "remove" states because zoneId is non-nullable.
|
||||
// States must be moved TO another zone to be removed from this one.
|
||||
// So we primarily handle "bringing states into this zone".
|
||||
// However, we should check if they exist first.
|
||||
await State.update(
|
||||
{ zoneId: zone.id },
|
||||
{ where: { id: stateIds } }
|
||||
);
|
||||
if (parentIds && Array.isArray(parentIds)) {
|
||||
// Re-sync parents
|
||||
await db.LocationHierarchy.destroy({ where: { locationId: id } });
|
||||
for (const pid of parentIds) {
|
||||
await db.LocationHierarchy.create({ locationId: id, parentId: pid });
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: true, message: 'Zone updated successfully' });
|
||||
res.json({ success: true, message: 'Location updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Update zone error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error updating zone' });
|
||||
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) => {
|
||||
try {
|
||||
const { zoneId } = req.query as { zoneId?: string };
|
||||
const where: any = {};
|
||||
if (zoneId) where.zoneId = zoneId;
|
||||
|
||||
const states = await State.findAll({
|
||||
where,
|
||||
include: [{ model: Zone, as: 'zone', attributes: ['zoneName'] }],
|
||||
order: [['stateName', 'ASC']]
|
||||
});
|
||||
res.json({ success: true, states });
|
||||
} catch (error) {
|
||||
console.error('Get states error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error fetching states' });
|
||||
}
|
||||
return getLocationsByType('state', req, res);
|
||||
};
|
||||
|
||||
export const createState = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { zoneId, stateName } = req.body;
|
||||
if (!zoneId || !stateName) return res.status(400).json({ success: false, message: 'Zone ID and state name required' });
|
||||
if (!stateName) return res.status(400).json({ success: false, message: 'State name is required' });
|
||||
|
||||
const state = await State.create({ zoneId, stateName });
|
||||
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);
|
||||
@ -257,46 +146,22 @@ export const createState = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateState = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { stateName, isActive } = req.body;
|
||||
const state = await State.findByPk(id);
|
||||
if (!state) return res.status(404).json({ success: false, message: 'State not found' });
|
||||
|
||||
await state.update({ stateName, isActive });
|
||||
res.json({ success: true, message: 'State updated' });
|
||||
} catch (error) {
|
||||
console.error('Update state error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error updating state' });
|
||||
}
|
||||
};
|
||||
|
||||
// --- Districts ---
|
||||
export const getDistricts = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { stateId } = req.query as { stateId?: string };
|
||||
const where: any = {};
|
||||
if (stateId) where.stateId = stateId;
|
||||
|
||||
const districts = await District.findAll({
|
||||
where,
|
||||
include: [{ model: State, as: 'state', attributes: ['stateName'] }],
|
||||
order: [['districtName', 'ASC']]
|
||||
});
|
||||
res.json({ success: true, districts });
|
||||
} catch (error) {
|
||||
console.error('Get districts error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error fetching districts' });
|
||||
}
|
||||
return getLocationsByType('district', req, res);
|
||||
};
|
||||
|
||||
export const createDistrict = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { stateId, districtName } = req.body;
|
||||
if (!stateId || !districtName) return res.status(400).json({ success: false, message: 'State ID and district name required' });
|
||||
if (!districtName) return res.status(400).json({ success: false, message: 'District name is required' });
|
||||
|
||||
const district = await District.create({ stateId, districtName });
|
||||
const district = await db.Location.create({ name: districtName, type: 'district' });
|
||||
|
||||
if (stateId) {
|
||||
await db.LocationHierarchy.create({ locationId: district.id, parentId: stateId });
|
||||
}
|
||||
|
||||
res.status(201).json({ success: true, message: 'District created', data: district });
|
||||
} catch (error) {
|
||||
console.error('Create district error:', error);
|
||||
@ -304,121 +169,20 @@ export const createDistrict = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateDistrict = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { districtName, isActive } = req.body;
|
||||
const district = await District.findByPk(id);
|
||||
if (!district) return res.status(404).json({ success: false, message: 'District not found' });
|
||||
|
||||
await district.update({ districtName, isActive });
|
||||
res.json({ success: true, message: 'District updated' });
|
||||
} catch (error) {
|
||||
console.error('Update district error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error updating district' });
|
||||
}
|
||||
};
|
||||
|
||||
// --- Areas ---
|
||||
export const getAreas = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { districtId } = req.query as { districtId?: string };
|
||||
const where: any = {};
|
||||
if (districtId) where.districtId = districtId;
|
||||
|
||||
const areas = await Area.findAll({
|
||||
where,
|
||||
include: [
|
||||
{ model: District, as: 'district', attributes: ['districtName'] },
|
||||
{ model: State, as: 'state', attributes: ['stateName'] },
|
||||
{ model: Region, as: 'region', attributes: ['regionName'] },
|
||||
{ model: Zone, as: 'zone', attributes: ['zoneName'] },
|
||||
// Include explicit manager column (legacy/fallback)
|
||||
{ model: User, as: 'manager', attributes: ['id', 'fullName', 'email', 'mobileNumber'] },
|
||||
// Include active managers from dedicated table
|
||||
{
|
||||
model: AreaManager,
|
||||
as: 'areaManagers',
|
||||
where: { isActive: true },
|
||||
required: false, // Left join, so we get areas even without managers
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'fullName', 'email', 'mobileNumber']
|
||||
}]
|
||||
}
|
||||
],
|
||||
order: [['areaName', 'ASC']]
|
||||
});
|
||||
res.json({ success: true, areas });
|
||||
} catch (error) {
|
||||
console.error('Get areas error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error fetching areas' });
|
||||
}
|
||||
return getLocationsByType('area', req, res);
|
||||
};
|
||||
|
||||
export const createArea = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { districtId, areaCode, areaName, city, pincode, managerId } = req.body;
|
||||
if (!districtId || !areaName || !pincode) return res.status(400).json({ success: false, message: 'District ID, area name, and pincode required' });
|
||||
const { districtId, areaName } = req.body;
|
||||
if (!areaName) return res.status(400).json({ success: false, message: 'Area name is required' });
|
||||
|
||||
// Need to fetch regionId from district -> state -> zone -> region?
|
||||
// Or user provides it?
|
||||
// The Area model has regionId, districtId.
|
||||
// It's safer to fetch relationships.
|
||||
const district = await District.findByPk(districtId, {
|
||||
include: [{
|
||||
model: State,
|
||||
as: 'state',
|
||||
include: [
|
||||
{ model: Zone, as: 'zone' },
|
||||
{ model: Region, as: 'region' }
|
||||
]
|
||||
}]
|
||||
});
|
||||
const area = await db.Location.create({ name: areaName, type: 'area' });
|
||||
|
||||
let regionId = null;
|
||||
let zoneId = null;
|
||||
let stateId = null;
|
||||
|
||||
if (district) {
|
||||
stateId = district.stateId;
|
||||
// Access associations using the logical structure (District -> State -> Zone/Region)
|
||||
if (district.state) {
|
||||
if (district.state.zone) {
|
||||
zoneId = district.state.zone.id;
|
||||
}
|
||||
if (district.state.region) {
|
||||
regionId = district.state.region.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const area = await Area.create({
|
||||
districtId,
|
||||
stateId,
|
||||
zoneId,
|
||||
regionId,
|
||||
areaCode,
|
||||
areaName,
|
||||
city,
|
||||
pincode,
|
||||
managerId: managerId || null, // Legacy support
|
||||
isActive: req.body.isActive ?? true,
|
||||
activeFrom: req.body.activeFrom || null,
|
||||
activeTo: req.body.activeTo || null
|
||||
});
|
||||
|
||||
// Create AreaManager record if manager assigned
|
||||
if (managerId) {
|
||||
await AreaManager.create({
|
||||
areaId: area.id,
|
||||
userId: managerId,
|
||||
managerType: 'ASM',
|
||||
isActive: true,
|
||||
assignedAt: new Date(),
|
||||
asmCode: req.body.asmCode || null
|
||||
});
|
||||
if (districtId) {
|
||||
await db.LocationHierarchy.create({ locationId: area.id, parentId: districtId });
|
||||
}
|
||||
|
||||
res.status(201).json({ success: true, message: 'Area created', data: area });
|
||||
@ -428,144 +192,44 @@ export const createArea = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
// --- Area Managers ---
|
||||
export const getAreaManagers = async (req: Request, res: Response) => {
|
||||
// --- Managers (Consolidated) ---
|
||||
export const getManagersByRole = async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Fetch Users who have active AreaManager assignments
|
||||
// We use the User model as the primary so we get the User details naturally
|
||||
const managers = await User.findAll({
|
||||
attributes: ['id', 'fullName', 'email', 'mobileNumber', 'employeeId', 'roleCode', 'zoneId', 'regionId'],
|
||||
include: [
|
||||
{
|
||||
model: AreaManager,
|
||||
as: 'areaManagers',
|
||||
where: { isActive: true },
|
||||
required: true, // Only return users who ARE active managers
|
||||
attributes: ['asmCode'],
|
||||
include: [
|
||||
{
|
||||
model: Area,
|
||||
as: 'area',
|
||||
attributes: ['id', 'areaName', 'areaCode'],
|
||||
include: [
|
||||
{ model: District, as: 'district', attributes: ['districtName'] },
|
||||
{ model: State, as: 'state', attributes: ['stateName'] },
|
||||
{ model: Region, as: 'region', attributes: ['id', 'regionName'] },
|
||||
{ model: Zone, as: 'zone', attributes: ['id', 'zoneName'] }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ model: Zone, as: 'zone', attributes: ['id', 'zoneName'] },
|
||||
{ model: Region, as: 'region', attributes: ['id', 'regionName'] }
|
||||
],
|
||||
order: [['fullName', 'ASC']]
|
||||
});
|
||||
const { roleCode, locationId } = req.query as any;
|
||||
const where: any = {};
|
||||
if (roleCode) where.roleCode = roleCode;
|
||||
if (locationId) where.locationId = locationId;
|
||||
|
||||
// Transform if necessary to flatten the structure for the frontend
|
||||
// But the user asked for "straightforward", so a clean nested JSON is usually best
|
||||
// We can double check if they want a flat list of (User, Area) pairs or User -> [Areas]
|
||||
// "Arean mangers" implies the People. So User -> [Areas] is the best entity representation.
|
||||
const managers = await User.findAll({
|
||||
where,
|
||||
attributes: ['id', 'fullName', 'email', 'mobileNumber', 'roleCode', 'locationId'],
|
||||
include: [{
|
||||
model: db.Location,
|
||||
as: 'location',
|
||||
attributes: ['id', 'name', 'type']
|
||||
}]
|
||||
});
|
||||
|
||||
res.json({ success: true, data: managers });
|
||||
} catch (error) {
|
||||
console.error('Get area managers error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error fetching area managers' });
|
||||
console.error('Get managers error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error fetching managers' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateArea = async (req: Request, res: Response) => {
|
||||
export const getAreaManagers = async (req: Request, res: Response) => {
|
||||
req.query.roleCode = 'ASM';
|
||||
return getManagersByRole(req, res);
|
||||
};
|
||||
|
||||
export const deleteLocation = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { areaName, city, pincode, isActive, managerId, districtId, activeFrom, activeTo } = req.body;
|
||||
const area = await Area.findByPk(id);
|
||||
if (!area) return res.status(404).json({ success: false, message: 'Area not found' });
|
||||
|
||||
const updates: any = {};
|
||||
if (areaName) updates.areaName = areaName;
|
||||
if (city) updates.city = city;
|
||||
if (pincode) updates.pincode = pincode;
|
||||
if (isActive !== undefined) updates.isActive = isActive;
|
||||
if (activeFrom !== undefined) updates.activeFrom = activeFrom || null;
|
||||
if (activeTo !== undefined) updates.activeTo = activeTo || null;
|
||||
if (managerId !== undefined) updates.managerId = managerId; // Legacy support
|
||||
|
||||
// If district is changed, update the entire hierarchy (State, Zone, Region)
|
||||
if (districtId && districtId !== area.districtId) {
|
||||
updates.districtId = districtId;
|
||||
|
||||
const district = await District.findByPk(districtId, {
|
||||
include: [{
|
||||
model: State,
|
||||
as: 'state',
|
||||
include: [
|
||||
{ model: Zone, as: 'zone' },
|
||||
{ model: Region, as: 'region' }
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
if (district) {
|
||||
updates.stateId = district.stateId;
|
||||
if (district.state) {
|
||||
if (district.state.zone) {
|
||||
updates.zoneId = district.state.zone.id;
|
||||
}
|
||||
if (district.state.region) {
|
||||
updates.regionId = district.state.region.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await area.update(updates);
|
||||
|
||||
// Handle AreaManager Table Update
|
||||
if (managerId !== undefined) {
|
||||
const asmCode = req.body.asmCode;
|
||||
|
||||
// 1. Find currently active manager for this area
|
||||
const currentActiveManager = await AreaManager.findOne({
|
||||
where: {
|
||||
areaId: id,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
|
||||
// If there is an active manager
|
||||
if (currentActiveManager) {
|
||||
// If the new managerId is different (or null, meaning unassign), deactivate the old one
|
||||
if (currentActiveManager.userId !== managerId) {
|
||||
await currentActiveManager.update({ isActive: false });
|
||||
} else {
|
||||
// If SAME user, update asmCode if provided
|
||||
if (asmCode !== undefined) {
|
||||
await currentActiveManager.update({ asmCode });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. If a new manager is being assigned (and it's not null)
|
||||
if (managerId) {
|
||||
// Check if this specific user is already active (to avoid duplicates if logic above missed it)
|
||||
const isAlreadyActive = currentActiveManager && currentActiveManager.userId === managerId;
|
||||
|
||||
if (!isAlreadyActive) {
|
||||
await AreaManager.create({
|
||||
areaId: id,
|
||||
userId: managerId,
|
||||
managerType: 'ASM', // Default type
|
||||
isActive: true,
|
||||
assignedAt: new Date(),
|
||||
asmCode: asmCode || null
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: true, message: 'Area updated successfully' });
|
||||
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' });
|
||||
} catch (error) {
|
||||
console.error('Update area error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error updating area' });
|
||||
console.error('Delete location error:', error);
|
||||
res.status(500).json({ success: false, message: 'Error deleting location' });
|
||||
}
|
||||
};
|
||||
|
||||
@ -17,25 +17,26 @@ 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.updateRegion);
|
||||
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.updateZone);
|
||||
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.updateState);
|
||||
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.updateDistrict);
|
||||
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.updateArea);
|
||||
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);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import db from '../../database/models/index.js';
|
||||
const { Application, Opportunity, ApplicationStatusHistory, ApplicationProgress, AuditLog, District, Region, Zone, Area } = db;
|
||||
const { Application, Opportunity, ApplicationStatusHistory, ApplicationProgress, AuditLog, Location } = db;
|
||||
import { AUDIT_ACTIONS, APPLICATION_STAGES, APPLICATION_STATUS } from '../../common/config/constants.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Op } from 'sequelize';
|
||||
@ -17,72 +17,44 @@ export const submitApplication = async (req: AuthRequest, res: Response) => {
|
||||
age, education, companyName, source, existingDealer, ownRoyalEnfield, royalEnfieldModel, description, address, pincode
|
||||
} = req.body;
|
||||
|
||||
// Check for duplicate email
|
||||
const existingApp = await Application.findOne({ where: { email } });
|
||||
// Check for duplicate application for SAME location
|
||||
const existingApp = await Application.findOne({
|
||||
where: {
|
||||
email,
|
||||
city: city || null,
|
||||
preferredLocation: preferredLocation || null,
|
||||
overallStatus: { [Op.ne]: 'Rejected' } // Don't block if previous was rejected
|
||||
}
|
||||
});
|
||||
|
||||
if (existingApp) {
|
||||
return res.status(400).json({ success: false, message: 'Application with this email already exists' });
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'An active application for this location already exists with this email address.'
|
||||
});
|
||||
}
|
||||
|
||||
const applicationId = `APP-${new Date().getFullYear()}-${uuidv4().substring(0, 6).toUpperCase()}`;
|
||||
|
||||
// Fetch hierarchy from Auto-detected Area
|
||||
let zoneId, regionId, areaId;
|
||||
// Fetch hierarchy from Auto-detected Location
|
||||
let locationId = null;
|
||||
let isOpportunityAvailable = false;
|
||||
|
||||
// Auto-detect Area from District
|
||||
// Auto-detect Location from District
|
||||
if (req.body.district) {
|
||||
const districtName = req.body.district;
|
||||
|
||||
// 1. Find District ID by Name
|
||||
const districtRecord = await District.findOne({
|
||||
where: { districtName: { [Op.iLike]: districtName } }
|
||||
});
|
||||
|
||||
if (districtRecord) {
|
||||
// 2. Find Active Area for this District
|
||||
const today = new Date();
|
||||
const validArea = await Area.findOne({
|
||||
where: {
|
||||
districtId: districtRecord.id,
|
||||
isActive: true,
|
||||
[Op.and]: [
|
||||
{
|
||||
[Op.or]: [
|
||||
{ activeFrom: { [Op.eq]: null } },
|
||||
{ activeFrom: { [Op.lte]: today } }
|
||||
]
|
||||
},
|
||||
{
|
||||
[Op.or]: [
|
||||
{ activeTo: { [Op.eq]: null } },
|
||||
{ activeTo: { [Op.gte]: today } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
if (validArea) {
|
||||
areaId = validArea.id;
|
||||
zoneId = validArea.zoneId;
|
||||
regionId = validArea.regionId;
|
||||
isOpportunityAvailable = true;
|
||||
// Find Location (type: district) match
|
||||
const districtRecord = await Location.findOne({
|
||||
where: {
|
||||
name: { [Op.iLike]: districtName },
|
||||
type: 'district'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine Initial Status
|
||||
let initialStatus = isOpportunityAvailable ? APPLICATION_STATUS.QUESTIONNAIRE_PENDING : APPLICATION_STATUS.SUBMITTED;
|
||||
|
||||
// Auto-assign Zone/Region from District if still null (even if no opportunity found)
|
||||
if (!zoneId && req.body.district) {
|
||||
const districtRecord = await District.findOne({
|
||||
where: { districtName: { [Op.iLike]: req.body.district } },
|
||||
include: [{ model: Region, as: 'region' }, { model: Zone, as: 'zone' }]
|
||||
});
|
||||
|
||||
if (districtRecord) {
|
||||
regionId = districtRecord.regionId;
|
||||
zoneId = districtRecord.zoneId;
|
||||
locationId = districtRecord.id;
|
||||
isOpportunityAvailable = true; // For now, assume if district exists, it's an opportunity
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,18 +72,16 @@ export const submitApplication = async (req: AuthRequest, res: Response) => {
|
||||
investmentCapacity,
|
||||
age, education, companyName, source, existingDealer, ownRoyalEnfield, royalEnfieldModel, description, address, pincode, locationType,
|
||||
currentStage: APPLICATION_STAGES.DD,
|
||||
overallStatus: initialStatus,
|
||||
overallStatus: isOpportunityAvailable ? APPLICATION_STATUS.QUESTIONNAIRE_PENDING : APPLICATION_STATUS.SUBMITTED,
|
||||
progressPercentage: isOpportunityAvailable ? 10 : 0,
|
||||
zoneId,
|
||||
regionId,
|
||||
areaId // Link to Area
|
||||
locationId
|
||||
});
|
||||
|
||||
// Log Status History
|
||||
await ApplicationStatusHistory.create({
|
||||
applicationId: application.id,
|
||||
previousStatus: null,
|
||||
newStatus: initialStatus,
|
||||
newStatus: application.overallStatus,
|
||||
changedBy: req.user?.id || null,
|
||||
reason: 'Initial Submission'
|
||||
});
|
||||
|
||||
@ -7,12 +7,11 @@ import { AUDIT_ACTIONS } from '../../common/config/constants.js';
|
||||
|
||||
export const getOpportunities = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { status, regionId, zoneId } = req.query as any;
|
||||
const { status, locationId } = req.query as any;
|
||||
const where: any = {};
|
||||
|
||||
if (status) where.status = status;
|
||||
if (regionId) where.regionId = regionId;
|
||||
if (zoneId) where.zoneId = zoneId;
|
||||
if (locationId) where.locationId = locationId;
|
||||
|
||||
const opportunities = await Opportunity.findAll({
|
||||
where,
|
||||
@ -33,7 +32,7 @@ export const createOpportunity = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const {
|
||||
leadSource, leadName, contactNumber, email,
|
||||
zoneId, regionId, stateId, districtId,
|
||||
locationId,
|
||||
opportunityType, priority
|
||||
} = req.body;
|
||||
|
||||
@ -42,10 +41,7 @@ export const createOpportunity = async (req: AuthRequest, res: Response) => {
|
||||
leadName,
|
||||
contactNumber,
|
||||
email,
|
||||
zoneId,
|
||||
regionId,
|
||||
stateId,
|
||||
districtId,
|
||||
locationId,
|
||||
opportunityType,
|
||||
priority,
|
||||
status: 'New',
|
||||
|
||||
@ -116,6 +116,17 @@ const seedQuestionnaire = async () => {
|
||||
weight: 0,
|
||||
order: 7
|
||||
},
|
||||
{
|
||||
text: "Are you an existing dealer/vendor of Royal Enfield?",
|
||||
type: "radio",
|
||||
section: "Basic Information",
|
||||
options: [
|
||||
{ text: "Yes", score: 0 },
|
||||
{ text: "No", score: 0 }
|
||||
],
|
||||
weight: 0,
|
||||
order: 8
|
||||
},
|
||||
|
||||
// Section 2: Profile & Background (Scoring Starts)
|
||||
{
|
||||
|
||||
169
src/scripts/test_multi_outlet.ts
Normal file
169
src/scripts/test_multi_outlet.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import db from '../database/models/index.js';
|
||||
const { Application, User, Outlet, Dealer, DealerCode } = db;
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
async function test() {
|
||||
console.log('--- Starting Multi-Outlet Support Test ---');
|
||||
|
||||
const email = `test_dealer_multi_${Date.now()}@example.com`;
|
||||
const phone = '9876543210';
|
||||
|
||||
try {
|
||||
// 1. Initial Application for City X
|
||||
console.log('1. Submitting first application for City X...');
|
||||
const app1 = await Application.create({
|
||||
applicationId: `APP-X-${Date.now()}`,
|
||||
applicantName: 'Test Dealer',
|
||||
email,
|
||||
phone,
|
||||
businessType: 'Dealership',
|
||||
city: 'City X',
|
||||
preferredLocation: 'Location X',
|
||||
overallStatus: 'Pending',
|
||||
currentStage: 'DD',
|
||||
zoneId: null,
|
||||
regionId: null
|
||||
});
|
||||
console.log('Success: Application 1 created.');
|
||||
|
||||
// 2. Duplicate Application for SAME Location
|
||||
console.log('2. Attempting duplicate application for City X...');
|
||||
const existingApp = await Application.findOne({
|
||||
where: {
|
||||
email,
|
||||
city: 'City X',
|
||||
preferredLocation: 'Location X',
|
||||
overallStatus: { [Op.ne]: 'Rejected' }
|
||||
}
|
||||
});
|
||||
|
||||
if (existingApp) {
|
||||
console.log('Correct: Detected duplicate application for same location.');
|
||||
} else {
|
||||
console.error('Error: Failed to detect duplicate location!');
|
||||
}
|
||||
|
||||
// 3. New Application for DIFFERENT Location (City Y)
|
||||
console.log('3. Submitting application for City Y...');
|
||||
const app2 = await Application.create({
|
||||
applicationId: `APP-Y-${Date.now()}`,
|
||||
applicantName: 'Test Dealer',
|
||||
email,
|
||||
phone,
|
||||
businessType: 'Dealership',
|
||||
city: 'City Y',
|
||||
preferredLocation: 'Location Y',
|
||||
overallStatus: 'Pending',
|
||||
currentStage: 'DD',
|
||||
zoneId: null,
|
||||
regionId: null
|
||||
});
|
||||
console.log('Success: Application 2 created for different location.');
|
||||
|
||||
// 4. Onboard Application 1 (Simulated step)
|
||||
console.log('4. Onboarding Application 1...');
|
||||
const dCode1 = await DealerCode.create({
|
||||
dealerCode: `CODE-X-${Date.now()}`,
|
||||
applicationId: app1.id
|
||||
});
|
||||
|
||||
const dealer1 = await Dealer.create({
|
||||
applicationId: app1.id,
|
||||
dealerCodeId: dCode1.id,
|
||||
legalName: 'Test Dealer X Ltd',
|
||||
businessName: 'Test Dealer - X',
|
||||
constitutionType: 'Partnership',
|
||||
status: 'Active'
|
||||
});
|
||||
|
||||
const user = await User.create({
|
||||
fullName: 'Test Dealer',
|
||||
email,
|
||||
password: 'hashed_password',
|
||||
roleCode: 'Dealer',
|
||||
dealerId: dealer1.id,
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
// Current Logic in dealer.controller.ts for outlet 1
|
||||
const outlet1 = await Outlet.create({
|
||||
code: `OUT-X-${Date.now()}`,
|
||||
name: `${app1.applicantName} - ${app1.city}`,
|
||||
type: 'Dealership',
|
||||
address: 'Address X',
|
||||
city: 'City X',
|
||||
state: 'State X',
|
||||
pincode: '123456',
|
||||
status: 'Active',
|
||||
establishedDate: new Date(),
|
||||
dealerId: user.id,
|
||||
region: 'Central',
|
||||
zone: 'National'
|
||||
});
|
||||
console.log('Success: Application 1 onboarded.');
|
||||
|
||||
// 5. Onboard Application 2 (Simulation of our fix)
|
||||
console.log('5. Onboarding Application 2 (sharing same user)...');
|
||||
const dCode2 = await DealerCode.create({
|
||||
dealerCode: `CODE-Y-${Date.now()}`,
|
||||
applicationId: app2.id
|
||||
});
|
||||
|
||||
const dealer2 = await Dealer.create({
|
||||
applicationId: app2.id,
|
||||
dealerCodeId: dCode2.id,
|
||||
legalName: 'Test Dealer Y Ltd',
|
||||
businessName: 'Test Dealer - Y',
|
||||
constitutionType: 'Partnership',
|
||||
status: 'Active'
|
||||
});
|
||||
|
||||
// Update existing user with new dealerId (Latest)
|
||||
await user.update({ dealerId: dealer2.id });
|
||||
|
||||
// Our Fix in dealer.controller.ts: Check for outlet for THIS application/city
|
||||
let existingOutletForThisApp = await Outlet.findOne({
|
||||
where: {
|
||||
dealerId: user.id,
|
||||
name: { [Op.like]: `%${app2.city}%` }
|
||||
}
|
||||
});
|
||||
|
||||
if (!existingOutletForThisApp) {
|
||||
const outlet2 = await Outlet.create({
|
||||
code: `OUT-Y-${Date.now()}`,
|
||||
name: `${app2.applicantName} - ${app2.city}`,
|
||||
type: 'Dealership',
|
||||
address: 'Address Y',
|
||||
city: 'City Y',
|
||||
state: 'State Y',
|
||||
pincode: '654321',
|
||||
status: 'Active',
|
||||
establishedDate: new Date(),
|
||||
dealerId: user.id,
|
||||
region: 'Central',
|
||||
zone: 'National'
|
||||
});
|
||||
console.log('Success: Outlet 2 created for City Y.');
|
||||
} else {
|
||||
console.error('Error: Outlet for City Y already exists wrongly!');
|
||||
}
|
||||
|
||||
// 6. Final Check
|
||||
const outletCount = await Outlet.count({ where: { dealerId: user.id } });
|
||||
console.log(`Final Verification: User ${email} has ${outletCount} outlets total.`);
|
||||
|
||||
if (outletCount === 2) {
|
||||
console.log('TEST PASSED: Multi-outlet support verified.');
|
||||
} else {
|
||||
console.error(`TEST FAILED: Expected 2 outlets, found ${outletCount}.`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Test error:', error);
|
||||
} finally {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
|
||||
test();
|
||||
74
src/scripts/verify_uuid_fix.ts
Normal file
74
src/scripts/verify_uuid_fix.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import db from '../database/models/index.js';
|
||||
const { User, Region, Zone } = db;
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
async function verifyFix() {
|
||||
console.log('--- Starting UUID Sanitization Verification ---');
|
||||
|
||||
const testEmail = `test_uuid_fix_${Date.now()}@example.com`;
|
||||
const password = await bcrypt.hash('Test@123', 10);
|
||||
|
||||
try {
|
||||
// 1. Attempt to create a user with empty string for regionId
|
||||
// This simulates what the createUser controller now does after sanitization
|
||||
console.log('1. Testing User creation with empty-string sanitized to null...');
|
||||
|
||||
const userData = {
|
||||
fullName: 'Test UUID User',
|
||||
email: testEmail,
|
||||
password: password,
|
||||
roleCode: 'ZBH',
|
||||
status: 'active',
|
||||
isActive: true,
|
||||
employeeId: `EMP-${Date.now()}`,
|
||||
mobileNumber: '1234567890',
|
||||
zoneId: null, // If frontend sends "", my controller now converts to null
|
||||
regionId: null,
|
||||
stateId: null,
|
||||
districtId: null,
|
||||
areaId: null
|
||||
};
|
||||
|
||||
const user = await User.create(userData);
|
||||
console.log('Success: User created with null UUID fields.');
|
||||
|
||||
// 2. Simulate the Circular Dependency Workflow
|
||||
console.log('2. Testing Circular Dependency Workflow...');
|
||||
|
||||
// a. Create a Zone first (needed for Region)
|
||||
const zone = await Zone.create({
|
||||
zoneCode: `ZNE-${Date.now().toString().slice(-4)}`,
|
||||
zoneName: `Test Zone ${Date.now()}`
|
||||
});
|
||||
|
||||
// b. Create a Region and assign the user as manager
|
||||
console.log('Creating Region with User as Manager...');
|
||||
const region = await Region.create({
|
||||
zoneId: zone.id,
|
||||
regionCode: `REG-${Date.now().toString().slice(-4)}`,
|
||||
regionName: 'Test Region',
|
||||
regionalManagerId: user.id
|
||||
});
|
||||
console.log('Success: Region created.');
|
||||
|
||||
// c. Link User back to the Region
|
||||
console.log('Linking User back to the new Region...');
|
||||
await user.update({ regionId: region.id });
|
||||
console.log('Success: User updated with regionId.');
|
||||
|
||||
// 3. Verify final state
|
||||
const updatedUser = await User.findByPk(user.id);
|
||||
if (updatedUser?.regionId === region.id) {
|
||||
console.log('VERIFICATION PASSED: User-Region cycle completed successfully.');
|
||||
} else {
|
||||
console.error('VERIFICATION FAILED: user.regionId was not updated correctly.');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('VERIFICATION FAILED with error:', error);
|
||||
} finally {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
|
||||
verifyFix();
|
||||
@ -2,6 +2,5 @@ export interface TokenPayload {
|
||||
userId: string;
|
||||
email: string;
|
||||
role: string;
|
||||
region: string | null;
|
||||
zone: string | null;
|
||||
locationId: string | null;
|
||||
}
|
||||
|
||||
112
verify_approval_sync.ts
Normal file
112
verify_approval_sync.ts
Normal file
@ -0,0 +1,112 @@
|
||||
|
||||
import 'dotenv/config';
|
||||
import db from './src/database/models/index';
|
||||
const { Application, Interview, InterviewParticipant, InterviewEvaluation } = db;
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
async function testApprovalSync() {
|
||||
console.log('--- Testing Synchronized Approval Logic (Direct DB Simulation) ---');
|
||||
|
||||
try {
|
||||
// Setup IDs for test (must exist in DB)
|
||||
const user1Id = 'fb9ba702-233b-4b3f-ba21-33bedec6209a'; // DD-ZM
|
||||
const user2Id = 'c3602348-c9bd-4938-83b2-acc3c62791aa'; // RBM
|
||||
|
||||
// 1. Setup a test application
|
||||
console.log('\n1. Creating test application...');
|
||||
const app = await Application.create({
|
||||
applicationId: 'APP-' + Date.now(),
|
||||
registrationNumber: 'TEST-' + Date.now(),
|
||||
fullName: 'Test Applicant',
|
||||
applicantName: 'Test Applicant',
|
||||
email: 'test' + Date.now() + '@example.com',
|
||||
phone: '1234567890',
|
||||
businessType: 'Dealership',
|
||||
overallStatus: 'Level 1 Interview Pending',
|
||||
currentStage: 'Level 1 Approved', // Mocking stage
|
||||
zoneId: 'aedf5f64-3f95-4fa7-ac64-52c97cb330e7',
|
||||
regionId: 'a5ea0aa2-c26c-49a9-95fe-0dbd7283c125'
|
||||
});
|
||||
|
||||
// 2. Create an interview with 2 participants
|
||||
console.log('2. Creating interview for Level 1...');
|
||||
const interview = await Interview.create({
|
||||
applicationId: app.id,
|
||||
level: 1,
|
||||
status: 'Scheduled',
|
||||
interviewType: 'Level 1 Interview'
|
||||
});
|
||||
|
||||
const participant1 = await InterviewParticipant.create({
|
||||
interviewId: interview.id,
|
||||
userId: user1Id,
|
||||
roleInPanel: 'Interviewer'
|
||||
});
|
||||
|
||||
const participant2 = await InterviewParticipant.create({
|
||||
interviewId: interview.id,
|
||||
userId: user2Id,
|
||||
roleInPanel: 'Interviewer'
|
||||
});
|
||||
|
||||
console.log(`Interview created with ID: ${interview.id}`);
|
||||
|
||||
// Decision logic
|
||||
async function submitDecision(userId: string, decision: string) {
|
||||
console.log(`\nSubmitting ${decision} for User: ${userId}...`);
|
||||
|
||||
await InterviewEvaluation.create({
|
||||
interviewId: interview.id,
|
||||
evaluatorId: userId,
|
||||
recommendation: decision,
|
||||
decision: decision,
|
||||
remarks: 'Testing'
|
||||
});
|
||||
|
||||
// Replicate the sync check from controller
|
||||
const participants = await InterviewParticipant.findAll({ where: { interviewId: interview.id } });
|
||||
const evaluations = await InterviewEvaluation.findAll({ where: { interviewId: interview.id } });
|
||||
|
||||
const isFullyEvaluated = evaluations.length >= participants.length;
|
||||
console.log(`Evaluations: ${evaluations.length}/${participants.length}. Fully Evaluated: ${isFullyEvaluated}`);
|
||||
|
||||
if (isFullyEvaluated) {
|
||||
console.log('Finalizing interview and updating application status...');
|
||||
await interview.update({ status: 'Completed' });
|
||||
|
||||
const nextStatus = 'Level 1 Approved';
|
||||
await Application.update({
|
||||
overallStatus: nextStatus,
|
||||
currentStage: 'Level 1 Approved'
|
||||
}, { where: { id: app.id } });
|
||||
console.log(`Application status updated to: ${nextStatus}`);
|
||||
} else {
|
||||
console.log('Waiting for more evaluations. Application status remains unchanged.');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. First participant rejects
|
||||
await submitDecision(user1Id, 'Rejected');
|
||||
let currentApp = await Application.findByPk(app.id);
|
||||
console.log(`Current App Overall Status: ${currentApp.overallStatus}`);
|
||||
|
||||
// 4. Second participant approves
|
||||
await submitDecision(user2Id, 'Approved');
|
||||
currentApp = await Application.findByPk(app.id);
|
||||
console.log(`Final App Overall Status: ${currentApp.overallStatus}`);
|
||||
|
||||
// Cleanup
|
||||
console.log('\nCleaning up...');
|
||||
await InterviewEvaluation.destroy({ where: { interviewId: interview.id } });
|
||||
await InterviewParticipant.destroy({ where: { interviewId: interview.id } });
|
||||
await Interview.destroy({ where: { id: interview.id } });
|
||||
await Application.destroy({ where: { id: app.id } });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Test failed:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
testApprovalSync();
|
||||
74
verify_db_logic.ts
Normal file
74
verify_db_logic.ts
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
import 'dotenv/config';
|
||||
import db from './src/database/models/index';
|
||||
const { User, Role } = db;
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
async function testFiltering() {
|
||||
console.log('--- Testing Backend Filtering Logic (Direct DB) ---');
|
||||
|
||||
const testCasesMap = [
|
||||
{
|
||||
name: 'National Role (NBH) - Should ignore location',
|
||||
params: { roleCode: 'NBH', zoneId: 'some-random-id' },
|
||||
expectedRoles: ['NBH']
|
||||
},
|
||||
{
|
||||
name: 'National Role (DD Head) - Should ignore location',
|
||||
params: { roleCode: 'DD Head', zoneId: 'some-random-id' },
|
||||
expectedRoles: ['DD Head']
|
||||
},
|
||||
{
|
||||
name: 'Zonal Role (RBM) - Should respect location (Matching)',
|
||||
params: { roleCode: 'RBM', zoneId: 'aedf5f64-3f95-4fa7-ac64-52c97cb330e7' }, // Known RBM zone from check_db_users
|
||||
expectedCount: 1
|
||||
},
|
||||
{
|
||||
name: 'Zonal Role (RBM) - Should respect location (Mismatch)',
|
||||
params: { roleCode: 'RBM', zoneId: '741e0e70-32d3-40d6-987f-5d2ffd54f152' }, // ZBH zone
|
||||
expectedCount: 0
|
||||
}
|
||||
];
|
||||
|
||||
for (const testCase of testCasesMap) {
|
||||
console.log(`\nTesting: ${testCase.name}`);
|
||||
const { roleCode, zoneId, regionId, areaId } = testCase.params;
|
||||
const whereClause: any = {};
|
||||
|
||||
// Replicating logic from admin.controller.ts
|
||||
if (roleCode) {
|
||||
if (Array.isArray(roleCode)) {
|
||||
whereClause.roleCode = { [Op.in]: roleCode };
|
||||
} else {
|
||||
whereClause.roleCode = roleCode;
|
||||
}
|
||||
}
|
||||
|
||||
const nationalRoles = ['NBH', 'DD Head'];
|
||||
const isNationalRole = (typeof roleCode === 'string' && nationalRoles.includes(roleCode)) ||
|
||||
(Array.isArray(roleCode) && roleCode.some(r => typeof r === 'string' && nationalRoles.includes(r)));
|
||||
|
||||
if (!isNationalRole) {
|
||||
if (zoneId) whereClause.zoneId = zoneId;
|
||||
if (regionId) whereClause.regionId = regionId;
|
||||
if (areaId) whereClause.areaId = areaId;
|
||||
}
|
||||
|
||||
const users = await User.findAll({
|
||||
where: whereClause,
|
||||
attributes: ['id', 'roleCode', 'zoneId']
|
||||
});
|
||||
|
||||
console.log(`Result: ${users.length} users found.`);
|
||||
if (users.length > 0) {
|
||||
console.log('Found Roles:', Array.from(new Set(users.map(u => u.roleCode))));
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testFiltering().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user