675 lines
27 KiB
TypeScript
675 lines
27 KiB
TypeScript
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';
|
|
import { AuthRequest } from '../../types/express.types.js';
|
|
import { syncLocationManagers, syncRegionManager, syncZoneManager } from '../master/syncHierarchy.service.js';
|
|
import { resolveManagerCode } from '../../services/userRoleCode.service.js';
|
|
|
|
const upsertUserAssignments = async (
|
|
userId: string,
|
|
assignments: any[],
|
|
actorUserId?: string
|
|
) => {
|
|
if (!Array.isArray(assignments)) return;
|
|
|
|
await db.UserRole.destroy({ where: { userId } });
|
|
|
|
for (let i = 0; i < assignments.length; i++) {
|
|
const assignment = assignments[i] || {};
|
|
const roleCode = assignment.roleCode || assignment.role;
|
|
if (!roleCode) continue;
|
|
|
|
const role = await Role.findOne({ where: { roleCode } });
|
|
if (!role) continue;
|
|
const managerCode = await resolveManagerCode(role.id, roleCode, null);
|
|
|
|
const createdRole = await db.UserRole.create({
|
|
userId,
|
|
roleId: role.id,
|
|
districtId: assignment.locationId || assignment.districtId || null,
|
|
zoneId: assignment.zoneId || null,
|
|
regionId: assignment.regionId || null,
|
|
managerCode,
|
|
isPrimary: assignment.isPrimary !== undefined ? Boolean(assignment.isPrimary) : i === 0,
|
|
isActive: assignment.isActive !== undefined ? Boolean(assignment.isActive) : true,
|
|
effectiveFrom: assignment.effectiveFrom || null,
|
|
effectiveTo: assignment.effectiveTo || null,
|
|
assignedBy: actorUserId || null
|
|
});
|
|
|
|
// Trigger Sync
|
|
const targetId = assignment.locationId || assignment.districtId;
|
|
if (targetId) await syncLocationManagers(targetId);
|
|
if (assignment.regionId) await syncRegionManager(assignment.regionId);
|
|
if (assignment.zoneId) await syncZoneManager(assignment.zoneId);
|
|
}
|
|
};
|
|
|
|
// --- Roles Management ---
|
|
|
|
export const getRoles = async (req: Request, res: Response) => {
|
|
try {
|
|
const roles = await Role.findAll({
|
|
include: [
|
|
{
|
|
model: Permission,
|
|
as: 'permissions',
|
|
through: { attributes: [] }
|
|
},
|
|
{
|
|
model: User,
|
|
as: 'users',
|
|
attributes: ['id']
|
|
}
|
|
],
|
|
order: [['roleName', 'ASC']]
|
|
});
|
|
|
|
// Map to include userCount
|
|
const result = roles.map((r: any) => ({
|
|
...r.toJSON(),
|
|
userCount: r.users?.length || 0
|
|
}));
|
|
|
|
res.json({ success: true, data: result });
|
|
} catch (error) {
|
|
console.error('Get roles error:', error);
|
|
res.status(500).json({ success: false, message: 'Error fetching roles' });
|
|
}
|
|
};
|
|
|
|
export const createRole = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { roleCode, roleName, description, permissionIds } = req.body; // permissionIds: string[]
|
|
|
|
const role = await Role.create({ roleCode, roleName, description });
|
|
|
|
if (permissionIds && permissionIds.length > 0) {
|
|
for (const pid of permissionIds) {
|
|
await RolePermission.create({
|
|
roleId: role.id,
|
|
permissionId: pid
|
|
});
|
|
}
|
|
}
|
|
|
|
await AuditLog.create({
|
|
userId: req.user?.id,
|
|
action: AUDIT_ACTIONS.CREATED,
|
|
entityType: 'role',
|
|
entityId: role.id,
|
|
newData: req.body
|
|
});
|
|
|
|
res.status(201).json({ success: true, data: role, message: 'Role created successfully' });
|
|
} catch (error) {
|
|
console.error('Create role error:', error);
|
|
res.status(500).json({ success: false, message: 'Error creating role' });
|
|
}
|
|
};
|
|
|
|
export const updateRole = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { roleName, description, permissionIds, permissions, isActive } = req.body;
|
|
const permsToUpdate = permissionIds || permissions;
|
|
|
|
const role = await Role.findByPk(id);
|
|
if (!role) return res.status(404).json({ success: false, message: 'Role not found' });
|
|
|
|
await role.update({ roleName, description, isActive });
|
|
|
|
if (permsToUpdate && Array.isArray(permsToUpdate)) {
|
|
// Resolve IDs if they are passed as codes
|
|
const resolvedIds: string[] = [];
|
|
for (const pid of permsToUpdate) {
|
|
if (pid.includes(':') || /^[A-Z_]+$/.test(pid)) {
|
|
const perm = await Permission.findOne({ where: { permissionCode: pid } });
|
|
if (perm) resolvedIds.push(perm.id);
|
|
} else {
|
|
resolvedIds.push(pid);
|
|
}
|
|
}
|
|
|
|
// Remove existing permissions and re-add new ones
|
|
await RolePermission.destroy({ where: { roleId: id } });
|
|
for (const resolvedId of resolvedIds) {
|
|
await RolePermission.create({
|
|
roleId: id,
|
|
permissionId: resolvedId
|
|
});
|
|
}
|
|
}
|
|
|
|
await AuditLog.create({
|
|
userId: req.user?.id,
|
|
action: AUDIT_ACTIONS.UPDATED,
|
|
entityType: 'role',
|
|
entityId: id,
|
|
newData: req.body
|
|
});
|
|
|
|
res.json({ success: true, message: 'Role updated successfully' });
|
|
} catch (error) {
|
|
console.error('Update role error:', error);
|
|
res.status(500).json({ success: false, message: 'Error updating role' });
|
|
}
|
|
};
|
|
|
|
// --- Permissions Management ---
|
|
|
|
export const getPermissions = async (req: Request, res: Response) => {
|
|
try {
|
|
const permissions = await Permission.findAll({ order: [['module', 'ASC']] });
|
|
res.json({ success: true, data: permissions });
|
|
} catch (error) {
|
|
console.error('Get permissions error:', error);
|
|
res.status(500).json({ success: false, message: 'Error fetching permissions' });
|
|
}
|
|
};
|
|
|
|
// --- User Management (Admin) ---
|
|
|
|
export const getAllUsers = async (req: Request, res: Response) => {
|
|
try {
|
|
const { roleCode, locationId, search, page = 1, limit = 100, isExternal } = req.query as any;
|
|
const whereClause: any = {};
|
|
|
|
// 0. External filter
|
|
if (isExternal !== undefined) {
|
|
whereClause.isExternal = isExternal === 'true';
|
|
}
|
|
|
|
// 1. Search filter
|
|
if (search) {
|
|
whereClause[Op.or] = [
|
|
{ fullName: { [Op.iLike]: `%${search}%` } },
|
|
{ email: { [Op.iLike]: `%${search}%` } },
|
|
{ employeeId: { [Op.iLike]: `%${search}%` } }
|
|
];
|
|
}
|
|
|
|
// 2. Role filter
|
|
let rawRoleCode: any = roleCode || req.query['roleCode[]'];
|
|
let finalRoleCodes: string[] = [];
|
|
|
|
if (rawRoleCode) {
|
|
if (Array.isArray(rawRoleCode)) {
|
|
finalRoleCodes = rawRoleCode;
|
|
} else if (typeof rawRoleCode === 'string') {
|
|
finalRoleCodes = rawRoleCode.split(',').map(r => r.trim());
|
|
}
|
|
}
|
|
|
|
if (finalRoleCodes.length > 0) {
|
|
whereClause.roleCode = { [Op.in]: finalRoleCodes };
|
|
}
|
|
|
|
const nationalRoles = ['NBH', 'DD Head', 'Super Admin'];
|
|
const isNationalRole = finalRoleCodes.some(r => nationalRoles.includes(r));
|
|
|
|
if (!isNationalRole && locationId) {
|
|
const district: any = await db.District.findByPk(locationId as string, {
|
|
attributes: ['id', 'zoneId', 'regionId', 'stateId']
|
|
});
|
|
|
|
if (district) {
|
|
const relevantIds = [district.id, district.zoneId, district.regionId, district.stateId].filter(Boolean);
|
|
whereClause[Op.or] = [
|
|
{ districtId: { [Op.in]: relevantIds } },
|
|
{ zoneId: { [Op.in]: relevantIds } },
|
|
{ regionId: { [Op.in]: relevantIds } },
|
|
{ stateId: { [Op.in]: relevantIds } },
|
|
{ '$userRoles.districtId$': { [Op.in]: relevantIds } },
|
|
{ '$userRoles.zoneId$': { [Op.in]: relevantIds } },
|
|
{ '$userRoles.regionId$': { [Op.in]: relevantIds } }
|
|
];
|
|
}
|
|
}
|
|
|
|
const { count, rows: users } = await User.findAndCountAll({
|
|
where: whereClause,
|
|
attributes: { exclude: ['password'] },
|
|
limit: Number(limit),
|
|
offset: (Number(page) - 1) * Number(limit),
|
|
include: [
|
|
{
|
|
model: Role,
|
|
as: 'role',
|
|
include: [
|
|
{
|
|
model: Permission,
|
|
as: 'permissions',
|
|
through: { attributes: [] }
|
|
}
|
|
]
|
|
},
|
|
{ model: db.District, as: 'district' },
|
|
{
|
|
model: db.UserRole,
|
|
as: 'userRoles',
|
|
include: [
|
|
{
|
|
model: db.District,
|
|
as: 'district',
|
|
attributes: ['id', 'name', 'stateId', 'regionId', 'zoneId'],
|
|
include: [
|
|
{ model: db.State, as: 'state', attributes: ['id', 'name'] },
|
|
{ model: db.Region, as: 'region', attributes: ['id', 'name'] },
|
|
{ model: db.Zone, as: 'zone', attributes: ['id', 'name'] }
|
|
]
|
|
},
|
|
{ model: db.Zone, as: 'zone', attributes: ['id', 'name'] },
|
|
{ model: db.Region, as: 'region', attributes: ['id', 'name'] }
|
|
]
|
|
}
|
|
],
|
|
order: [['createdAt', 'DESC']],
|
|
distinct: true
|
|
});
|
|
|
|
const result = users.map((u: any) => {
|
|
const userJson = u.toJSON();
|
|
const assignments = userJson.userRoles || [];
|
|
|
|
// territories mapping — provide fallbacks to the nested location fields
|
|
const territories = assignments.map((a: any) => ({
|
|
role: a.role?.roleName,
|
|
roleCode: a.role?.roleCode,
|
|
districtId: a.districtId,
|
|
districtName: a.district?.name,
|
|
locationType: 'district',
|
|
managerCode: a.managerCode,
|
|
zoneId: a.zoneId || a.district?.zoneId,
|
|
zone: a.zone?.name || a.district?.zone?.name,
|
|
regionId: a.regionId || a.district?.regionId,
|
|
region: a.region?.name || a.district?.region?.name,
|
|
stateId: a.district?.state?.id || a.district?.stateId,
|
|
state: a.district?.state?.name,
|
|
isActive: a.isActive
|
|
}));
|
|
|
|
userJson.territoryProfile = territories;
|
|
userJson.allRoles = Array.from(new Set([
|
|
u.role?.roleCode,
|
|
u.role?.roleName,
|
|
...assignments.flatMap((a: any) => [a.role?.roleCode, a.role?.roleName])
|
|
].filter(Boolean)));
|
|
userJson.allZones = Array.from(new Set(
|
|
territories.map((t: any) => t.zone).filter(Boolean).map((z: string) => z.toUpperCase())
|
|
));
|
|
userJson.allRegions = Array.from(new Set(
|
|
territories.map((t: any) => t.region).filter(Boolean).map((r: string) => r.toUpperCase())
|
|
));
|
|
|
|
return userJson;
|
|
});
|
|
|
|
res.json({ success: true, data: result, total: count });
|
|
} catch (error) {
|
|
console.error('Get users error:', error);
|
|
res.status(500).json({ success: false, message: 'Error fetching users' });
|
|
}
|
|
}
|
|
|
|
export const createUser = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const {
|
|
fullName, email, roleCode,
|
|
employeeId, mobileNumber, department, designation,
|
|
locationId,
|
|
assignments,
|
|
districts, // New: ASM managed areas
|
|
regionIds, // New: ZM managed regions
|
|
asmCode, // New: ASM code
|
|
zmCode // New: ZM code
|
|
} = req.body;
|
|
|
|
|
|
// Validate required fields
|
|
if (!fullName || !email || !roleCode) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Full Name, Email, and Role are required'
|
|
});
|
|
}
|
|
|
|
// Check if user already exists (Email)
|
|
const existingEmail = await User.findOne({ where: { email } });
|
|
if (existingEmail) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'User with this email already exists'
|
|
});
|
|
}
|
|
|
|
// Check if user already exists (Employee ID)
|
|
if (employeeId) {
|
|
const existingEmpId = await User.findOne({ where: { employeeId } });
|
|
if (existingEmpId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: `User with Employee ID ${employeeId} already exists`
|
|
});
|
|
}
|
|
}
|
|
|
|
// Hash default password
|
|
const hashedPassword = await bcrypt.hash('Admin@123', 10);
|
|
|
|
// Location is optional. Only persist to user.districtId if the value is a valid district UUID.
|
|
let safeDistrictId: string | null = null;
|
|
if (locationId) {
|
|
const districtExists = await db.District.findByPk(locationId);
|
|
safeDistrictId = districtExists ? locationId : null;
|
|
}
|
|
|
|
// Create user
|
|
const user = await User.create({
|
|
fullName,
|
|
email,
|
|
password: hashedPassword,
|
|
roleCode,
|
|
status: 'active',
|
|
isActive: true,
|
|
employeeId,
|
|
mobileNumber,
|
|
department,
|
|
designation,
|
|
districtId: safeDistrictId
|
|
});
|
|
|
|
if (Array.isArray(assignments) && assignments.length > 0) {
|
|
await upsertUserAssignments(user.id, assignments, req.user?.id);
|
|
} else if (districts && Array.isArray(districts) && roleCode === 'ASM') {
|
|
const targetRole = await Role.findOne({ where: { roleCode: 'ASM' } });
|
|
if (targetRole) {
|
|
for (const distId of districts) {
|
|
const sampleDistrict = await db.District.findByPk(distId);
|
|
const managerCode = await resolveManagerCode(targetRole.id, 'ASM', null);
|
|
await db.UserRole.create({
|
|
userId: user.id,
|
|
roleId: targetRole.id,
|
|
districtId: distId,
|
|
zoneId: sampleDistrict?.zoneId || null,
|
|
regionId: sampleDistrict?.regionId || null,
|
|
managerCode,
|
|
isPrimary: false,
|
|
isActive: true,
|
|
assignedBy: req.user?.id || null
|
|
});
|
|
await syncLocationManagers(distId);
|
|
}
|
|
}
|
|
} else if ((regionIds || districts) && (roleCode === 'ZM' || roleCode === 'DD-ZM')) {
|
|
const targetRole = await Role.findOne({ where: { roleCode: roleCode } });
|
|
if (targetRole) {
|
|
// If districts are passed for ZM, we convert them to unique regionIds for consistency
|
|
let finalRegionIds = regionIds || [];
|
|
if (finalRegionIds.length === 0 && districts && districts.length > 0) {
|
|
const mappedDistricts = await db.District.findAll({ where: { id: { [Op.in]: districts } } });
|
|
finalRegionIds = Array.from(new Set(mappedDistricts.map((d: any) => d.regionId).filter(Boolean)));
|
|
}
|
|
|
|
for (const regId of finalRegionIds) {
|
|
const region = await db.Region.findByPk(regId);
|
|
const managerCode = await resolveManagerCode(targetRole.id, roleCode, null);
|
|
await db.UserRole.create({
|
|
userId: user.id,
|
|
roleId: targetRole.id,
|
|
regionId: regId,
|
|
zoneId: region?.zoneId || null,
|
|
managerCode,
|
|
isActive: true,
|
|
assignedBy: req.user?.id || null
|
|
});
|
|
|
|
// Trigger sync for all districts in this region to update legacy zmId
|
|
const regionDistricts = await db.District.findAll({ where: { regionId: regId } });
|
|
for (const d of regionDistricts) await syncLocationManagers(d.id);
|
|
}
|
|
}
|
|
} else if (roleCode) {
|
|
const role = await Role.findOne({ where: { roleCode } });
|
|
if (role) {
|
|
const managerCode = await resolveManagerCode(role.id, roleCode, null);
|
|
await db.UserRole.create({
|
|
userId: user.id,
|
|
roleId: role.id,
|
|
districtId: safeDistrictId,
|
|
managerCode,
|
|
isPrimary: true,
|
|
isActive: true,
|
|
assignedBy: req.user?.id || null
|
|
});
|
|
}
|
|
}
|
|
|
|
await AuditLog.create({
|
|
userId: req.user?.id,
|
|
action: AUDIT_ACTIONS.CREATED,
|
|
entityType: 'user',
|
|
entityId: user.id,
|
|
newData: req.body
|
|
});
|
|
|
|
res.status(201).json({ success: true, message: 'User created successfully', data: user });
|
|
} catch (error: any) {
|
|
console.error('Create user error:', error);
|
|
if (error.name === 'SequelizeUniqueConstraintError') {
|
|
const field = error.errors[0]?.path || 'field';
|
|
const value = error.errors[0]?.value || '';
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: `${field} "${value}" already exists. Please use a unique value.`
|
|
});
|
|
}
|
|
res.status(500).json({ success: false, message: 'Error creating user' });
|
|
}
|
|
};
|
|
|
|
export const updateUserStatus = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { status, isActive } = req.body;
|
|
|
|
const user = await User.findByPk(id);
|
|
if (!user) return res.status(404).json({ success: false, message: 'User not found' });
|
|
|
|
await user.update({ status, isActive });
|
|
|
|
// If user is deactivated, clear their ASM assignments in District table
|
|
if (isActive === false) {
|
|
await db.District.update({ asmId: null }, { where: { asmId: id } });
|
|
}
|
|
|
|
await AuditLog.create({
|
|
userId: req.user?.id,
|
|
action: AUDIT_ACTIONS.UPDATED,
|
|
entityType: 'user',
|
|
entityId: id,
|
|
newData: { status, isActive }
|
|
});
|
|
|
|
res.json({ success: true, message: 'User status updated' });
|
|
} catch (error) {
|
|
console.error('Update user status error:', error);
|
|
res.status(500).json({ success: false, message: 'Error updating user status' });
|
|
}
|
|
};
|
|
|
|
export const updateUser = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const {
|
|
fullName, email, roleCode, status, isActive, employeeId,
|
|
mobileNumber, department, designation,
|
|
locationId,
|
|
assignments,
|
|
districts, // New: ASM managed areas
|
|
regionIds, // New: ZM managed regions
|
|
asmCode, // New: ASM code
|
|
zmCode, // New: ZM code
|
|
password // Optional password update
|
|
} = req.body;
|
|
|
|
const user = await User.findByPk(id);
|
|
if (!user) return res.status(404).json({ success: false, message: 'User not found' });
|
|
|
|
const oldData = user.toJSON();
|
|
const updates: any = {
|
|
fullName: fullName || user.fullName,
|
|
email: email || user.email,
|
|
roleCode: roleCode || user.roleCode,
|
|
status: status || user.status,
|
|
isActive: isActive !== undefined ? isActive : user.isActive,
|
|
employeeId: employeeId || user.employeeId,
|
|
mobileNumber: mobileNumber || user.mobileNumber,
|
|
department: department || user.department,
|
|
designation: designation || user.designation
|
|
};
|
|
|
|
// NEW: Validate locationId if provided (must exist in districts table, otherwise set to null on User record)
|
|
if (locationId !== undefined) {
|
|
if (locationId === '' || locationId === null) {
|
|
updates.districtId = null;
|
|
} else {
|
|
const districtExists = await db.District.findByPk(locationId);
|
|
updates.districtId = districtExists ? locationId : null;
|
|
}
|
|
}
|
|
|
|
// If password is provided, hash it and update
|
|
if (password && password.trim() !== '') {
|
|
updates.password = await bcrypt.hash(password, 10);
|
|
}
|
|
|
|
await user.update(updates);
|
|
|
|
if (Array.isArray(assignments)) {
|
|
await upsertUserAssignments(id as string, assignments, req.user?.id);
|
|
} else if (districts && Array.isArray(districts) && roleCode === 'ASM') {
|
|
const targetRole = await Role.findOne({ where: { roleCode: 'ASM' } });
|
|
if (!targetRole) throw new Error(`ASM role not found`);
|
|
|
|
await db.UserRole.destroy({ where: { userId: id, roleId: targetRole.id } });
|
|
await db.District.update({ asmId: null, asmCode: null }, { where: { asmId: id } });
|
|
|
|
for (const distId of districts) {
|
|
const sampleDistrict = await db.District.findByPk(distId);
|
|
const managerCode = await resolveManagerCode(targetRole.id, 'ASM', null);
|
|
await db.UserRole.create({
|
|
userId: id,
|
|
roleId: targetRole.id,
|
|
districtId: distId,
|
|
zoneId: sampleDistrict?.zoneId || null,
|
|
regionId: sampleDistrict?.regionId || null,
|
|
managerCode,
|
|
isActive: true,
|
|
assignedBy: req.user?.id || null
|
|
});
|
|
await syncLocationManagers(distId);
|
|
}
|
|
} else if ((regionIds || districts) && (roleCode === 'ZM' || roleCode === 'DD-ZM')) {
|
|
const targetRole = await Role.findOne({ where: { roleCode: roleCode } });
|
|
if (!targetRole) throw new Error(`${roleCode} role not found`);
|
|
|
|
await db.UserRole.destroy({ where: { userId: id, roleId: targetRole.id } });
|
|
await db.District.update({ zmId: null, zmCode: null }, { where: { zmId: id } });
|
|
|
|
let finalRegionIds = regionIds || [];
|
|
if (finalRegionIds.length === 0 && districts && districts.length > 0) {
|
|
const mappedDistricts = await db.District.findAll({ where: { id: { [Op.in]: districts } } });
|
|
finalRegionIds = Array.from(new Set(mappedDistricts.map((d: any) => d.regionId).filter(Boolean)));
|
|
}
|
|
|
|
for (const regId of finalRegionIds) {
|
|
const region = await db.Region.findByPk(regId);
|
|
const managerCode = await resolveManagerCode(targetRole.id, roleCode, null);
|
|
await db.UserRole.create({
|
|
userId: id,
|
|
roleId: targetRole.id,
|
|
regionId: regId,
|
|
zoneId: region?.zoneId || null,
|
|
managerCode,
|
|
isActive: true,
|
|
assignedBy: req.user?.id || null
|
|
});
|
|
|
|
const regionDistricts = await db.District.findAll({ where: { regionId: regId } });
|
|
for (const d of regionDistricts) await syncLocationManagers(d.id);
|
|
}
|
|
}
|
|
else if (roleCode !== undefined || locationId !== undefined) {
|
|
const primaryRoleCode = roleCode || user.roleCode;
|
|
if (primaryRoleCode) {
|
|
const role = await Role.findOne({ where: { roleCode: primaryRoleCode } });
|
|
if (role) {
|
|
await db.UserRole.destroy({ where: { userId: id, isPrimary: true } });
|
|
const created = await db.UserRole.create({
|
|
userId: id,
|
|
roleId: role.id,
|
|
districtId: updates.districtId ?? user.districtId ?? null,
|
|
isPrimary: true,
|
|
isActive: updates.isActive,
|
|
assignedBy: req.user?.id || null
|
|
});
|
|
// Sync primary location if exists
|
|
if (created.districtId) await syncLocationManagers(created.districtId);
|
|
if (created.regionId) await syncRegionManager(created.regionId);
|
|
if (created.zoneId) await syncZoneManager(created.zoneId);
|
|
}
|
|
}
|
|
}
|
|
|
|
await AuditLog.create({
|
|
userId: req.user?.id,
|
|
action: AUDIT_ACTIONS.UPDATED,
|
|
entityType: 'user',
|
|
entityId: id,
|
|
oldData,
|
|
newData: req.body
|
|
});
|
|
|
|
res.json({ success: true, message: 'User updated successfully', data: user });
|
|
} catch (error: any) {
|
|
console.error('Update user error:', error);
|
|
if (error.name === 'SequelizeUniqueConstraintError') {
|
|
const field = error.errors[0]?.path || 'field';
|
|
const value = error.errors[0]?.value || '';
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: `${field} "${value}" already exists. Please use a unique value.`
|
|
});
|
|
}
|
|
res.status(500).json({ success: false, message: 'Error updating user' });
|
|
}
|
|
};
|
|
|
|
// --- Dealer Codes ---
|
|
|
|
export const generateDealerCode = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
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
|
|
|
|
const timestamp = Date.now().toString().slice(-6);
|
|
const code = `DLR-${timestamp}`;
|
|
|
|
const dealerCode = await DealerCode.create({
|
|
code,
|
|
isUsed: false,
|
|
generatedBy: req.user?.id
|
|
});
|
|
|
|
res.status(201).json({ success: true, data: dealerCode });
|
|
} catch (error) {
|
|
console.error('Generate dealer code error:', error);
|
|
res.status(500).json({ success: false, message: 'Error generating dealer code' });
|
|
}
|
|
};
|