From 7ca2ada0d4cc6592c6e5d6f5814e673491d00ef7 Mon Sep 17 00:00:00 2001 From: selfrelease Date: Sat, 25 Jul 2026 20:10:42 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B7=B2=E6=92=A4?= =?UTF-8?q?=E9=94=80=E8=A7=A3=E8=81=98=E8=AE=B0=E5=BD=95(CANCELLED)?= =?UTF-8?q?=E5=9C=A8=E5=90=84=E6=A8=A1=E5=9D=97=E4=B8=AD=E6=9C=AA=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E8=BF=87=E6=BB=A4=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 花名册API新增latestTerminationStatus字段,前端列表/操作按钮/离职日期列过滤CANCELLED/COMPLETED - 证据链后端过滤CANCELLED状态的解聘记录,不再产生无效证据和风险提醒 - 重新入职判断增加status=COMPLETED条件,避免已撤销记录被误判为已离职 - 薪酬警告过滤CANCELLED状态,不再对已撤销记录触发结算提醒 - 风险扫描查询条件增加status=COMPLETED,避免已撤销记录导致员工被跳过 - 解聘向导canProceed和警告提示过滤CANCELLED/COMPLETED状态 - 档案解聘记录列表过滤CANCELLED状态,新增流程状态标签 - 重新生成Prisma Client以同步femaleWorkerType字段 --- backend/src/routes/roster.routes.ts | 5 +++-- backend/src/services/contract.service.ts | 2 +- backend/src/services/payroll.service.ts | 2 +- backend/src/services/risk.service.ts | 2 +- frontend/src/pages/Roster.tsx | 16 +++++++++++----- frontend/src/pages/Termination.tsx | 5 +++-- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/backend/src/routes/roster.routes.ts b/backend/src/routes/roster.routes.ts index 6c11d60..7961ae3 100644 --- a/backend/src/routes/roster.routes.ts +++ b/backend/src/routes/roster.routes.ts @@ -118,6 +118,7 @@ router.get('/', authMiddleware, async (req: AuthRequest, res, next) => { latestTerminationDate: e.terminations[0]?.terminationDate || null, latestTerminationType: e.terminations[0]?.type || null, latestTerminationId: e.terminations[0]?.id || null, + latestTerminationStatus: e.terminations[0]?.status || null, hireDate: e.hireDate, gender: e.gender, phone: e.phone, @@ -389,8 +390,8 @@ router.get('/:id/evidence-chain', authMiddleware, async (req: AuthRequest, res, }) }) - // 7. 解聘证据 - employee.terminations.forEach((t) => { + // 7. 解聘证据(排除已撤销的记录) + employee.terminations.filter((t) => t.status !== 'CANCELLED').forEach((t) => { const reasonMap: Record = { NEGOTIATED: '协商一致', FAULT: '员工过错', NONFAULT: '非过错解除', LAYOFF: '经济性裁员', EXPIRED: '合同到期', RESIGNATION: '员工主动离职' } const typeLabel = t.type === 'RESIGNATION' && t.reason === 'NEGOTIATED' ? '协商一致离职' diff --git a/backend/src/services/contract.service.ts b/backend/src/services/contract.service.ts index 668dbb6..a20e499 100644 --- a/backend/src/services/contract.service.ts +++ b/backend/src/services/contract.service.ts @@ -315,7 +315,7 @@ export async function rehireEmployee(orgId: string, userId: string, id: string, const today = new Date() today.setHours(0, 0, 0, 0) - const isResigned = employee.terminations.some((t) => t.terminationDate <= today) + const isResigned = employee.terminations.some((t) => t.status === 'COMPLETED' && t.terminationDate <= today) if (!isResigned) { throw { code: 'CONFLICT', message: '该员工当前在职,无需重新入职' } } diff --git a/backend/src/services/payroll.service.ts b/backend/src/services/payroll.service.ts index d859f6e..0b568d5 100644 --- a/backend/src/services/payroll.service.ts +++ b/backend/src/services/payroll.service.ts @@ -236,7 +236,7 @@ export async function getPayrollRiskWarnings(orgId: string, employeeId: string): if (!employee.housingFundBase) { warnings.push('未设置公积金缴费基数') } - if (employee.terminations.length) { + if (employee.terminations.some((t) => t.status !== 'CANCELLED')) { warnings.push('已有解聘记录,请注意结算') } diff --git a/backend/src/services/risk.service.ts b/backend/src/services/risk.service.ts index 13288ff..6f5b512 100644 --- a/backend/src/services/risk.service.ts +++ b/backend/src/services/risk.service.ts @@ -107,7 +107,7 @@ export async function detectOnboardingRisks(orgId: string) { where: { orgId, status: 'ACTIVE', hireDate: { lte: today } }, include: { contracts: { orderBy: { createdAt: 'desc' }, take: 1 }, - terminations: { where: { terminationDate: { lte: today } }, take: 1 }, + terminations: { where: { terminationDate: { lte: today }, status: 'COMPLETED' }, take: 1 }, }, }) diff --git a/frontend/src/pages/Roster.tsx b/frontend/src/pages/Roster.tsx index f2c89bd..c101f58 100644 --- a/frontend/src/pages/Roster.tsx +++ b/frontend/src/pages/Roster.tsx @@ -346,7 +346,7 @@ export default function Roster() { {e.hireDate?.toString().slice(0, 10)} - {e.hasTermination && e.latestTerminationDate ? ( + {e.hasTermination && e.latestTerminationDate && e.latestTerminationStatus !== 'CANCELLED' && e.latestTerminationStatus !== 'COMPLETED' ? ( {e.latestTerminationDate.toString().slice(0, 10)} {e.status === 'ACTIVE' && ' (预计)'} @@ -425,7 +425,7 @@ export default function Roster() { {e.counts?.performanceRecords || 0} {e.counts?.payslips || 0} - {e.status === 'ACTIVE' && !e.hasTermination && ( + {e.status === 'ACTIVE' && (!e.hasTermination || e.latestTerminationStatus === 'CANCELLED' || e.latestTerminationStatus === 'COMPLETED') && (
)} - {e.hasTermination && e.status === 'ACTIVE' && ( + {e.hasTermination && e.status === 'ACTIVE' && e.latestTerminationStatus !== 'CANCELLED' && e.latestTerminationStatus !== 'COMPLETED' && (
{e.latestTerminationType === 'RESIGNATION' ? '待离职' : '待解聘'} @@ -2294,7 +2294,8 @@ function TerminationInfo({ employeeId, profile, records }: { employeeId: string; enabled: !!printRecord, }) - if (!records?.length) return
暂无离职/解聘记录
+ const validRecords = (records || []).filter((t: any) => t.status !== 'CANCELLED') + if (!validRecords.length) return
暂无离职/解聘记录
if (printRecord) { return ( @@ -2412,7 +2413,7 @@ function TerminationInfo({ employeeId, profile, records }: { employeeId: string; return (
- {records.map((t) => ( + {validRecords.map((t) => (
@@ -2424,6 +2425,11 @@ function TerminationInfo({ employeeId, profile, records }: { employeeId: string; {t.riskLevel === 'SAFE' ? '风险低' : t.riskLevel === 'WARNING' ? '注意' : '高风险'} )} + {t.status && ( + + {t.status === 'DRAFT' ? '草稿' : t.status === 'PENDING_APPROVAL' ? '待审批' : t.status === 'APPROVED' ? '已审批' : t.status === 'COMPLETED' ? '已完成' : t.status === 'REJECTED' ? '已驳回' : t.status === 'EXECUTING' ? '执行中' : t.status} + + )}
{t.type === 'RESIGNATION' ? ( diff --git a/frontend/src/pages/Termination.tsx b/frontend/src/pages/Termination.tsx index 19a466c..f0a049b 100644 --- a/frontend/src/pages/Termination.tsx +++ b/frontend/src/pages/Termination.tsx @@ -49,6 +49,7 @@ interface RosterEmployee { department: string status: string hasTermination?: boolean + latestTerminationStatus?: string hireDate: string monthlySalary: number latestContract: any @@ -417,7 +418,7 @@ export default function Termination() { }, [selectedEmployee, terminationDate, socialAvgWage, reason]) const canProceed = () => { - if (step === 0) return !!employeeId && (!!draftId || !selectedEmployee?.hasTermination) + if (step === 0) return !!employeeId && (!!draftId || !selectedEmployee?.hasTermination || selectedEmployee?.latestTerminationStatus === 'CANCELLED' || selectedEmployee?.latestTerminationStatus === 'COMPLETED') if (step === 1) return !!reason && !!terminationDate && (!riskAssessment?.warnings.length || acknowledgeRisk) if (step === 2) return true if (step === 3) return true @@ -921,7 +922,7 @@ export default function Termination() {
{selectedEmployee.name}({selectedEmployee.department})
入职日期:{selectedEmployee.hireDate?.toString().slice(0, 10)}
月工资:¥{fmt(selectedEmployee.monthlySalary)}
- {selectedEmployee.hasTermination && ( + {selectedEmployee.hasTermination && selectedEmployee.latestTerminationStatus !== 'CANCELLED' && selectedEmployee.latestTerminationStatus !== 'COMPLETED' && (
⚠️ 该员工已有离职/解聘记录,如需再次解聘请先办理重新雇佣
)} {selectedEmployee.latestContract ? (