Files
TurboHR/backend/src/services/termination.service.ts
T
2026-07-23 12:34:43 +08:00

164 lines
5.2 KiB
TypeScript

import prisma from '../lib/prisma'
import { decrypt } from '../lib/crypto'
import { RiskAssessment } from '@prisma/client'
function daysBetween(a: Date, b: Date): number {
return Math.floor((a.getTime() - b.getTime()) / (1000 * 60 * 60 * 24))
}
export function getChecklistForReason(reason: string): { key: string; label: string }[] {
switch (reason) {
case 'NEGOTIATED':
return [
{ key: 'compensation_paid', label: '是否已支付经济补偿金' },
{ key: 'agreement_signed', label: '是否签署协商解除协议' },
{ key: 'final_pay_ready', label: '是否结清最后工资' },
]
case 'FAULT':
return [
{ key: 'has_rules', label: '是否有规章制度依据' },
{ key: 'has_evidence', label: '是否有违纪证据' },
{ key: 'notify_union', label: '是否事先通知工会' },
{ key: 'written_notice', label: '是否出具书面解除通知' },
]
case 'NONFAULT':
return [
{ key: 'medical_period_end', label: '医疗期是否已届满' },
{ key: 'training_given', label: '是否经过培训或调岗' },
{ key: 'compensation_paid', label: '是否支付经济补偿金' },
{ key: 'advance_notice', label: '是否提前30天通知或支付代通知金' },
]
case 'LAYOFF':
return [
{ key: 'advance_notice_30', label: '是否提前30天向工会或全体职工说明' },
{ key: 'listen_opinions', label: '是否听取工会或职工意见' },
{ key: 'report_labor_dept', label: '是否向劳动行政部门报告' },
{ key: 'compensation_paid', label: '是否支付经济补偿金' },
]
case 'EXPIRED':
return [
{ key: 'compensation_paid', label: '是否支付经济补偿金(如需)' },
{ key: 'written_notice', label: '是否提前通知员工不续签' },
]
default:
return []
}
}
export function assessRisk(employee: any, reason: string): { level: RiskAssessment; warnings: string[] } {
const warnings: string[] = []
if (employee.isPregnant) {
warnings.push('该员工在孕期/哺乳期,法律禁止以非过错理由解除')
}
if (employee.isWorkInjured) {
warnings.push('工伤期间不得解除劳动合同')
}
if (employee.isInMedicalPeriod && reason !== 'FAULT') {
warnings.push('医疗期内不得解除劳动合同(非过错理由)')
}
let level: RiskAssessment = 'SAFE'
if (warnings.length > 0) {
level = 'DANGER'
}
return { level, warnings }
}
export async function createTermination(orgId: string, userId: string, data: any) {
const employee = await prisma.employee.findFirst({ where: { id: data.employeeId, orgId } })
if (!employee) {
throw { code: 'NOT_FOUND', message: '员工不存在' }
}
const { level } = assessRisk(employee, data.reason)
const record = await prisma.terminationRecord.create({
data: {
orgId,
employeeId: data.employeeId,
reason: data.reason,
terminationDate: new Date(data.terminationDate),
compensation: data.compensation || 0,
riskLevel: level,
checklist: data.checklist || {},
remark: data.remark,
createdBy: userId,
},
})
await prisma.employee.update({
where: { id: data.employeeId },
data: { status: 'RESIGNED' },
})
await prisma.riskItem.updateMany({
where: { employeeId: data.employeeId, status: 'PENDING' },
data: { status: 'RESOLVED', resolvedAt: new Date() },
})
return { id: record.id }
}
export async function getTerminations(orgId: string, page: number, pageSize: number) {
const skip = (page - 1) * pageSize
const [total, records] = await Promise.all([
prisma.terminationRecord.count({ where: { orgId } }),
prisma.terminationRecord.findMany({
where: { orgId },
include: { employee: true },
orderBy: { createdAt: 'desc' },
skip,
take: pageSize,
}),
])
return {
items: records.map((r) => ({
id: r.id,
employeeName: r.employee.name,
department: r.employee.department,
reason: r.reason,
terminationDate: r.terminationDate.toISOString().slice(0, 10),
compensation: r.compensation,
riskLevel: r.riskLevel,
createdAt: r.createdAt.toISOString().slice(0, 10),
})),
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
}
}
export function calculateCompensation(hireDate: Date, leaveDate: Date, monthlyWage: number, socialAvgWage: number = 0): {
years: number
remainingMonths: number
compMonths: number
totalPay: number
capped: boolean
} {
const totalMonths = (leaveDate.getFullYear() - hireDate.getFullYear()) * 12 + (leaveDate.getMonth() - hireDate.getMonth())
const years = Math.floor(totalMonths / 12)
const remainingMonths = totalMonths % 12
let compMonths: number
if (remainingMonths >= 6) compMonths = years + 1
else if (remainingMonths > 0) compMonths = years + 0.5
else compMonths = years
if (compMonths <= 0) compMonths = 0.5
let wage = monthlyWage
let capped = false
if (socialAvgWage > 0 && monthlyWage > socialAvgWage * 3) {
wage = socialAvgWage * 3
compMonths = Math.min(compMonths, 12)
capped = true
}
return { years, remainingMonths, compMonths, totalPay: wage * compMonths, capped }
}