gongxiangp2/lib/auto-personnel-rules.ts

153 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 自动人员规则(决策:以花名册为唯一权威,减少人工操作)
*
* 规则:
* 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<T extends { employmentState: string | null }>(
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<boolean> {
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<string | null> {
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
}