import { Prisma } from '@prisma/client' import { prisma } from './prisma' import { findNearestGroupMemberships, normalizeOrganizationPath, organizationPathChain, unionPermissionPacks, type PermissionPackMap, } from './organization-material-permissions' import { packItemsToMap } from './user-permission-inherit' function isMissingGroupTable(error: unknown) { return error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2021' } const PACK_ITEM_SELECT = { materialType: true, canView: true, canDownload: true, canDownloadSource: true, canShare: true, canEditAssets: true, canAccessPrivate: true, } as const /** Shared by both admin views. Historical snapshots never grant access. */ export async function loadPermissionGroupDepartments() { return prisma.materialPermissionPackDepartment.findMany({ orderBy: { createdAt: 'asc' }, select: { id: true, packId: true, departmentPath: true, organizationUnitId: true, organizationUnit: { select: { id: true, path: true, name: true, isActive: true } }, }, }) } export async function loadDepartmentGroupPack(path: string): Promise { const packs = await loadDepartmentGroupPacks([path]) return packs.get(normalizeOrganizationPath(path)) ?? null } export async function loadDepartmentGroupPacks(paths: string[]): Promise> { const normalizedPaths = Array.from(new Set(paths.map(normalizeOrganizationPath).filter(Boolean))) const chains = new Map(normalizedPaths.map((path) => [path, organizationPathChain(path)])) const candidatePaths = Array.from(new Set(Array.from(chains.values()).flat())) const result = new Map() if (candidatePaths.length === 0) return result try { const memberships = await prisma.materialPermissionPackDepartment.findMany({ where: { organizationUnit: { isActive: true, path: { in: candidatePaths } } }, select: { organizationUnit: { select: { path: true, isActive: true } }, pack: { select: { items: { select: PACK_ITEM_SELECT }, }, }, }, }) const currentMemberships = memberships.flatMap((row) => row.organizationUnit?.isActive ? [{ ...row, departmentPath: row.organizationUnit.path }] : []) for (const path of normalizedPaths) { const nearest = findNearestGroupMemberships(path, currentMemberships) if (!nearest) continue result.set(path, unionPermissionPacks(...nearest.items.map((row) => packItemsToMap(row.pack.items)))) } return result } catch (error) { if (isMissingGroupTable(error)) return result throw error } } export async function findConflictingDepartmentGroup(organizationUnitId: string, packId: string) { const existing = await prisma.materialPermissionPackDepartment.findFirst({ where: { organizationUnitId, NOT: { packId } }, select: { pack: { select: { slug: true, name: true } } }, }) return existing?.pack ?? null } export function makePermissionGroupSlug(name: string): string { const ascii = name .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') const suffix = Date.now().toString(36) return ascii ? `${ascii}-${suffix}` : `group-${suffix}` }