import { Prisma } from '@prisma/client' import { prisma } from './prisma' import { findNearestGroupMemberships, lastOrganizationPathSegment, LEGACY_DEPARTMENT_ALIASES, normalizeOrganizationPath, isUnderOrganizationPath, 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, } as const function packTargetDepartment(slug: string): string | null { if (slug === 'operations') return '运营中心' if (slug === 'visual') return '视觉中心' return null } function unitMatchesPack(unitName: string, target: string): boolean { return unitName === target || LEGACY_DEPARTMENT_ALIASES[unitName] === target } function pickCanonicalGroupDepartments(paths: string[]): string[] { const unique = Array.from(new Set(paths.map(normalizeOrganizationPath).filter(Boolean))) return unique.filter( (path) => !unique.some((other) => other !== path && isUnderOrganizationPath(path, other)), ) } export async function seedDefaultGroupDepartments() { try { const packs = await prisma.materialPermissionPack.findMany({ where: { slug: { in: ['operations', 'visual'] } }, select: { id: true, slug: true, _count: { select: { departments: true } }, }, }) const needsSeed = packs.filter((pack) => pack._count.departments === 0) if (needsSeed.length === 0) return const [units, occupiedRows] = await Promise.all([ prisma.organizationUnit.findMany({ where: { isActive: true }, select: { path: true, name: true }, }), prisma.materialPermissionPackDepartment.findMany({ select: { departmentPath: true }, }), ]) const occupied = new Set(occupiedRows.map((row) => row.departmentPath)) for (const pack of needsSeed) { const target = packTargetDepartment(pack.slug) if (!target) continue const matched = units .filter((unit) => unitMatchesPack(lastOrganizationPathSegment(unit.path) || unit.name, target)) .map((unit) => unit.path) const canonical = pickCanonicalGroupDepartments(matched).filter((path) => !occupied.has(path)) if (canonical.length === 0) continue await prisma.materialPermissionPackDepartment.createMany({ data: canonical.map((departmentPath) => ({ packId: pack.id, departmentPath, })), skipDuplicates: true, }) for (const path of canonical) occupied.add(path) } } catch (error) { if (isMissingGroupTable(error)) return throw error } } 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: { departmentPath: { in: candidatePaths } }, select: { departmentPath: true, pack: { select: { items: { select: PACK_ITEM_SELECT }, }, }, }, }) for (const path of normalizedPaths) { const nearest = findNearestGroupMemberships(path, memberships) 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(path: string, packId: string) { const existing = await prisma.materialPermissionPackDepartment.findFirst({ where: { departmentPath: path, 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}` }