135 lines
3.3 KiB
TypeScript
135 lines
3.3 KiB
TypeScript
import type { Prisma } from '@prisma/client'
|
|
import { prisma } from '@/lib/prisma'
|
|
import { buildDepartmentDescendants, isDepartmentWithinScope } from '@/lib/departments'
|
|
|
|
export type MaterialPermissionActor = {
|
|
id: string
|
|
role: string
|
|
isSuperAdmin: boolean
|
|
department?: string | null
|
|
managedDepartment?: string | null
|
|
}
|
|
|
|
export type MaterialPermissionTarget = {
|
|
userId?: string | null
|
|
createdByUserId?: string | null
|
|
ownerType?: string | null
|
|
ownerId?: string | null
|
|
user?: {
|
|
id?: string | null
|
|
department?: string | null
|
|
} | null
|
|
}
|
|
|
|
const normalizeDepartment = (department?: string | null) => (department || '').trim()
|
|
|
|
export async function getMaterialPermissionActor(session: unknown): Promise<MaterialPermissionActor | null> {
|
|
const userId = (session as any)?.user?.id
|
|
if (!userId) {
|
|
return null
|
|
}
|
|
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: {
|
|
id: true,
|
|
role: true,
|
|
isSuperAdmin: true,
|
|
department: true,
|
|
managedDepartment: true,
|
|
},
|
|
})
|
|
|
|
if (!user) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
id: user.id,
|
|
role: user.role,
|
|
isSuperAdmin: Boolean(user.isSuperAdmin),
|
|
department: user.department,
|
|
managedDepartment: user.managedDepartment,
|
|
}
|
|
}
|
|
|
|
export function isMaterialOwnedByActor(material: MaterialPermissionTarget, actor: MaterialPermissionActor | null) {
|
|
if (!actor) {
|
|
return false
|
|
}
|
|
|
|
return (
|
|
material.userId === actor.id ||
|
|
(material.ownerType === 'USER' && material.ownerId === actor.id)
|
|
)
|
|
}
|
|
|
|
export async function canManageMaterial(
|
|
material: MaterialPermissionTarget,
|
|
actor: MaterialPermissionActor | null
|
|
) {
|
|
if (!actor) {
|
|
return false
|
|
}
|
|
|
|
if (actor.isSuperAdmin) {
|
|
return true
|
|
}
|
|
|
|
if (isMaterialOwnedByActor(material, actor)) {
|
|
return true
|
|
}
|
|
|
|
if (actor.role !== 'ADMIN') {
|
|
return false
|
|
}
|
|
|
|
const scopeDepartment = normalizeDepartment(actor.managedDepartment)
|
|
const targetDepartment = normalizeDepartment(material.user?.department)
|
|
if (!scopeDepartment || !targetDepartment) {
|
|
return false
|
|
}
|
|
|
|
if (targetDepartment === scopeDepartment || targetDepartment.startsWith(`${scopeDepartment}-`)) {
|
|
return true
|
|
}
|
|
|
|
const departments = await prisma.department.findMany({ select: { name: true } })
|
|
const descendantMap = buildDepartmentDescendants(departments)
|
|
return isDepartmentWithinScope(scopeDepartment, targetDepartment, descendantMap)
|
|
}
|
|
|
|
export async function buildMaterialManagementScopeWhere(
|
|
actor: MaterialPermissionActor | null
|
|
): Promise<Prisma.MaterialWhereInput> {
|
|
if (!actor) {
|
|
return { id: { in: [] } }
|
|
}
|
|
|
|
if (actor.isSuperAdmin) {
|
|
return {}
|
|
}
|
|
|
|
const scopeDepartment = normalizeDepartment(actor.managedDepartment)
|
|
const ownWhere: Prisma.MaterialWhereInput[] = [
|
|
{ userId: actor.id },
|
|
{ ownerType: 'USER', ownerId: actor.id },
|
|
]
|
|
|
|
if (actor.role !== 'ADMIN' || !scopeDepartment) {
|
|
return { OR: ownWhere }
|
|
}
|
|
|
|
const departments = await prisma.department.findMany({ select: { name: true } })
|
|
const descendants = buildDepartmentDescendants(departments).get(scopeDepartment)
|
|
const scopedDepartments = Array.from(descendants || new Set([scopeDepartment]))
|
|
|
|
return {
|
|
OR: [
|
|
...ownWhere,
|
|
{ user: { department: { in: scopedDepartments } } },
|
|
{ user: { department: { startsWith: `${scopeDepartment}-` } } },
|
|
],
|
|
}
|
|
}
|