fix: 全面优化安全、性能和代码质量
P0: 环境变量校验/事务保护/RBAC权限 P1: 花名册分页/密码重置验证码/ErrorBoundary/N+1查询优化 P2: 导出限制/自定义错误类/body大小限制/代码去重 P3: 自定义确认弹窗替换window.confirm
This commit is contained in:
@@ -190,112 +190,112 @@ export async function createEmployee(orgId: string, userId: string, data: any) {
|
||||
const socialInsStartMonth = data.socialInsStartMonth || hireMonth
|
||||
const housingFundStartMonth = data.housingFundStartMonth || hireMonth
|
||||
|
||||
const employee = await prisma.employee.create({
|
||||
data: {
|
||||
orgId,
|
||||
name: data.name,
|
||||
department: data.department,
|
||||
hireDate,
|
||||
monthlySalary: encrypt(data.monthlySalary),
|
||||
gender: data.gender,
|
||||
phone: data.phone,
|
||||
idCardNumber: data.idCardNumber ? encrypt(data.idCardNumber) : null,
|
||||
idCardHash: data.idCardNumber ? sha256(data.idCardNumber) : null,
|
||||
isPregnant: data.isPregnant || false,
|
||||
isInMedicalPeriod: data.isInMedicalPeriod || false,
|
||||
isWorkInjured: data.isWorkInjured || false,
|
||||
socialInsBase,
|
||||
housingFundBase,
|
||||
socialInsStartMonth,
|
||||
housingFundStartMonth,
|
||||
createdBy: userId,
|
||||
city: data.city || '北京',
|
||||
},
|
||||
})
|
||||
|
||||
// 创建社保缴费记录
|
||||
await prisma.employeeSocialInsRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: employee.id,
|
||||
startMonth: socialInsStartMonth,
|
||||
endMonth: null,
|
||||
base: socialInsBase,
|
||||
changeType: 'ONBOARDING',
|
||||
createdBy: userId,
|
||||
city: data.city || '北京',
|
||||
},
|
||||
})
|
||||
|
||||
// 创建公积金缴费记录
|
||||
await prisma.employeeHousingFundRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: employee.id,
|
||||
startMonth: housingFundStartMonth,
|
||||
endMonth: null,
|
||||
base: housingFundBase,
|
||||
changeType: 'ONBOARDING',
|
||||
createdBy: userId,
|
||||
city: data.city || '北京',
|
||||
},
|
||||
})
|
||||
|
||||
// 创建初始薪资变更记录
|
||||
await prisma.salaryChangeRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: employee.id,
|
||||
oldSalary: 0,
|
||||
newSalary: salaryNum,
|
||||
effectiveDate: hireDate,
|
||||
effectiveMonth: hireMonth,
|
||||
endMonth: null,
|
||||
changeType: 'ONBOARDING',
|
||||
createdBy: userId,
|
||||
},
|
||||
})
|
||||
|
||||
// 创建初始部门记录
|
||||
await prisma.employeeDepartmentRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: employee.id,
|
||||
oldDepartment: '',
|
||||
newDepartment: data.department,
|
||||
effectiveMonth: hireMonth,
|
||||
endMonth: null,
|
||||
changeType: 'ONBOARDING',
|
||||
createdBy: userId,
|
||||
},
|
||||
})
|
||||
|
||||
if (data.contract && data.contract.contractType !== 'UNSIGNED') {
|
||||
const contractMonths = data.contract.endDate
|
||||
? Math.ceil(daysBetween(new Date(data.contract.endDate), new Date(data.contract.startDate)) / 30.44)
|
||||
: data.contract.contractYears * 12
|
||||
|
||||
const probationCheck = validateProbation(contractMonths, data.contract.probationMonths)
|
||||
if (!probationCheck.valid) {
|
||||
throw { code: 'VALIDATION_ERROR', message: probationCheck.message }
|
||||
}
|
||||
|
||||
await prisma.laborContract.create({
|
||||
const employee = await prisma.$transaction(async (tx) => {
|
||||
const emp = await tx.employee.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: employee.id,
|
||||
signDate: data.contract.signDate ? new Date(data.contract.signDate) : null,
|
||||
startDate: new Date(data.contract.startDate),
|
||||
endDate: data.contract.endDate ? new Date(data.contract.endDate) : null,
|
||||
contractType: data.contract.contractType,
|
||||
signMethod: data.contract.signMethod || 'PAPER',
|
||||
contractYears: data.contract.contractYears || 3,
|
||||
probationMonths: data.contract.probationMonths || 0,
|
||||
probationSalary: data.contract.probationSalary || 0,
|
||||
name: data.name,
|
||||
department: data.department,
|
||||
hireDate,
|
||||
monthlySalary: encrypt(data.monthlySalary),
|
||||
gender: data.gender,
|
||||
phone: data.phone,
|
||||
idCardNumber: data.idCardNumber ? encrypt(data.idCardNumber) : null,
|
||||
idCardHash: data.idCardNumber ? sha256(data.idCardNumber) : null,
|
||||
isPregnant: data.isPregnant || false,
|
||||
isInMedicalPeriod: data.isInMedicalPeriod || false,
|
||||
isWorkInjured: data.isWorkInjured || false,
|
||||
socialInsBase,
|
||||
housingFundBase,
|
||||
socialInsStartMonth,
|
||||
housingFundStartMonth,
|
||||
createdBy: userId,
|
||||
city: data.city || '北京',
|
||||
},
|
||||
})
|
||||
|
||||
await tx.employeeSocialInsRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: emp.id,
|
||||
startMonth: socialInsStartMonth,
|
||||
endMonth: null,
|
||||
base: socialInsBase,
|
||||
changeType: 'ONBOARDING',
|
||||
createdBy: userId,
|
||||
city: data.city || '北京',
|
||||
},
|
||||
})
|
||||
|
||||
await tx.employeeHousingFundRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: emp.id,
|
||||
startMonth: housingFundStartMonth,
|
||||
endMonth: null,
|
||||
base: housingFundBase,
|
||||
changeType: 'ONBOARDING',
|
||||
createdBy: userId,
|
||||
city: data.city || '北京',
|
||||
},
|
||||
})
|
||||
|
||||
await tx.salaryChangeRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: emp.id,
|
||||
oldSalary: 0,
|
||||
newSalary: salaryNum,
|
||||
effectiveDate: hireDate,
|
||||
effectiveMonth: hireMonth,
|
||||
endMonth: null,
|
||||
changeType: 'ONBOARDING',
|
||||
createdBy: userId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
await tx.employeeDepartmentRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: emp.id,
|
||||
oldDepartment: '',
|
||||
newDepartment: data.department,
|
||||
effectiveMonth: hireMonth,
|
||||
endMonth: null,
|
||||
changeType: 'ONBOARDING',
|
||||
createdBy: userId,
|
||||
},
|
||||
})
|
||||
|
||||
if (data.contract && data.contract.contractType !== 'UNSIGNED') {
|
||||
const contractMonths = data.contract.endDate
|
||||
? Math.ceil(daysBetween(new Date(data.contract.endDate), new Date(data.contract.startDate)) / 30.44)
|
||||
: data.contract.contractYears * 12
|
||||
|
||||
const probationCheck = validateProbation(contractMonths, data.contract.probationMonths)
|
||||
if (!probationCheck.valid) {
|
||||
throw { code: 'VALIDATION_ERROR', message: probationCheck.message }
|
||||
}
|
||||
|
||||
await tx.laborContract.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: emp.id,
|
||||
signDate: data.contract.signDate ? new Date(data.contract.signDate) : null,
|
||||
startDate: new Date(data.contract.startDate),
|
||||
endDate: data.contract.endDate ? new Date(data.contract.endDate) : null,
|
||||
contractType: data.contract.contractType,
|
||||
signMethod: data.contract.signMethod || 'PAPER',
|
||||
contractYears: data.contract.contractYears || 3,
|
||||
probationMonths: data.contract.probationMonths || 0,
|
||||
probationSalary: data.contract.probationSalary || 0,
|
||||
createdBy: userId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return emp
|
||||
})
|
||||
|
||||
await runRiskDetection(orgId)
|
||||
|
||||
|
||||
@@ -280,18 +280,29 @@ export async function generatePayslipFromBatches(orgId: string, month: string) {
|
||||
|
||||
// 计算累计数据
|
||||
const year = month.slice(0, 4)
|
||||
const employeeIds = Array.from(employeeMap.keys())
|
||||
|
||||
const allPrevPayslips = await prisma.payslip.findMany({
|
||||
where: { orgId, employeeId: { in: employeeIds }, month: { startsWith: year, lt: month } },
|
||||
select: { employeeId: true, totalPay: true, tax: true, socialEmp: true, housingEmp: true },
|
||||
})
|
||||
const prevMap = new Map<string, { totalPay: number; tax: number; socialEmp: number; housingEmp: number }>()
|
||||
for (const p of allPrevPayslips) {
|
||||
const existing = prevMap.get(p.employeeId) || { totalPay: 0, tax: 0, socialEmp: 0, housingEmp: 0 }
|
||||
existing.totalPay += p.totalPay
|
||||
existing.tax += p.tax
|
||||
existing.socialEmp += p.socialEmp
|
||||
existing.housingEmp += p.housingEmp
|
||||
prevMap.set(p.employeeId, existing)
|
||||
}
|
||||
|
||||
let generated = 0
|
||||
for (const [employeeId, summary] of employeeMap) {
|
||||
// 获取当年之前月份的累计数据
|
||||
const prevPayslips = await prisma.payslip.findMany({
|
||||
where: { orgId, employeeId, month: { startsWith: year, lt: month } },
|
||||
select: { totalPay: true, tax: true, socialEmp: true, housingEmp: true },
|
||||
})
|
||||
const ytdIncome = prevPayslips.reduce((s, p) => s + p.totalPay, 0) + summary.totalPay
|
||||
const ytdTaxDeducted = prevPayslips.reduce((s, p) => s + p.tax, 0) + summary.tax
|
||||
const ytdSocialEmp = prevPayslips.reduce((s, p) => s + p.socialEmp, 0) + summary.socialEmp
|
||||
const ytdHousingEmp = prevPayslips.reduce((s, p) => s + p.housingEmp, 0) + summary.housingEmp
|
||||
const prev = prevMap.get(employeeId) || { totalPay: 0, tax: 0, socialEmp: 0, housingEmp: 0 }
|
||||
const ytdIncome = prev.totalPay + summary.totalPay
|
||||
const ytdTaxDeducted = prev.tax + summary.tax
|
||||
const ytdSocialEmp = prev.socialEmp + summary.socialEmp
|
||||
const ytdHousingEmp = prev.housingEmp + summary.housingEmp
|
||||
|
||||
await prisma.payslip.upsert({
|
||||
where: { employeeId_month: { employeeId, month } },
|
||||
|
||||
@@ -136,150 +136,125 @@ export function assessRisk(employee: any, reason: string): { level: RiskAssessme
|
||||
return { level, warnings }
|
||||
}
|
||||
|
||||
async function createTerminationRecord(
|
||||
orgId: string,
|
||||
userId: string,
|
||||
data: any,
|
||||
recordData: {
|
||||
type: 'TERMINATION' | 'RESIGNATION'
|
||||
reason: TerminationReason | 'RESIGNATION'
|
||||
compensation?: number
|
||||
riskLevel: RiskAssessment
|
||||
checklist: any
|
||||
remark?: string | null
|
||||
resignationReason?: string | null
|
||||
},
|
||||
conflictMsg: string,
|
||||
) {
|
||||
const employee = await prisma.employee.findFirst({ where: { id: data.employeeId, orgId } })
|
||||
if (!employee) {
|
||||
throw { code: 'NOT_FOUND', message: '员工不存在' }
|
||||
}
|
||||
|
||||
const latestTerm = await prisma.terminationRecord.findFirst({
|
||||
where: { employeeId: data.employeeId },
|
||||
orderBy: { terminationDate: 'desc' },
|
||||
})
|
||||
if (latestTerm && latestTerm.terminationDate >= employee.hireDate) {
|
||||
throw { code: 'CONFLICT', message: conflictMsg }
|
||||
}
|
||||
|
||||
const termDate = new Date(data.terminationDate)
|
||||
const termMonth = dateToMonth(termDate)
|
||||
const socialInsEndMonth = data.socialInsEndMonth || termMonth
|
||||
const housingFundEndMonth = data.housingFundEndMonth || termMonth
|
||||
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const isResigned = termDate <= today
|
||||
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const record = await tx.terminationRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: data.employeeId,
|
||||
terminationDate: termDate,
|
||||
socialInsEndMonth,
|
||||
housingFundEndMonth,
|
||||
createdBy: userId,
|
||||
...recordData,
|
||||
},
|
||||
})
|
||||
|
||||
await tx.employeeSocialInsRecord.updateMany({
|
||||
where: { employeeId: data.employeeId, endMonth: null },
|
||||
data: { endMonth: socialInsEndMonth, changeRefId: record.id },
|
||||
})
|
||||
|
||||
await tx.employeeHousingFundRecord.updateMany({
|
||||
where: { employeeId: data.employeeId, endMonth: null },
|
||||
data: { endMonth: housingFundEndMonth, changeRefId: record.id },
|
||||
})
|
||||
|
||||
await tx.employee.update({
|
||||
where: { id: data.employeeId },
|
||||
data: {
|
||||
status: isResigned ? 'RESIGNED' : 'ACTIVE',
|
||||
socialInsEndMonth,
|
||||
housingFundEndMonth,
|
||||
},
|
||||
})
|
||||
|
||||
await tx.riskItem.updateMany({
|
||||
where: { employeeId: data.employeeId, status: 'PENDING' },
|
||||
data: { status: 'RESOLVED', resolvedAt: new Date() },
|
||||
})
|
||||
|
||||
return { id: record.id }
|
||||
})
|
||||
}
|
||||
|
||||
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 latestTerm = await prisma.terminationRecord.findFirst({
|
||||
where: { employeeId: data.employeeId },
|
||||
orderBy: { terminationDate: 'desc' },
|
||||
})
|
||||
if (latestTerm && latestTerm.terminationDate >= employee.hireDate) {
|
||||
throw { code: 'CONFLICT', message: '该员工已有离职/解聘记录,如需再次解聘请先办理重新雇佣' }
|
||||
}
|
||||
|
||||
const { level } = assessRisk(employee, data.reason)
|
||||
|
||||
const termDate = new Date(data.terminationDate)
|
||||
const termMonth = dateToMonth(termDate)
|
||||
const socialInsEndMonth = data.socialInsEndMonth || termMonth
|
||||
const housingFundEndMonth = data.housingFundEndMonth || termMonth
|
||||
|
||||
const record = await prisma.terminationRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: data.employeeId,
|
||||
return createTerminationRecord(
|
||||
orgId,
|
||||
userId,
|
||||
data,
|
||||
{
|
||||
type: 'TERMINATION',
|
||||
reason: data.reason,
|
||||
terminationDate: termDate,
|
||||
compensation: data.compensation || 0,
|
||||
socialInsEndMonth,
|
||||
housingFundEndMonth,
|
||||
riskLevel: level,
|
||||
checklist: data.checklist || {},
|
||||
remark: data.remark,
|
||||
createdBy: userId,
|
||||
},
|
||||
})
|
||||
|
||||
// 关闭社保缴费记录(设置 endMonth)
|
||||
await prisma.employeeSocialInsRecord.updateMany({
|
||||
where: { employeeId: data.employeeId, endMonth: null },
|
||||
data: { endMonth: socialInsEndMonth, changeRefId: record.id },
|
||||
})
|
||||
|
||||
// 关闭公积金缴费记录
|
||||
await prisma.employeeHousingFundRecord.updateMany({
|
||||
where: { employeeId: data.employeeId, endMonth: null },
|
||||
data: { endMonth: housingFundEndMonth, changeRefId: record.id },
|
||||
})
|
||||
|
||||
// 根据解聘日期判断在职/离职状态
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const isResigned = termDate <= today
|
||||
|
||||
await prisma.employee.update({
|
||||
where: { id: data.employeeId },
|
||||
data: {
|
||||
status: isResigned ? 'RESIGNED' : 'ACTIVE',
|
||||
socialInsEndMonth,
|
||||
housingFundEndMonth,
|
||||
},
|
||||
})
|
||||
|
||||
await prisma.riskItem.updateMany({
|
||||
where: { employeeId: data.employeeId, status: 'PENDING' },
|
||||
data: { status: 'RESOLVED', resolvedAt: new Date() },
|
||||
})
|
||||
|
||||
return { id: record.id }
|
||||
'该员工已有离职/解聘记录,如需再次解聘请先办理重新雇佣',
|
||||
)
|
||||
}
|
||||
|
||||
// 员工主动离职
|
||||
export async function createResignation(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 latestTerm = await prisma.terminationRecord.findFirst({
|
||||
where: { employeeId: data.employeeId },
|
||||
orderBy: { terminationDate: 'desc' },
|
||||
})
|
||||
if (latestTerm && latestTerm.terminationDate >= employee.hireDate) {
|
||||
throw { code: 'CONFLICT', message: '该员工已有离职/解聘记录,如需再次办理请先重新雇佣' }
|
||||
}
|
||||
|
||||
const termDate = new Date(data.terminationDate)
|
||||
const termMonth = dateToMonth(termDate)
|
||||
const socialInsEndMonth = data.socialInsEndMonth || termMonth
|
||||
const housingFundEndMonth = data.housingFundEndMonth || termMonth
|
||||
|
||||
const record = await prisma.terminationRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: data.employeeId,
|
||||
return createTerminationRecord(
|
||||
orgId,
|
||||
userId,
|
||||
data,
|
||||
{
|
||||
type: 'RESIGNATION',
|
||||
reason: 'RESIGNATION',
|
||||
terminationDate: termDate,
|
||||
resignationReason: data.resignationReason || null,
|
||||
compensation: 0,
|
||||
socialInsEndMonth,
|
||||
housingFundEndMonth,
|
||||
riskLevel: 'SAFE',
|
||||
checklist: {},
|
||||
remark: data.remark || null,
|
||||
createdBy: userId,
|
||||
},
|
||||
})
|
||||
|
||||
// 关闭社保缴费记录
|
||||
await prisma.employeeSocialInsRecord.updateMany({
|
||||
where: { employeeId: data.employeeId, endMonth: null },
|
||||
data: { endMonth: socialInsEndMonth, changeRefId: record.id },
|
||||
})
|
||||
|
||||
// 关闭公积金缴费记录
|
||||
await prisma.employeeHousingFundRecord.updateMany({
|
||||
where: { employeeId: data.employeeId, endMonth: null },
|
||||
data: { endMonth: housingFundEndMonth, changeRefId: record.id },
|
||||
})
|
||||
|
||||
// 根据离职日期判断在职/离职状态
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const isResigned = termDate <= today
|
||||
|
||||
await prisma.employee.update({
|
||||
where: { id: data.employeeId },
|
||||
data: {
|
||||
status: isResigned ? 'RESIGNED' : 'ACTIVE',
|
||||
socialInsEndMonth,
|
||||
housingFundEndMonth,
|
||||
},
|
||||
})
|
||||
|
||||
await prisma.riskItem.updateMany({
|
||||
where: { employeeId: data.employeeId, status: 'PENDING' },
|
||||
data: { status: 'RESOLVED', resolvedAt: new Date() },
|
||||
})
|
||||
|
||||
return { id: record.id }
|
||||
'该员工已有离职/解聘记录,如需再次办理请先重新雇佣',
|
||||
)
|
||||
}
|
||||
|
||||
// 撤回离职/解聘(仅未到日期可撤回)
|
||||
@@ -439,73 +414,84 @@ export async function batchTerminate(
|
||||
const success: string[] = []
|
||||
const failed: Array<{ employeeId: string; reason: string }> = []
|
||||
|
||||
const employeeIds = items.map((i) => i.employeeId)
|
||||
const employees = await prisma.employee.findMany({
|
||||
where: { id: { in: employeeIds }, orgId },
|
||||
})
|
||||
const empMap = new Map(employees.map((e) => [e.id, e]))
|
||||
|
||||
const existingTerms = await prisma.terminationRecord.findMany({
|
||||
where: { employeeId: { in: employeeIds } },
|
||||
orderBy: { terminationDate: 'desc' },
|
||||
})
|
||||
const latestTermMap = new Map<string, Date>()
|
||||
for (const t of existingTerms) {
|
||||
if (!latestTermMap.has(t.employeeId)) {
|
||||
latestTermMap.set(t.employeeId, t.terminationDate)
|
||||
}
|
||||
}
|
||||
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
|
||||
for (const item of items) {
|
||||
try {
|
||||
const termDate = new Date(item.terminationDate)
|
||||
const termMonth = dateToMonth(termDate)
|
||||
|
||||
// 校验:已有离职/解聘记录
|
||||
const latestTerm = await prisma.terminationRecord.findFirst({
|
||||
where: { employeeId: item.employeeId },
|
||||
orderBy: { terminationDate: 'desc' },
|
||||
})
|
||||
const employee = await prisma.employee.findFirst({ where: { id: item.employeeId, orgId } })
|
||||
const employee = empMap.get(item.employeeId)
|
||||
if (!employee) {
|
||||
failed.push({ employeeId: item.employeeId, reason: '员工不存在' })
|
||||
continue
|
||||
}
|
||||
if (latestTerm && latestTerm.terminationDate >= employee.hireDate) {
|
||||
const latestTermDate = latestTermMap.get(item.employeeId)
|
||||
if (latestTermDate && latestTermDate >= employee.hireDate) {
|
||||
failed.push({ employeeId: item.employeeId, reason: '该员工已有离职/解聘记录' })
|
||||
continue
|
||||
}
|
||||
|
||||
const termDate = new Date(item.terminationDate)
|
||||
const termMonth = dateToMonth(termDate)
|
||||
const { level } = assessRisk(employee, item.reason)
|
||||
|
||||
await prisma.terminationRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: item.employeeId,
|
||||
type: 'TERMINATION',
|
||||
reason: item.reason as TerminationReason,
|
||||
terminationDate: termDate,
|
||||
compensation: item.compensation || 0,
|
||||
socialInsEndMonth: termMonth,
|
||||
housingFundEndMonth: termMonth,
|
||||
riskLevel: level,
|
||||
checklist: {},
|
||||
remark: '批量解聘',
|
||||
createdBy: userId,
|
||||
},
|
||||
})
|
||||
|
||||
// 关闭社保和公积金
|
||||
await prisma.employeeSocialInsRecord.updateMany({
|
||||
where: { employeeId: item.employeeId, endMonth: null },
|
||||
data: { endMonth: termMonth },
|
||||
})
|
||||
await prisma.employeeHousingFundRecord.updateMany({
|
||||
where: { employeeId: item.employeeId, endMonth: null },
|
||||
data: { endMonth: termMonth },
|
||||
})
|
||||
|
||||
// 更新员工状态
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const isResigned = termDate <= today
|
||||
|
||||
await prisma.employee.update({
|
||||
where: { id: item.employeeId },
|
||||
data: {
|
||||
status: isResigned ? 'RESIGNED' : 'ACTIVE',
|
||||
socialInsEndMonth: termMonth,
|
||||
housingFundEndMonth: termMonth,
|
||||
},
|
||||
})
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.terminationRecord.create({
|
||||
data: {
|
||||
orgId,
|
||||
employeeId: item.employeeId,
|
||||
type: 'TERMINATION',
|
||||
reason: item.reason as TerminationReason,
|
||||
terminationDate: termDate,
|
||||
compensation: item.compensation || 0,
|
||||
socialInsEndMonth: termMonth,
|
||||
housingFundEndMonth: termMonth,
|
||||
riskLevel: level,
|
||||
checklist: {},
|
||||
remark: '批量解聘',
|
||||
createdBy: userId,
|
||||
},
|
||||
})
|
||||
|
||||
// 关闭风险项
|
||||
await prisma.riskItem.updateMany({
|
||||
where: { employeeId: item.employeeId, status: 'PENDING' },
|
||||
data: { status: 'RESOLVED', resolvedAt: new Date() },
|
||||
await tx.employeeSocialInsRecord.updateMany({
|
||||
where: { employeeId: item.employeeId, endMonth: null },
|
||||
data: { endMonth: termMonth },
|
||||
})
|
||||
await tx.employeeHousingFundRecord.updateMany({
|
||||
where: { employeeId: item.employeeId, endMonth: null },
|
||||
data: { endMonth: termMonth },
|
||||
})
|
||||
|
||||
await tx.employee.update({
|
||||
where: { id: item.employeeId },
|
||||
data: {
|
||||
status: isResigned ? 'RESIGNED' : 'ACTIVE',
|
||||
socialInsEndMonth: termMonth,
|
||||
housingFundEndMonth: termMonth,
|
||||
},
|
||||
})
|
||||
|
||||
await tx.riskItem.updateMany({
|
||||
where: { employeeId: item.employeeId, status: 'PENDING' },
|
||||
data: { status: 'RESOLVED', resolvedAt: new Date() },
|
||||
})
|
||||
})
|
||||
|
||||
success.push(item.employeeId)
|
||||
@@ -687,42 +673,39 @@ export async function executeTermination(orgId: string, recordId: string, userId
|
||||
const socialInsEndMonth = record.socialInsEndMonth || termMonth
|
||||
const housingFundEndMonth = record.housingFundEndMonth || termMonth
|
||||
|
||||
// 关闭社保缴费记录
|
||||
await prisma.employeeSocialInsRecord.updateMany({
|
||||
where: { employeeId: record.employeeId, endMonth: null },
|
||||
data: { endMonth: socialInsEndMonth, changeRefId: record.id },
|
||||
})
|
||||
|
||||
// 关闭公积金缴费记录
|
||||
await prisma.employeeHousingFundRecord.updateMany({
|
||||
where: { employeeId: record.employeeId, endMonth: null },
|
||||
data: { endMonth: housingFundEndMonth, changeRefId: record.id },
|
||||
})
|
||||
|
||||
// 更新员工状态
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const isResigned = termDate <= today
|
||||
|
||||
await prisma.employee.update({
|
||||
where: { id: record.employeeId },
|
||||
data: {
|
||||
status: isResigned ? 'RESIGNED' : 'ACTIVE',
|
||||
socialInsEndMonth,
|
||||
housingFundEndMonth,
|
||||
},
|
||||
})
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.employeeSocialInsRecord.updateMany({
|
||||
where: { employeeId: record.employeeId, endMonth: null },
|
||||
data: { endMonth: socialInsEndMonth, changeRefId: record.id },
|
||||
})
|
||||
|
||||
// 关闭风险项
|
||||
await prisma.riskItem.updateMany({
|
||||
where: { employeeId: record.employeeId, status: 'PENDING' },
|
||||
data: { status: 'RESOLVED', resolvedAt: new Date() },
|
||||
})
|
||||
await tx.employeeHousingFundRecord.updateMany({
|
||||
where: { employeeId: record.employeeId, endMonth: null },
|
||||
data: { endMonth: housingFundEndMonth, changeRefId: record.id },
|
||||
})
|
||||
|
||||
// 标记为已完成
|
||||
await prisma.terminationRecord.update({
|
||||
where: { id: recordId },
|
||||
data: { status: 'COMPLETED', updatedBy: userId },
|
||||
await tx.employee.update({
|
||||
where: { id: record.employeeId },
|
||||
data: {
|
||||
status: isResigned ? 'RESIGNED' : 'ACTIVE',
|
||||
socialInsEndMonth,
|
||||
housingFundEndMonth,
|
||||
},
|
||||
})
|
||||
|
||||
await tx.riskItem.updateMany({
|
||||
where: { employeeId: record.employeeId, status: 'PENDING' },
|
||||
data: { status: 'RESOLVED', resolvedAt: new Date() },
|
||||
})
|
||||
|
||||
await tx.terminationRecord.update({
|
||||
where: { id: recordId },
|
||||
data: { status: 'COMPLETED', updatedBy: userId },
|
||||
})
|
||||
})
|
||||
|
||||
return { id: recordId }
|
||||
|
||||
Reference in New Issue
Block a user