fix: 统一风险中心及全应用身份证号显示
后端: - employee.routes.ts: all-lite 接口加上 idCardNumber 并解密 - special-status.routes.ts: 列表和详情查询加上 idCardNumber 并解密 - esign.routes.ts: 列表/pending/detail/remind 接口加上 idCardNumber 并解密 - risk.service.ts: getMonthlyCalendar 所有事件加上 employeeIdCardNumber - termination.service.ts: batchTerminatePreview 加上 employeeIdCardNumber - employee.routes.ts: preview-renew 加上 employeeIdCardNumber 前端: - Roster.tsx: 批量续签/解聘预检结果加上身份证号 - SpecialStatus.tsx: 列表/下拉/删除确认加上身份证号 - Calendar.tsx: 日历事件姓名后显示身份证号 - ESign.tsx: 签署列表/详情/员工列表/提醒弹窗/下拉加上身份证号 - CommercialInsuranceTab.tsx: 参保选择列表加上身份证号 - OvertimeTab.tsx: 导入预览加上身份证号
This commit is contained in:
@@ -56,10 +56,14 @@ router.get('/all-lite', authMiddleware, async (req: AuthRequest, res, next) => {
|
||||
status: { in: status },
|
||||
...(department && { department }),
|
||||
},
|
||||
select: { id: true, name: true, department: true, position: true, gender: true, status: true },
|
||||
select: { id: true, name: true, department: true, position: true, gender: true, status: true, idCardNumber: true },
|
||||
orderBy: { name: 'asc' },
|
||||
})
|
||||
res.json({ success: true, data: employees })
|
||||
const data = employees.map((e: any) => ({
|
||||
...e,
|
||||
idCardNumber: (() => { try { const v = e.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
}))
|
||||
res.json({ success: true, data })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
@@ -301,6 +305,7 @@ router.post('/contracts/preview-renew', authMiddleware, async (req: AuthRequest,
|
||||
contractId: contract.id,
|
||||
employeeId: contract.employeeId,
|
||||
employeeName: employee.name,
|
||||
employeeIdCardNumber: (() => { try { const v = employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
department: employee.department,
|
||||
currentContractType: contract.contractType,
|
||||
renewalCount,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { authMiddleware, AuthRequest } from '../middleware/auth'
|
||||
import { auditLog } from '../middleware/auditLog'
|
||||
import { createEvidence, appendEvidence } from '../services/evidence.service'
|
||||
import { renderTemplate, getTemplateById } from '../services/template.service'
|
||||
import { decrypt } from '../lib/crypto'
|
||||
|
||||
const router = Router()
|
||||
router.use(authMiddleware)
|
||||
@@ -69,11 +70,15 @@ router.get('/', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
...(scene && { scene }),
|
||||
},
|
||||
include: {
|
||||
employee: { select: { id: true, name: true, department: true, phone: true } },
|
||||
employee: { select: { id: true, name: true, department: true, phone: true, idCardNumber: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
res.json({ success: true, data: records })
|
||||
const recordsWithIdCard = records.map((r: any) => ({
|
||||
...r,
|
||||
employee: r.employee ? { ...r.employee, idCardNumber: (() => { try { const v = r.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })() } : r.employee,
|
||||
}))
|
||||
res.json({ success: true, data: recordsWithIdCard })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
@@ -98,7 +103,7 @@ router.get('/pending', async (req: AuthRequest, res: Response, next: NextFunctio
|
||||
status: { in: ['PENDING', 'SIGNING'] },
|
||||
},
|
||||
include: {
|
||||
employee: { select: { id: true, name: true, department: true, phone: true, status: true } },
|
||||
employee: { select: { id: true, name: true, department: true, phone: true, status: true, idCardNumber: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
@@ -111,7 +116,7 @@ router.get('/pending', async (req: AuthRequest, res: Response, next: NextFunctio
|
||||
employee: { status: 'ACTIVE' },
|
||||
},
|
||||
include: {
|
||||
employee: { select: { id: true, name: true, department: true, phone: true, status: true } },
|
||||
employee: { select: { id: true, name: true, department: true, phone: true, status: true, idCardNumber: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
@@ -122,17 +127,19 @@ router.get('/pending', async (req: AuthRequest, res: Response, next: NextFunctio
|
||||
name: string
|
||||
department: string | null
|
||||
phone: string | null
|
||||
idCardNumber: string | null
|
||||
pendingItems: any[]
|
||||
}>()
|
||||
|
||||
// 辅助函数:添加员工到 map
|
||||
const ensureEmployee = (emp: { id: string; name: string; department: string | null; phone: string | null }) => {
|
||||
const ensureEmployee = (emp: { id: string; name: string; department: string | null; phone: string | null; idCardNumber?: string | null }) => {
|
||||
if (!employeeMap.has(emp.id)) {
|
||||
employeeMap.set(emp.id, {
|
||||
employeeId: emp.id,
|
||||
name: emp.name,
|
||||
department: emp.department,
|
||||
phone: emp.phone,
|
||||
idCardNumber: (() => { try { const v = emp.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
pendingItems: [],
|
||||
})
|
||||
}
|
||||
@@ -287,7 +294,7 @@ router.post('/remind', async (req: AuthRequest, res: Response, next: NextFunctio
|
||||
}
|
||||
const employee = await prisma.employee.findFirst({
|
||||
where: { id: employeeId, orgId: req.user!.orgId, status: 'ACTIVE' },
|
||||
select: { id: true, name: true, phone: true, orgId: true },
|
||||
select: { id: true, name: true, phone: true, orgId: true, idCardNumber: true },
|
||||
})
|
||||
if (!employee) {
|
||||
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在或已离职' } })
|
||||
@@ -300,7 +307,7 @@ router.post('/remind', async (req: AuthRequest, res: Response, next: NextFunctio
|
||||
)
|
||||
const url = `${process.env.PORTAL_BASE_URL || ''}/portal/auto-login?token=${token}&redirect=/portal/esign`
|
||||
await auditLog(req, 'REMIND', 'ESIGN', employeeId, { employeeName: employee.name })
|
||||
res.json({ success: true, data: { url, token, employeeName: employee.name, phone: employee.phone } })
|
||||
res.json({ success: true, data: { url, token, employeeName: employee.name, employeeIdCardNumber: (() => { try { const v = employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(), phone: employee.phone } })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
@@ -413,11 +420,15 @@ router.get('/:id', async (req: AuthRequest, res: Response, next: NextFunction) =
|
||||
const record = await prisma.eSignRecord.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
include: {
|
||||
employee: { select: { id: true, name: true, department: true, phone: true } },
|
||||
employee: { select: { id: true, name: true, department: true, phone: true, idCardNumber: true } },
|
||||
},
|
||||
})
|
||||
if (!record) return res.status(404).json({ success: false, error: { message: '记录不存在' } })
|
||||
res.json({ success: true, data: record })
|
||||
const recordWithIdCard = {
|
||||
...record,
|
||||
employee: record.employee ? { ...record.employee, idCardNumber: (() => { try { const v = record.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })() } : record.employee,
|
||||
}
|
||||
res.json({ success: true, data: recordWithIdCard })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { Router, Response, NextFunction } from 'express'
|
||||
import { authMiddleware, AuthRequest } from '../middleware/auth'
|
||||
import prisma from '../lib/prisma'
|
||||
import { decrypt } from '../lib/crypto'
|
||||
import { parsePagination } from '../lib/pagination'
|
||||
import {
|
||||
createSpecialStatus,
|
||||
@@ -49,7 +50,7 @@ router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: Ne
|
||||
take: pageSize,
|
||||
include: {
|
||||
employee: {
|
||||
select: { id: true, name: true, department: true, phone: true, gender: true, status: true },
|
||||
select: { id: true, name: true, department: true, phone: true, gender: true, status: true, idCardNumber: true },
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -60,6 +61,7 @@ router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: Ne
|
||||
const listWithAlert = list.map((item: any) => ({
|
||||
...item,
|
||||
alertLevel: getAlertLevel(item.reminderDate, item.status),
|
||||
employee: item.employee ? { ...item.employee, idCardNumber: (() => { try { const v = item.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })() } : item.employee,
|
||||
}))
|
||||
|
||||
res.json({
|
||||
@@ -80,7 +82,7 @@ router.get('/:id', authMiddleware, async (req: AuthRequest, res: Response, next:
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
include: {
|
||||
employee: {
|
||||
select: { id: true, name: true, department: true, phone: true, gender: true, hireDate: true },
|
||||
select: { id: true, name: true, department: true, phone: true, gender: true, hireDate: true, idCardNumber: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1072,6 +1072,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
|
||||
type: string
|
||||
title: string
|
||||
employeeName?: string
|
||||
employeeIdCardNumber?: string | null
|
||||
employeeId?: string
|
||||
actionUrl?: string
|
||||
priority: 'high' | 'medium' | 'low'
|
||||
@@ -1091,6 +1092,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
|
||||
type: 'CONTRACT_EXPIRY',
|
||||
title: `${c.employee.name} 合同到期`,
|
||||
employeeName: c.employee.name,
|
||||
employeeIdCardNumber: (() => { try { const v = c.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
employeeId: c.employeeId,
|
||||
actionUrl: '/contracts',
|
||||
priority: 'high',
|
||||
@@ -1114,6 +1116,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
|
||||
type: 'PROBATION_END',
|
||||
title: `${c.employee.name} 试用期到期`,
|
||||
employeeName: c.employee.name,
|
||||
employeeIdCardNumber: (() => { try { const v = c.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
employeeId: c.employeeId,
|
||||
actionUrl: '/roster',
|
||||
priority: 'medium',
|
||||
@@ -1136,6 +1139,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
|
||||
type: 'TERMINATION',
|
||||
title: `${t.employee.name} 离职/解聘日`,
|
||||
employeeName: t.employee.name,
|
||||
employeeIdCardNumber: (() => { try { const v = t.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
employeeId: t.employeeId,
|
||||
actionUrl: '/termination',
|
||||
priority: 'high',
|
||||
@@ -1162,6 +1166,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
|
||||
type: 'ANNIVERSARY',
|
||||
title: `${emp.name} 入职${years}周年`,
|
||||
employeeName: emp.name,
|
||||
employeeIdCardNumber: (() => { try { const v = emp.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
employeeId: emp.id,
|
||||
actionUrl: '/roster',
|
||||
priority: 'low',
|
||||
@@ -1189,6 +1194,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
|
||||
type: 'RISK_DEADLINE',
|
||||
title: `${r.title} 处理截止日`,
|
||||
employeeName: r.employee?.name,
|
||||
employeeIdCardNumber: (() => { try { const v = r.employee?.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
employeeId: r.employeeId || undefined,
|
||||
actionUrl: r.actionUrl || '/',
|
||||
priority: r.level === 'HIGH' ? 'high' : 'medium',
|
||||
@@ -1217,6 +1223,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
|
||||
type: 'RETIREMENT',
|
||||
title: `${emp.name} 达到法定退休年龄`,
|
||||
employeeName: emp.name,
|
||||
employeeIdCardNumber: (() => { try { const v = emp.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
employeeId: emp.id,
|
||||
actionUrl: '/roster',
|
||||
priority: 'high',
|
||||
@@ -1251,7 +1258,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
|
||||
orgId,
|
||||
date: { gte: monthStart, lte: monthEnd },
|
||||
},
|
||||
include: { employee: { select: { name: true } } },
|
||||
include: { employee: { select: { name: true, idCardNumber: true } } },
|
||||
})
|
||||
for (const ev of customEvents) {
|
||||
events.push({
|
||||
@@ -1259,6 +1266,7 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
|
||||
type: ev.type,
|
||||
title: ev.title + (ev.employee ? ` — ${ev.employee.name}` : ''),
|
||||
employeeName: ev.employee?.name,
|
||||
employeeIdCardNumber: (() => { try { const v = ev.employee?.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
actionUrl: '/dashboard',
|
||||
priority: ev.priority as 'high' | 'medium' | 'low',
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import prisma from '../lib/prisma'
|
||||
import { RiskAssessment, TerminationReason } from '@prisma/client'
|
||||
import { autoCreateEsignRecord } from './esign.service'
|
||||
import { decrypt } from '../lib/crypto'
|
||||
|
||||
function dateToMonth(date: Date): string {
|
||||
const y = date.getFullYear()
|
||||
@@ -400,6 +401,7 @@ export function calculateCompensation(hireDate: Date, leaveDate: Date, monthlyWa
|
||||
export interface BatchTerminatePreview {
|
||||
employeeId: string
|
||||
employeeName: string
|
||||
employeeIdCardNumber: string | null
|
||||
department: string
|
||||
reason: string
|
||||
terminationDate: string
|
||||
@@ -423,6 +425,7 @@ export async function batchTerminatePreview(
|
||||
results.push({
|
||||
employeeId: item.employeeId,
|
||||
employeeName: '(未找到)',
|
||||
employeeIdCardNumber: null,
|
||||
department: '',
|
||||
reason: item.reason,
|
||||
terminationDate: item.terminationDate,
|
||||
@@ -437,6 +440,7 @@ export async function batchTerminatePreview(
|
||||
results.push({
|
||||
employeeId: item.employeeId,
|
||||
employeeName: employee.name,
|
||||
employeeIdCardNumber: (() => { try { const v = employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
|
||||
department: employee.department,
|
||||
reason: item.reason,
|
||||
terminationDate: item.terminationDate,
|
||||
|
||||
Reference in New Issue
Block a user