/** * 自动人员规则(决策:以花名册为唯一权威,减少人工操作) * * 规则: * 1. 自动激活:账号有活跃绑定(UserEmployeeBinding ACTIVE)且花名册在职 * (employmentState 非 RESIGNED)时,账号应自动激活,无需管理员手动激活。 * 2. 自动角色:花名册 M 级(levelType='M')或负责人(isLeadership)→ 管理层角色; * P 级 → 普通用户。角色由花名册数据派生,不靠人工设置。 * 3. 离职自动归档:已在 organization-sync.applyResignationEffects 处理(离职即处理,无天数缓冲)。 * * 供登录链路(凭证登录 / 钉钉登录)与同步链路复用。 */ import { prisma } from '@/lib/prisma' import { Role } from '@prisma/client' export const AUTO_ROLE: Record<'DEPT_ADMIN' | 'USER', Role> = { /** M 级/负责人 → 部门管理员 */ DEPT_ADMIN: 'DEPT_ADMIN', /** 其余 → 普通用户 */ USER: 'USER', } as const /** * 查询账号的权威人员信息(通过活跃绑定)。 * 返回 null 表示无绑定(无法自动判定,需人工处理)。 */ export async function getAuthoritativePersonnelForUser(userId: string): Promise<{ employmentState: string | null levelType: string | null positionLevel: string | null isLeadership: boolean } | null> { const binding = await prisma.userEmployeeBinding.findFirst({ where: { userId, status: 'ACTIVE' }, select: { employeeProfile: { select: { employmentState: true, levelType: true, positionLevel: true, isLeadership: true, }, }, }, }) if (!binding?.employeeProfile) return null return { employmentState: binding.employeeProfile.employmentState, levelType: binding.employeeProfile.levelType, positionLevel: binding.employeeProfile.positionLevel, isLeadership: binding.employeeProfile.isLeadership, } } /** * 花名册是否按在职计:UNKNOWN/空视为在职,宁可多留;只有明确 RESIGNED 才算离职。 */ export function isRosterEmployed(employmentState: string | null | undefined): boolean { return employmentState !== 'RESIGNED' } /** * 组织架构默认名单:只看花名册在职。 * includeResigned 打开后才把离职的人加回来,供核对托管/交接。 */ export function selectVisibleOrganizationPeople( people: T[], includeResigned = false, ): T[] { if (includeResigned) return people return people.filter((person) => isRosterEmployed(person.employmentState)) } /** * 判断账号是否应自动激活: * 有活跃绑定 + 花名册在职(employmentState 非 RESIGNED;UNKNOWN/空视为在职,宁可多留)。 */ export function shouldAutoActivate(personnel: { employmentState: string | null } | null): boolean { if (!personnel) return false return isRosterEmployed(personnel.employmentState) } /** * 从花名册数据派生角色: * M 级(levelType='M')或负责人(isLeadership)→ DEPT_ADMIN;否则 USER。 */ export function deriveRoleFromPersonnel(personnel: { levelType: string | null isLeadership: boolean }): Role { if (personnel.levelType === 'M' || personnel.isLeadership) { return AUTO_ROLE.DEPT_ADMIN } return AUTO_ROLE.USER } /** * 登录时调用:若账号在花名册有绑定且在职,则自动激活账号。 * 返回 true 表示执行了自动激活;false 表示无需处理(无绑定或已激活)。 */ export async function autoActivateUserIfEligible(userId: string): Promise { const personnel = await getAuthoritativePersonnelForUser(userId) if (!shouldAutoActivate(personnel)) return false const current = await prisma.user.findUnique({ where: { id: userId }, select: { status: true, isActive: true }, }) if (!current) return false const currentStatus = current.status || (current.isActive ? 'active' : 'pending') if (currentStatus === 'active') return false await prisma.user.update({ where: { id: userId }, data: { status: 'active', isActive: true, statusChangedAt: new Date(), statusChangedBy: 'auto-花名册在册', }, }) return true } /** * 登录时调用:若账号在花名册有绑定,按花名册 M/P 等级自动同步角色。 * 仅当当前角色与派生角色不一致且当前角色不是 SYSTEM_ADMIN/ADMIN(不降级超管)时更新。 */ export async function autoSyncRoleFromPersonnel(userId: string): Promise { const personnel = await getAuthoritativePersonnelForUser(userId) if (!personnel) return null const derived = deriveRoleFromPersonnel(personnel) const current = await prisma.user.findUnique({ where: { id: userId }, select: { role: true }, }) if (!current) return null // 不自动降级系统管理员/管理员(人工授予的高权限保持不变) if (current.role === 'SYSTEM_ADMIN' || current.role === 'ADMIN') return null if (current.role === derived) return null await prisma.user.update({ where: { id: userId }, data: { role: derived }, }) return derived }