153 lines
5.0 KiB
TypeScript
153 lines
5.0 KiB
TypeScript
/**
|
||
* 自动人员规则(决策:以花名册为唯一权威,减少人工操作)
|
||
*
|
||
* 规则:
|
||
* 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
|
||
}
|