- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
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);
|
|
}
|