feat(gps): фильтры heartbeat-эхо/valid=false/spike; доступ к GPS и Финансам — матрица на /users (пользователи+роли)

This commit is contained in:
2026-08-26 17:13:49 +03:00
parent 2fb7ad6606
commit ac4f89d42e
16 changed files with 431 additions and 227 deletions

View File

@@ -0,0 +1,41 @@
import { db } from "../db";
import { roles, roleMembers } from "@shared/schema";
import { and, eq } from "drizzle-orm";
/**
* Доступ к модулям (GPS / Финансы) по флагам users.<flag> и roles.<flag>.
*
* Effective-доступ = users.<flag> OR любая организационная роль пользователя
* с флагом OR appRole = 'admin'. Оба флага NOT NULL DEFAULT true — при выкатке
* никто не блокируется; админ снимает галки в матрице на странице /users.
*
* Для Финансов флаг работает как ДОПОЛНИТЕЛЬНЫЙ ограничитель поверх
* существующего app-role права finance.manage (см. requirePermission) —
* только отбирает доступ, не добавляет.
*/
export type ModuleAccessKey = "gps" | "finance";
export interface ModuleAccessUser {
id: number;
organizationId: number;
appRole?: string;
gpsAccess?: boolean;
financeAccess?: boolean;
}
export async function hasModuleAccess(user: ModuleAccessUser, module: ModuleAccessKey): Promise<boolean> {
if (user.appRole === "admin") return true;
const userFlag = module === "gps" ? user.gpsAccess : user.financeAccess;
if (userFlag) return true;
// Флаг любой организационной роли пользователя
const rows = await db
.select({ gpsAccess: roles.gpsAccess, financeAccess: roles.financeAccess })
.from(roleMembers)
.innerJoin(roles, eq(roleMembers.roleId, roles.id))
.where(and(eq(roleMembers.userId, user.id), eq(roles.organizationId, user.organizationId)));
return rows.some((r) => (module === "gps" ? r.gpsAccess : r.financeAccess));
}