feat: 预入职状态+入职手续待办
1. 花名册动态状态:hireDate > today 显示「预入职」(蓝色标签) 2. RiskType 增加 ONBOARDING 枚举 3. 新增 detectOnboardingRisks:入职日期到但未签合同→生成待办 4. detectContractRisks 排除预入职员工(未到入职日期不检查合同) 5. Dashboard 风险提醒 tab 支持 ONBOARDING 待办类型 6. 前端类型定义同步更新
This commit is contained in:
@@ -42,6 +42,7 @@ enum RiskType {
|
||||
SALARY
|
||||
TERMINATION
|
||||
MONTHLY
|
||||
ONBOARDING
|
||||
}
|
||||
|
||||
enum RiskLevel {
|
||||
|
||||
@@ -58,11 +58,13 @@ router.get('/', authMiddleware, async (req: AuthRequest, res, next) => {
|
||||
contractType: 'UNSIGNED',
|
||||
hireDate: e.hireDate,
|
||||
})
|
||||
const isResigned = e.terminations.some((t) => t.terminationDate <= today)
|
||||
const isPreHire = !isResigned && e.hireDate > today
|
||||
return {
|
||||
id: e.id,
|
||||
name: e.name,
|
||||
department: e.department,
|
||||
status: e.terminations.some((t) => t.terminationDate <= today) ? 'RESIGNED' : 'ACTIVE',
|
||||
status: isResigned ? 'RESIGNED' : (isPreHire ? 'PRE_HIRE' : 'ACTIVE'),
|
||||
hasTermination: e.terminations.length > 0,
|
||||
latestTerminationDate: e.terminations[0]?.terminationDate || null,
|
||||
latestTerminationType: e.terminations[0]?.type || null,
|
||||
|
||||
@@ -7,8 +7,10 @@ function daysBetween(a: Date, b: Date): number {
|
||||
}
|
||||
|
||||
export async function detectContractRisks(orgId: string) {
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const employees = await prisma.employee.findMany({
|
||||
where: { orgId, status: 'ACTIVE' },
|
||||
where: { orgId, status: 'ACTIVE', hireDate: { lte: today } },
|
||||
include: { contracts: { orderBy: { createdAt: 'desc' } } },
|
||||
})
|
||||
|
||||
@@ -98,6 +100,43 @@ export async function detectContractRisks(orgId: string) {
|
||||
return risks
|
||||
}
|
||||
|
||||
// 预入职检查:入职日期已到但未签合同 → 待办
|
||||
export async function detectOnboardingRisks(orgId: string) {
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const employees = await prisma.employee.findMany({
|
||||
where: { orgId, status: 'ACTIVE', hireDate: { lte: today } },
|
||||
include: {
|
||||
contracts: { orderBy: { createdAt: 'desc' }, take: 1 },
|
||||
terminations: { where: { terminationDate: { lte: today } }, take: 1 },
|
||||
},
|
||||
})
|
||||
|
||||
const risks: { employeeId: string; type: RiskType; level: RiskLevel; title: string; description: string; actionUrl: string }[] = []
|
||||
|
||||
for (const emp of employees) {
|
||||
// 已离职的跳过
|
||||
if (emp.terminations.length > 0) continue
|
||||
|
||||
const latestContract = emp.contracts[0]
|
||||
const hasSignedContract = latestContract && latestContract.contractType !== 'UNSIGNED'
|
||||
|
||||
if (!hasSignedContract) {
|
||||
const daysSinceHire = daysBetween(today, emp.hireDate)
|
||||
risks.push({
|
||||
employeeId: emp.id,
|
||||
type: 'ONBOARDING',
|
||||
level: daysSinceHire > 30 ? 'HIGH' : 'MEDIUM',
|
||||
title: `${emp.name}入职手续未完成${daysSinceHire > 30 ? `(已超${daysSinceHire}天)` : ''}`,
|
||||
description: `入职日期 ${emp.hireDate.toISOString().slice(0, 10)},尚未签订劳动合同,请尽快完成入职手续。`,
|
||||
actionUrl: `/roster?employee=${encodeURIComponent(emp.name)}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return risks
|
||||
}
|
||||
|
||||
export async function detectTerminationRisks(orgId: string) {
|
||||
const employees = await prisma.employee.findMany({
|
||||
where: { orgId, status: 'ACTIVE' },
|
||||
@@ -206,10 +245,11 @@ export async function runRiskDetection(orgId: string) {
|
||||
|
||||
const contractRisks = await detectContractRisks(orgId)
|
||||
const terminationRisks = await detectTerminationRisks(orgId)
|
||||
const onboardingRisks = await detectOnboardingRisks(orgId)
|
||||
const monthlyTasks = await detectMonthlyTasks(orgId)
|
||||
|
||||
// 月度任务用 monthlyKeys 去重,其他任务用 existingKeys 去重
|
||||
const nonMonthlyRisks = [...contractRisks, ...terminationRisks]
|
||||
const nonMonthlyRisks = [...contractRisks, ...terminationRisks, ...onboardingRisks]
|
||||
const toCreate = [
|
||||
...nonMonthlyRisks.filter((r) => !existingKeys.has(`${r.employeeId}:${r.title}`)),
|
||||
...monthlyTasks.filter((r) => !monthlyKeys.has(`${r.employeeId}:${r.title}`)),
|
||||
|
||||
Reference in New Issue
Block a user