343 lines
12 KiB
TypeScript
343 lines
12 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { Prisma } from '@prisma/client'
|
|
import { getServerSession } from 'next-auth'
|
|
import { authOptions } from '@/lib/auth'
|
|
import { prisma } from '@/lib/prisma'
|
|
import { getAllMaterialTypes, isSuperAdmin } from '@/lib/department-permission'
|
|
import { filterSelectableMaterialTypes } from '@/lib/material-type-options'
|
|
import { BINDING_STATUS } from '@/lib/employee-directory'
|
|
import { selectVisibleOrganizationPeople } from '@/lib/auto-personnel-rules'
|
|
import {
|
|
collectOccupiedOrganizationPaths,
|
|
lastOrganizationPathSegment,
|
|
normalizeOrganizationPath,
|
|
pruneOrganizationTree,
|
|
resolveEmployeeOrganizationPaths,
|
|
} from '@/lib/organization-material-permissions'
|
|
import {
|
|
findConflictingDepartmentGroup,
|
|
makePermissionGroupSlug,
|
|
seedDefaultGroupDepartments,
|
|
} from '@/lib/permission-groups'
|
|
import {
|
|
ensureDefaultPermissionPacks,
|
|
packItemsToMap,
|
|
savePermissionPackItems,
|
|
} from '@/lib/user-permission-inherit'
|
|
|
|
type OrgNode = {
|
|
id: string
|
|
name: string
|
|
path: string
|
|
level: number
|
|
parentId: string | null
|
|
children: OrgNode[]
|
|
}
|
|
|
|
async function requireSuperAdmin() {
|
|
const session = await getServerSession(authOptions)
|
|
const userId = (session?.user as { id?: string } | undefined)?.id
|
|
if (!session || !(await isSuperAdmin(userId))) return null
|
|
return session
|
|
}
|
|
|
|
async function requireDingTalkUser(userId: string) {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: { id: true, dingtalkUserId: true },
|
|
})
|
|
if (!user) return { error: '没有这个账号', status: 400 as const }
|
|
if (!user.dingtalkUserId) {
|
|
return { error: '这个账号没有钉钉绑定,不能进权限组', status: 400 as const }
|
|
}
|
|
return { user }
|
|
}
|
|
|
|
export async function GET() {
|
|
try {
|
|
if (!(await requireSuperAdmin())) {
|
|
return NextResponse.json({ error: '需要超级管理员权限' }, { status: 403 })
|
|
}
|
|
|
|
await ensureDefaultPermissionPacks()
|
|
await seedDefaultGroupDepartments()
|
|
|
|
const [packs, units, rosterProfiles, materialTypes, departmentRows] = await Promise.all([
|
|
prisma.materialPermissionPack.findMany({
|
|
orderBy: { name: 'asc' },
|
|
select: {
|
|
id: true,
|
|
slug: true,
|
|
name: true,
|
|
items: {
|
|
select: {
|
|
materialType: true,
|
|
canView: true,
|
|
canDownload: true,
|
|
canDownloadSource: true,
|
|
canShare: true,
|
|
},
|
|
},
|
|
inheritors: {
|
|
orderBy: { updatedAt: 'desc' },
|
|
select: {
|
|
userId: true,
|
|
user: {
|
|
select: {
|
|
username: true,
|
|
realName: true,
|
|
employeeBindings: {
|
|
where: { status: BINDING_STATUS.ACTIVE },
|
|
take: 1,
|
|
select: {
|
|
employeeProfile: {
|
|
select: { displayName: true, departmentPath: true, departmentName: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
prisma.organizationUnit.findMany({
|
|
where: { isActive: true },
|
|
select: { id: true, name: true, path: true, level: true, parentId: true, sourceDeptId: true },
|
|
orderBy: [{ level: 'asc' }, { name: 'asc' }],
|
|
}),
|
|
prisma.performanceSurveyEmployeeProfile.findMany({
|
|
select: {
|
|
departmentPath: true,
|
|
employmentState: true,
|
|
sourcePayload: true,
|
|
departmentMemberships: {
|
|
select: { sourceDeptId: true, departmentPath: true },
|
|
},
|
|
},
|
|
}),
|
|
getAllMaterialTypes().then(filterSelectableMaterialTypes),
|
|
prisma.materialPermissionPackDepartment
|
|
.findMany({
|
|
orderBy: { departmentPath: 'asc' },
|
|
select: { packId: true, departmentPath: true },
|
|
})
|
|
.catch((error) => {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2021') {
|
|
return [] as Array<{ packId: string; departmentPath: string }>
|
|
}
|
|
throw error
|
|
}),
|
|
])
|
|
|
|
const nodeMap = new Map<string, OrgNode>()
|
|
for (const unit of units) {
|
|
nodeMap.set(unit.id, {
|
|
id: unit.id,
|
|
name: unit.name,
|
|
path: unit.path,
|
|
level: unit.level,
|
|
parentId: unit.parentId,
|
|
children: [],
|
|
})
|
|
}
|
|
const roots: OrgNode[] = []
|
|
for (const node of Array.from(nodeMap.values())) {
|
|
if (node.parentId && nodeMap.has(node.parentId)) {
|
|
nodeMap.get(node.parentId)!.children.push(node)
|
|
} else {
|
|
roots.push(node)
|
|
}
|
|
}
|
|
const pathBySourceDeptId = new Map(units.map((unit) => [String(unit.sourceDeptId), unit.path]))
|
|
const occupiedPaths = collectOccupiedOrganizationPaths([
|
|
...selectVisibleOrganizationPeople(rosterProfiles).flatMap((profile) =>
|
|
resolveEmployeeOrganizationPaths(profile, pathBySourceDeptId),
|
|
),
|
|
...departmentRows.map((row) => row.departmentPath),
|
|
])
|
|
const tree = pruneOrganizationTree(roots, occupiedPaths)
|
|
const departmentsByPack = new Map<string, string[]>()
|
|
for (const row of departmentRows) {
|
|
const list = departmentsByPack.get(row.packId) || []
|
|
list.push(row.departmentPath)
|
|
departmentsByPack.set(row.packId, list)
|
|
}
|
|
|
|
return NextResponse.json({
|
|
materialTypes,
|
|
tree,
|
|
groups: packs.map((pack) => ({
|
|
slug: pack.slug,
|
|
name: pack.name,
|
|
permissions: packItemsToMap(pack.items),
|
|
departments: (departmentsByPack.get(pack.id) || []).map((path) => ({
|
|
path,
|
|
name: lastOrganizationPathSegment(path) || path,
|
|
})),
|
|
members: pack.inheritors.map((row) => {
|
|
const profile = row.user.employeeBindings[0]?.employeeProfile
|
|
return {
|
|
userId: row.userId,
|
|
username: row.user.username,
|
|
displayName: profile?.displayName || row.user.realName || row.user.username,
|
|
departmentPath: profile?.departmentPath || null,
|
|
departmentName: profile?.departmentName || null,
|
|
}
|
|
}),
|
|
})),
|
|
})
|
|
} catch (error) {
|
|
console.error('[permission-groups] GET failed', error)
|
|
return NextResponse.json({ error: '获取权限组失败' }, { status: 500 })
|
|
}
|
|
}
|
|
|
|
export async function PUT(request: NextRequest) {
|
|
try {
|
|
if (!(await requireSuperAdmin())) {
|
|
return NextResponse.json({ error: '需要超级管理员权限' }, { status: 403 })
|
|
}
|
|
|
|
await ensureDefaultPermissionPacks()
|
|
const body = await request.json()
|
|
const action = String(body?.action || '').trim()
|
|
|
|
if (action === 'create') {
|
|
const name = String(body?.name || '').trim()
|
|
if (!name) return NextResponse.json({ error: '请填写组名' }, { status: 400 })
|
|
const slug = makePermissionGroupSlug(name)
|
|
await prisma.materialPermissionPack.create({
|
|
data: { slug, name },
|
|
})
|
|
return NextResponse.json({ ok: true, slug })
|
|
}
|
|
|
|
const slug = String(body?.slug || '').trim()
|
|
if (!slug) return NextResponse.json({ error: '缺少权限组' }, { status: 400 })
|
|
|
|
const pack = await prisma.materialPermissionPack.findUnique({
|
|
where: { slug },
|
|
select: { id: true, slug: true, name: true },
|
|
})
|
|
if (!pack) return NextResponse.json({ error: '没有这个权限组' }, { status: 400 })
|
|
|
|
if (action === 'rename') {
|
|
const name = String(body?.name || '').trim()
|
|
if (!name) return NextResponse.json({ error: '请填写组名' }, { status: 400 })
|
|
await prisma.materialPermissionPack.update({
|
|
where: { id: pack.id },
|
|
data: { name },
|
|
})
|
|
return NextResponse.json({ ok: true })
|
|
}
|
|
|
|
if (action === 'delete') {
|
|
await prisma.$transaction([
|
|
prisma.userPermissionInherit.deleteMany({ where: { packId: pack.id } }),
|
|
prisma.materialPermissionPack.delete({ where: { id: pack.id } }),
|
|
])
|
|
return NextResponse.json({ ok: true })
|
|
}
|
|
|
|
if (action === 'save-pack') {
|
|
const incoming: Array<{
|
|
materialType: string
|
|
canView?: boolean
|
|
canDownload?: boolean
|
|
canDownloadSource?: boolean
|
|
canShare?: boolean
|
|
}> = Array.isArray(body?.permissions) ? body.permissions : []
|
|
if (incoming.length === 0) {
|
|
return NextResponse.json({ error: '素材类型列表不能为空' }, { status: 400 })
|
|
}
|
|
await savePermissionPackItems(
|
|
slug,
|
|
incoming.map((item) => ({
|
|
materialType: item.materialType,
|
|
canView: Boolean(item.canView),
|
|
canDownload: Boolean(item.canDownload),
|
|
canDownloadSource: Boolean(item.canDownloadSource),
|
|
canShare: item.canShare !== false,
|
|
})),
|
|
)
|
|
return NextResponse.json({ ok: true })
|
|
}
|
|
|
|
if (action === 'add-department' || action === 'remove-department') {
|
|
const path = normalizeOrganizationPath(body?.path)
|
|
if (!path) return NextResponse.json({ error: '请选择部门' }, { status: 400 })
|
|
const unit = await prisma.organizationUnit.findFirst({
|
|
where: { isActive: true, path },
|
|
select: { path: true },
|
|
})
|
|
if (!unit) return NextResponse.json({ error: '花名册里没有这个部门' }, { status: 400 })
|
|
|
|
if (action === 'remove-department') {
|
|
await prisma.materialPermissionPackDepartment.deleteMany({
|
|
where: { packId: pack.id, departmentPath: path },
|
|
})
|
|
return NextResponse.json({ ok: true })
|
|
}
|
|
|
|
const conflict = await findConflictingDepartmentGroup(path, pack.id)
|
|
if (conflict) {
|
|
const name = lastOrganizationPathSegment(path) || path
|
|
return NextResponse.json(
|
|
{ error: `「${name}」已经在权限组「${conflict.name}」里,一个部门只能进一组。先从那组拿掉,再加到这里。` },
|
|
{ status: 409 },
|
|
)
|
|
}
|
|
|
|
try {
|
|
await prisma.materialPermissionPackDepartment.upsert({
|
|
where: {
|
|
packId_departmentPath: { packId: pack.id, departmentPath: path },
|
|
},
|
|
update: {},
|
|
create: { packId: pack.id, departmentPath: path },
|
|
})
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
|
|
const name = lastOrganizationPathSegment(path) || path
|
|
return NextResponse.json(
|
|
{ error: `「${name}」已经在别的权限组里,一个部门只能进一组。` },
|
|
{ status: 409 },
|
|
)
|
|
}
|
|
throw error
|
|
}
|
|
return NextResponse.json({ ok: true })
|
|
}
|
|
|
|
if (action === 'add-member' || action === 'remove-member') {
|
|
const userId = String(body?.userId || '').trim()
|
|
if (!userId) return NextResponse.json({ error: '请选择人员' }, { status: 400 })
|
|
|
|
if (action === 'remove-member') {
|
|
await prisma.userPermissionInherit.deleteMany({
|
|
where: { userId, packId: pack.id },
|
|
})
|
|
return NextResponse.json({ ok: true })
|
|
}
|
|
|
|
const checked = await requireDingTalkUser(userId)
|
|
if ('error' in checked) {
|
|
return NextResponse.json({ error: checked.error }, { status: checked.status })
|
|
}
|
|
|
|
await prisma.userPermissionInherit.upsert({
|
|
where: { userId_packId: { userId, packId: pack.id } },
|
|
update: {},
|
|
create: { userId, packId: pack.id },
|
|
})
|
|
return NextResponse.json({ ok: true })
|
|
}
|
|
|
|
return NextResponse.json({ error: '不认识这个操作' }, { status: 400 })
|
|
} catch (error) {
|
|
console.error('[permission-groups] PUT failed', error)
|
|
return NextResponse.json({ error: '保存权限组失败' }, { status: 500 })
|
|
}
|
|
}
|