fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
This commit is contained in:
100
server/utils/user-access.ts
Normal file
100
server/utils/user-access.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { storage } from "../storage";
|
||||
import type { User } from "@shared/schema";
|
||||
|
||||
interface RoleNode {
|
||||
id: number;
|
||||
parentId: number | null;
|
||||
managerId: number | null;
|
||||
children: RoleNode[];
|
||||
}
|
||||
|
||||
function buildRoleTree(roles: { id: number; parentId: number | null; managerId: number | null }[]): RoleNode[] {
|
||||
const map = new Map<number, RoleNode>();
|
||||
for (const role of roles) {
|
||||
map.set(role.id, { id: role.id, parentId: role.parentId, managerId: role.managerId, children: [] });
|
||||
}
|
||||
const roots: RoleNode[] = [];
|
||||
for (const node of map.values()) {
|
||||
if (node.parentId && map.has(node.parentId)) {
|
||||
map.get(node.parentId)!.children.push(node);
|
||||
} else {
|
||||
roots.push(node);
|
||||
}
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
function findRoleNode(tree: RoleNode[], roleId: number): RoleNode | null {
|
||||
for (const node of tree) {
|
||||
if (node.id === roleId) return node;
|
||||
const found = findRoleNode(node.children, roleId);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectSubtreeRoleIds(node: RoleNode): number[] {
|
||||
const ids = [node.id];
|
||||
for (const child of node.children) {
|
||||
ids.push(...collectSubtreeRoleIds(child));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
export async function getManagedUserIds(
|
||||
currentUserId: number,
|
||||
organizationId: number
|
||||
): Promise<Set<number>> {
|
||||
const roles = await storage.getRoles(organizationId);
|
||||
const managedRoleIds = roles
|
||||
.filter(r => r.managerId === currentUserId)
|
||||
.map(r => r.id);
|
||||
|
||||
if (managedRoleIds.length === 0) return new Set();
|
||||
|
||||
const tree = buildRoleTree(roles.map(r => ({ id: r.id, parentId: r.parentId, managerId: r.managerId })));
|
||||
const allManagedRoleIds = new Set<number>();
|
||||
|
||||
for (const roleId of managedRoleIds) {
|
||||
const node = findRoleNode(tree, roleId);
|
||||
if (node) {
|
||||
for (const id of collectSubtreeRoleIds(node)) {
|
||||
allManagedRoleIds.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const managedUserIds = new Set<number>();
|
||||
for (const role of roles) {
|
||||
if (allManagedRoleIds.has(role.id)) {
|
||||
for (const member of role.members) {
|
||||
managedUserIds.add(member.userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return managedUserIds;
|
||||
}
|
||||
|
||||
export async function canManageUser(
|
||||
currentUser: User | undefined,
|
||||
targetUserId: number,
|
||||
organizationId: number
|
||||
): Promise<boolean> {
|
||||
if (!currentUser) return false;
|
||||
if (currentUser.appRole === "admin") return true;
|
||||
if (currentUser.id === targetUserId) return true;
|
||||
const managedIds = await getManagedUserIds(currentUser.id, organizationId);
|
||||
return managedIds.has(targetUserId);
|
||||
}
|
||||
|
||||
export async function filterUsersByAccess(
|
||||
currentUser: User | undefined,
|
||||
organizationId: number,
|
||||
users: { id: number }[]
|
||||
): Promise<{ id: number }[]> {
|
||||
if (!currentUser) return [];
|
||||
if (currentUser.appRole === "admin") return users;
|
||||
const managedIds = await getManagedUserIds(currentUser.id, organizationId);
|
||||
return users.filter(u => managedIds.has(u.id) || u.id === currentUser.id);
|
||||
}
|
||||
Reference in New Issue
Block a user