fix roles view
This commit is contained in:
@@ -134,6 +134,42 @@ class AdminController {
|
||||
}
|
||||
}
|
||||
|
||||
async createPosition(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.id;
|
||||
const position = await adminService.createPosition(
|
||||
{
|
||||
title: req.body.title,
|
||||
titleAr: req.body.titleAr,
|
||||
departmentId: req.body.departmentId,
|
||||
level: req.body.level,
|
||||
code: req.body.code,
|
||||
},
|
||||
userId
|
||||
);
|
||||
res.status(201).json(ResponseFormatter.success(position));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async updatePosition(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.id;
|
||||
const position = await adminService.updatePosition(
|
||||
req.params.id,
|
||||
{
|
||||
title: req.body.title,
|
||||
titleAr: req.body.titleAr,
|
||||
},
|
||||
userId
|
||||
);
|
||||
res.json(ResponseFormatter.success(position));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async updatePositionPermissions(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const position = await adminService.updatePositionPermissions(
|
||||
|
||||
@@ -89,6 +89,34 @@ router.get(
|
||||
adminController.getPositions
|
||||
);
|
||||
|
||||
// Create role
|
||||
router.post(
|
||||
'/positions',
|
||||
authorize('admin', 'roles', 'create'),
|
||||
[
|
||||
body('title').notEmpty().trim(),
|
||||
body('titleAr').optional().isString().trim(),
|
||||
body('departmentId').isUUID(),
|
||||
body('level').optional().isInt({ min: 1 }),
|
||||
body('code').optional().isString().trim(),
|
||||
],
|
||||
validate,
|
||||
adminController.createPosition
|
||||
);
|
||||
|
||||
// Update role name (title/titleAr)
|
||||
router.put(
|
||||
'/positions/:id',
|
||||
authorize('admin', 'roles', 'update'),
|
||||
[
|
||||
param('id').isUUID(),
|
||||
body('title').optional().notEmpty().trim(),
|
||||
body('titleAr').optional().isString().trim(),
|
||||
],
|
||||
validate,
|
||||
adminController.updatePosition
|
||||
);
|
||||
|
||||
// Delete (soft delete) a role/position
|
||||
router.delete(
|
||||
'/positions/:id',
|
||||
|
||||
@@ -39,6 +39,19 @@ export interface AuditLogFilters {
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface CreatePositionData {
|
||||
title: string;
|
||||
titleAr?: string;
|
||||
departmentId: string;
|
||||
level?: number;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export interface UpdatePositionData {
|
||||
title?: string;
|
||||
titleAr?: string;
|
||||
}
|
||||
|
||||
class AdminService {
|
||||
// ========== USERS ==========
|
||||
|
||||
@@ -94,7 +107,7 @@ class AdminService {
|
||||
]);
|
||||
|
||||
const sanitized = users.map((u) => {
|
||||
const { password: _, ...rest } = u;
|
||||
const { password: _, ...rest } = u as any;
|
||||
return rest;
|
||||
});
|
||||
|
||||
@@ -124,7 +137,7 @@ class AdminService {
|
||||
throw new AppError(404, 'المستخدم غير موجود - User not found');
|
||||
}
|
||||
|
||||
const { password: _, ...rest } = user;
|
||||
const { password: _, ...rest } = user as any;
|
||||
return rest;
|
||||
}
|
||||
|
||||
@@ -223,7 +236,7 @@ class AdminService {
|
||||
...(data.email && { email: data.email }),
|
||||
...(data.username && { username: data.username }),
|
||||
...(data.isActive !== undefined && { isActive: data.isActive }),
|
||||
...(data.employeeId !== undefined && { employeeId: data.employeeId || null }),
|
||||
...(data.employeeId !== undefined && { employeeId: (data.employeeId as any) || null }),
|
||||
};
|
||||
|
||||
if (data.password && data.password.length >= 8) {
|
||||
@@ -242,7 +255,7 @@ class AdminService {
|
||||
},
|
||||
});
|
||||
|
||||
const { password: _, ...sanitized } = user;
|
||||
const { password: _, ...sanitized } = user as any;
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'USER',
|
||||
@@ -273,7 +286,7 @@ class AdminService {
|
||||
},
|
||||
});
|
||||
|
||||
const { password: _, ...sanitized } = updated;
|
||||
const { password: _, ...sanitized } = updated as any;
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'USER',
|
||||
@@ -380,7 +393,7 @@ class AdminService {
|
||||
const positions = await prisma.position.findMany({
|
||||
where: { isActive: true },
|
||||
include: {
|
||||
department: { select: { name: true, nameAr: true } },
|
||||
department: { select: { id: true, name: true, nameAr: true } },
|
||||
permissions: true,
|
||||
_count: {
|
||||
select: {
|
||||
@@ -406,6 +419,120 @@ class AdminService {
|
||||
return withUserCount;
|
||||
}
|
||||
|
||||
private async generateUniqueCode(base: string) {
|
||||
const cleaned = (base || 'ROLE')
|
||||
.toUpperCase()
|
||||
.replace(/[^A-Z0-9]+/g, '_')
|
||||
.replace(/^_+|_+$/g, '')
|
||||
.slice(0, 18) || 'ROLE';
|
||||
|
||||
for (let i = 0; i < 25; i++) {
|
||||
const suffix = Math.floor(1000 + Math.random() * 9000);
|
||||
const code = `${cleaned}_${suffix}`;
|
||||
const exists = await prisma.position.findUnique({ where: { code } });
|
||||
if (!exists) return code;
|
||||
}
|
||||
|
||||
// fallback
|
||||
return `${cleaned}_${Date.now()}`;
|
||||
}
|
||||
|
||||
async createPosition(data: CreatePositionData, createdById: string) {
|
||||
const title = (data.title || '').trim();
|
||||
const titleAr = (data.titleAr || '').trim();
|
||||
|
||||
if (!title && !titleAr) {
|
||||
throw new AppError(400, 'اسم الدور مطلوب - Role name is required');
|
||||
}
|
||||
|
||||
const department = await prisma.department.findUnique({
|
||||
where: { id: data.departmentId },
|
||||
});
|
||||
|
||||
if (!department || !department.isActive) {
|
||||
throw new AppError(400, 'القسم غير موجود - Department not found');
|
||||
}
|
||||
|
||||
let code = (data.code || '').trim();
|
||||
if (code) {
|
||||
code = code.toUpperCase().replace(/[^A-Z0-9_]+/g, '_');
|
||||
const exists = await prisma.position.findUnique({ where: { code } });
|
||||
if (exists) {
|
||||
throw new AppError(400, 'الكود مستخدم بالفعل - Code already exists');
|
||||
}
|
||||
} else {
|
||||
code = await this.generateUniqueCode(title || titleAr || 'ROLE');
|
||||
}
|
||||
|
||||
const level = Number.isFinite(data.level as any) ? Math.max(1, Number(data.level)) : 1;
|
||||
|
||||
const created = await prisma.position.create({
|
||||
data: {
|
||||
title: title || titleAr,
|
||||
titleAr: titleAr || null,
|
||||
code,
|
||||
departmentId: data.departmentId,
|
||||
level,
|
||||
},
|
||||
});
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'POSITION',
|
||||
entityId: created.id,
|
||||
action: 'CREATE',
|
||||
userId: createdById,
|
||||
changes: {
|
||||
created: {
|
||||
title: created.title,
|
||||
titleAr: created.titleAr,
|
||||
code: created.code,
|
||||
departmentId: created.departmentId,
|
||||
level: created.level,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const all = await this.getPositions();
|
||||
return all.find((p: any) => p.id === created.id) || created;
|
||||
}
|
||||
|
||||
async updatePosition(positionId: string, data: UpdatePositionData, updatedById: string) {
|
||||
const existing = await prisma.position.findUnique({ where: { id: positionId } });
|
||||
if (!existing) {
|
||||
throw new AppError(404, 'الدور غير موجود - Position not found');
|
||||
}
|
||||
|
||||
const nextTitle = data.title !== undefined ? (data.title || '').trim() : existing.title;
|
||||
const nextTitleAr = data.titleAr !== undefined ? (data.titleAr || '').trim() : (existing.titleAr || '');
|
||||
|
||||
const finalTitle = nextTitle || nextTitleAr;
|
||||
if (!finalTitle) {
|
||||
throw new AppError(400, 'اسم الدور مطلوب - Role name is required');
|
||||
}
|
||||
|
||||
const updated = await prisma.position.update({
|
||||
where: { id: positionId },
|
||||
data: {
|
||||
title: finalTitle,
|
||||
titleAr: nextTitleAr ? nextTitleAr : null,
|
||||
},
|
||||
});
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'POSITION',
|
||||
entityId: positionId,
|
||||
action: 'UPDATE',
|
||||
userId: updatedById,
|
||||
changes: {
|
||||
before: { title: existing.title, titleAr: existing.titleAr },
|
||||
after: { title: updated.title, titleAr: updated.titleAr },
|
||||
},
|
||||
});
|
||||
|
||||
const all = await this.getPositions();
|
||||
return all.find((p: any) => p.id === positionId) || updated;
|
||||
}
|
||||
|
||||
async updatePositionPermissions(positionId: string, permissions: Array<{ module: string; resource: string; actions: string[] }>) {
|
||||
const position = await prisma.position.findUnique({ where: { id: positionId } });
|
||||
if (!position) {
|
||||
@@ -427,7 +554,7 @@ class AdminService {
|
||||
});
|
||||
}
|
||||
|
||||
return this.getPositions().then((pos) => pos.find((p) => p.id === positionId) || position);
|
||||
return this.getPositions().then((pos: any) => pos.find((p: any) => p.id === positionId) || position);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user