Files
iistwin/server/routes/roles.routes.ts
Ильяс Султанов 1f5ecb6da4 fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric,
  чтобы браузер не округлял значения через input type=number
- пробелы при вставке в number-поля удаляются
- бэкенд нормализует значения number-полей в строку перед сохранением
- добавлен хелпер normalizeFieldValueForStorage

Closes: искажение расчётного счёта и других длинных числовых полей
2026-07-07 21:03:40 +03:00

169 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Router } from "express";
import { storage } from "../storage";
import { authenticateToken, requirePermission, type AuthenticatedRequest } from "../middleware/auth.middleware";
import { tenantIsolation } from "../middleware/tenant.middleware";
import { canManageUser } from "../utils/user-access";
const router = Router();
// GET /api/roles — список всех ролей организации
router.get('/api/roles', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const roles = await storage.getRoles(req.organizationId!);
return res.json({ success: true, roles });
} catch (error) {
console.error('Get roles error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// GET /api/roles/:id — одна роль с участниками
router.get('/api/roles/:id', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) return res.status(400).json({ success: false, error: 'Некорректный ID' });
const role = await storage.getRoleMembers(id, req.organizationId!);
return res.json({ success: true, role });
} catch (error: any) {
if (error?.message === 'Роль не найдена') return res.status(404).json({ success: false, error: error.message });
console.error('Get role error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// POST /api/roles — создать роль (только admin)
router.post('/api/roles', authenticateToken, requirePermission('roles.manage'), tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const { name, description, parentId, managerId } = req.body;
if (!name?.trim()) return res.status(400).json({ success: false, error: 'Название роли обязательно' });
const role = await storage.createRole({
organizationId: req.organizationId!,
name: name.trim(),
description: description ?? null,
parentId: parentId ?? null,
managerId: managerId ?? null,
});
return res.status(201).json({ success: true, role });
} catch (error) {
console.error('Create role error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// PUT /api/roles/:id — обновить роль (только admin)
router.put('/api/roles/:id', authenticateToken, requirePermission('roles.manage'), tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) return res.status(400).json({ success: false, error: 'Некорректный ID' });
const { name, description, parentId, managerId } = req.body;
const updates: { name?: string; description?: string | null; parentId?: number | null; managerId?: number | null } = {};
if (name !== undefined) updates.name = name.trim();
if (description !== undefined) updates.description = description ?? null;
if (parentId !== undefined) updates.parentId = parentId ?? null;
if (managerId !== undefined) updates.managerId = managerId ?? null;
const role = await storage.updateRole(id, req.organizationId!, updates);
if (!role) return res.status(404).json({ success: false, error: 'Роль не найдена' });
return res.json({ success: true, role });
} catch (error) {
console.error('Update role error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// DELETE /api/roles/:id — удалить роль (только admin)
router.delete('/api/roles/:id', authenticateToken, requirePermission('roles.manage'), tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) return res.status(400).json({ success: false, error: 'Некорректный ID' });
await storage.deleteRole(id, req.organizationId!);
return res.json({ success: true });
} catch (error) {
console.error('Delete role error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// POST /api/roles/:id/members — добавить участника в роль (admin или руководитель роли)
router.post('/api/roles/:id/members', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const roleId = parseInt(req.params.id);
if (isNaN(roleId)) return res.status(400).json({ success: false, error: 'Некорректный ID роли' });
const { userId } = req.body;
if (!userId) return res.status(400).json({ success: false, error: 'userId обязателен' });
const role = await storage.getRole(roleId, req.organizationId!);
if (!role) return res.status(404).json({ success: false, error: 'Роль не найдена' });
const hasAdminPermission = req.user?.appRole === 'admin' || (req.user?.permissions?.includes('roles.manage') ?? false);
const isRoleManager = role.managerId === req.user?.id;
const canManage = await canManageUser(req.user, parseInt(userId), req.organizationId!);
if (!hasAdminPermission && !isRoleManager && !canManage) {
return res.status(403).json({ success: false, error: 'Недостаточно прав' });
}
await storage.addRoleMember(roleId, parseInt(userId), req.organizationId!);
return res.status(201).json({ success: true });
} catch (error: any) {
if (error?.message === 'Роль не найдена') return res.status(404).json({ success: false, error: error.message });
console.error('Add role member error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// DELETE /api/roles/:id/members/:userId — удалить участника из роли (admin или руководитель роли)
router.delete('/api/roles/:id/members/:userId', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const roleId = parseInt(req.params.id);
const userId = parseInt(req.params.userId);
if (isNaN(roleId) || isNaN(userId)) return res.status(400).json({ success: false, error: 'Некорректный ID' });
const role = await storage.getRole(roleId, req.organizationId!);
if (!role) return res.status(404).json({ success: false, error: 'Роль не найдена' });
const hasAdminPermission = req.user?.appRole === 'admin' || (req.user?.permissions?.includes('roles.manage') ?? false);
const isRoleManager = role.managerId === req.user?.id;
const canManage = await canManageUser(req.user, userId, req.organizationId!);
if (!hasAdminPermission && !isRoleManager && !canManage) {
return res.status(403).json({ success: false, error: 'Недостаточно прав' });
}
await storage.removeRoleMember(roleId, userId, req.organizationId!);
return res.json({ success: true });
} catch (error: any) {
if (error?.message === 'Роль не найдена') return res.status(404).json({ success: false, error: error.message });
console.error('Remove role member error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// PUT /api/roles/:id/members/:userId/move — переместить участника в другую роль
router.put('/api/roles/:id/members/:userId/move', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const sourceRoleId = parseInt(req.params.id);
const userId = parseInt(req.params.userId);
const { targetRoleId } = req.body;
if (isNaN(sourceRoleId) || isNaN(userId)) {
return res.status(400).json({ success: false, error: 'Некорректный ID' });
}
if (targetRoleId !== null && (typeof targetRoleId !== 'number' || isNaN(targetRoleId))) {
return res.status(400).json({ success: false, error: 'Некорректный targetRoleId' });
}
const hasAdminPermission = req.user?.appRole === 'admin' || (req.user?.permissions?.includes('roles.manage') ?? false);
const canManage = await canManageUser(req.user, userId, req.organizationId!);
if (!hasAdminPermission && !canManage) {
return res.status(403).json({ success: false, error: 'Недостаточно прав' });
}
await storage.moveRoleMember(sourceRoleId, targetRoleId, userId, req.organizationId!);
return res.json({ success: true });
} catch (error: any) {
if (error?.message?.includes('не найден')) return res.status(404).json({ success: false, error: error.message });
console.error('Move role member error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
export default router;