From f5a03f82bca689fed14b8bd410372bfc1a0f048f Mon Sep 17 00:00:00 2001 From: selfrelease Date: Fri, 31 Jul 2026 09:24:44 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=BA=AB=E4=BB=BD=E8=AF=81=E5=8F=B7?= =?UTF-8?q?=E5=BF=85=E5=A1=AB+=E5=8E=BB=E9=87=8D+=E6=8A=A5=E5=91=8A?= =?UTF-8?q?=E6=8C=89=E5=A7=93=E5=90=8D=E5=85=9C=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 createEmployeeSchema 加 idCardNumber 必填18位校验 - 后端 updateEmployeeSchema 加 idCardNumber 可选编辑 - updateEmployee 支持编辑身份证号,自动生成 hash/生日/性别 - Excel 导入时身份证号必填,为空跳过 - 年度价值报告去重 key 改为 idCardHash > name > employeeId 兜底 --- backend/src/routes/import.routes.ts | 1 + backend/src/schemas/contract.schema.ts | 2 ++ backend/src/services/contract.service.ts | 10 ++++++++++ backend/src/services/risk.service.ts | 4 ++-- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/backend/src/routes/import.routes.ts b/backend/src/routes/import.routes.ts index 757d497..64d7375 100644 --- a/backend/src/routes/import.routes.ts +++ b/backend/src/routes/import.routes.ts @@ -260,6 +260,7 @@ router.post('/excel', authMiddleware, requireAdmin, upload.single('file'), async if (salary === '0') { result.skipped++; result.errors.push(`员工第${i + 2}行:月工资为空`); result.details.push({ sheet: '员工信息', row: i + 2, name, status: 'skipped', message: '月工资为空' }); continue } let idCard = val(getField(r, '身份证号')) + if (!idCard) { result.skipped++; result.errors.push(`员工第${i + 2}行:身份证号为空,跳过`); result.details.push({ sheet: '员工信息', row: i + 2, name, status: 'skipped', message: '身份证号为空' }); continue } if (idCard) { const idCheck = validateIdCard(idCard) if (!idCheck.valid) { result.skipped++; result.errors.push(`员工第${i + 2}行:${idCheck.error}`); result.details.push({ sheet: '员工信息', row: i + 2, name, status: 'skipped', message: idCheck.error }); continue } diff --git a/backend/src/schemas/contract.schema.ts b/backend/src/schemas/contract.schema.ts index d0a57f2..3bc172f 100644 --- a/backend/src/schemas/contract.schema.ts +++ b/backend/src/schemas/contract.schema.ts @@ -5,6 +5,7 @@ export const createEmployeeSchema = z.object({ department: z.string().min(1, '部门不能为空').max(50, '部门最多50个字'), hireDate: z.string().datetime(), monthlySalary: z.string().min(1, '月薪不能为空'), + idCardNumber: z.string().min(18, '身份证号不能为空且必须18位').max(18, '身份证号必须18位'), gender: z.enum(['男', '女']).optional(), femaleWorkerType: z.enum(['CADRE', 'WORKER']).optional(), phone: z.string().regex(/^1[3-9]\d{9}$/).optional(), @@ -30,6 +31,7 @@ export const updateEmployeeSchema = z.object({ department: z.string().min(1).max(50).optional(), hireDate: z.string().datetime().optional(), monthlySalary: z.string().min(1).optional(), + idCardNumber: z.string().min(18).max(18).optional(), gender: z.enum(['男', '女']).optional(), femaleWorkerType: z.enum(['CADRE', 'WORKER']).optional(), phone: z.string().regex(/^1[3-9]\d{9}$/).optional(), diff --git a/backend/src/services/contract.service.ts b/backend/src/services/contract.service.ts index 327ee63..e0352c9 100644 --- a/backend/src/services/contract.service.ts +++ b/backend/src/services/contract.service.ts @@ -1,6 +1,7 @@ import prisma from '../lib/prisma' import { encrypt, decrypt, sha256 } from '../lib/crypto' import { runRiskDetection } from './risk.service' +import { extractBirthDateFromIdCard, extractGenderFromIdCard } from './retirement.service' function daysBetween(a: Date, b: Date): number { return Math.floor((a.getTime() - b.getTime()) / (1000 * 60 * 60 * 24)) @@ -525,6 +526,15 @@ export async function updateEmployee(orgId: string, id: string, data: any) { if (data.gender !== undefined) updateData.gender = data.gender if (data.femaleWorkerType !== undefined) updateData.femaleWorkerType = data.femaleWorkerType if (data.phone !== undefined) updateData.phone = data.phone + if (data.idCardNumber !== undefined) { + updateData.idCardNumber = encrypt(data.idCardNumber) + updateData.idCardHash = sha256(data.idCardNumber) + updateData.birthDate = extractBirthDateFromIdCard(data.idCardNumber) + if (!updateData.gender) { + const gender = extractGenderFromIdCard(data.idCardNumber) + if (gender) updateData.gender = gender + } + } if (data.bankName !== undefined) updateData.bankName = data.bankName if (data.bankAccount !== undefined) updateData.bankAccount = encrypt(data.bankAccount) if (data.emergencyContact !== undefined) updateData.emergencyContact = data.emergencyContact diff --git a/backend/src/services/risk.service.ts b/backend/src/services/risk.service.ts index 808a5c0..a07db89 100644 --- a/backend/src/services/risk.service.ts +++ b/backend/src/services/risk.service.ts @@ -1674,8 +1674,8 @@ export async function getAnnualValueReport(orgId: string, year: number) { } for (const r of employeeRiskDetails) { - // 按身份证号分组,无身份证号时回退到 employeeId - const personKey = r.employee?.idCardHash || r.employeeId || '_unknown' + // 去重优先级:身份证号 > 姓名回退到姓名,避免同一人多条 Employee 记录被重复计算 + const personKey = r.employee?.idCardHash || r.employee?.name || r.employeeId || '_unknown' if (!personBreakdown[personKey]) { personBreakdown[personKey] = { personKey,