fix: 提成奖金、证据链条、风险中心姓名显示身份证号

后端:
- commission-bonus.service.ts: listByMonth 查询加上 idCardNumber 并解密
- evidence.service.ts: getEvidenceList 加上 idCardNumber 并解密返回 employeeIdCardNumber
- roster.routes.ts: 证据链接口返回 employee.idCardNumber,Excel 导出加上身份证号

前端:
- CommissionBonus.tsx: 列表姓名下显示身份证号,下拉选项和编辑弹窗也加上
- EvidenceChain.tsx: 两处姓名显示加上身份证号,文本导出加上身份证号
- Evidence.tsx: 证据链列表页姓名后显示身份证号
This commit is contained in:
freedakgmail
2026-08-18 08:03:09 +08:00
parent 5db53ed591
commit 4d78eac5c2
6 changed files with 23 additions and 8 deletions
+2
View File
@@ -638,6 +638,7 @@ router.get('/:id/evidence-chain', authMiddleware, async (req: AuthRequest, res,
status: employee.terminations.some((t) => t.status === 'COMPLETED' && t.terminationDate <= new Date()) ? 'RESIGNED' : 'ACTIVE', status: employee.terminations.some((t) => t.status === 'COMPLETED' && t.terminationDate <= new Date()) ? 'RESIGNED' : 'ACTIVE',
gender: employee.gender, gender: employee.gender,
phone: employee.phone, phone: employee.phone,
idCardNumber: safeDecryptStr(employee.idCardNumber),
}, },
evidence, evidence,
risks, risks,
@@ -691,6 +692,7 @@ router.get('/:id/evidence-chain/export', authMiddleware, async (req: AuthRequest
const empStatus = employee.terminations.some((t) => t.status === 'COMPLETED' && t.terminationDate <= today) ? '离职' : '在职' const empStatus = employee.terminations.some((t) => t.status === 'COMPLETED' && t.terminationDate <= today) ? '离职' : '在职'
wsInfo.addRows([ wsInfo.addRows([
{ label: '姓名', value: empName }, { label: '姓名', value: empName },
{ label: '身份证号', value: safeDecryptStr(employee.idCardNumber) || '' },
{ label: '部门', value: empDept }, { label: '部门', value: empDept },
{ label: '入职日期', value: hireDate }, { label: '入职日期', value: hireDate },
{ label: '状态', value: empStatus }, { label: '状态', value: empStatus },
@@ -13,12 +13,18 @@ export async function listByMonth(orgId: string, month: string) {
where: { orgId, month }, where: { orgId, month },
include: { include: {
employee: { employee: {
select: { id: true, name: true, department: true, idCardHash: true }, select: { id: true, name: true, department: true, idCardHash: true, idCardNumber: true },
}, },
}, },
orderBy: [{ employee: { department: 'asc' } }, { employee: { name: 'asc' } }], orderBy: [{ employee: { department: 'asc' } }, { employee: { name: 'asc' } }],
}) })
return records return records.map(r => ({
...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,
}))
} }
/** /**
+3 -2
View File
@@ -1,5 +1,5 @@
import prisma from '../lib/prisma' import prisma from '../lib/prisma'
import { sha256 } from '../lib/crypto' import { sha256, decrypt } from '../lib/crypto'
/** /**
* 递归排序对象/数组中所有 key,确保 JSON.stringify 结果一致 * 递归排序对象/数组中所有 key,确保 JSON.stringify 结果一致
@@ -165,7 +165,7 @@ export async function getEvidenceList(orgId: string, category?: string, page: nu
prisma.evidenceChain.count({ where }), prisma.evidenceChain.count({ where }),
prisma.evidenceChain.findMany({ prisma.evidenceChain.findMany({
where, where,
include: { employee: { select: { id: true, name: true, department: true } } }, include: { employee: { select: { id: true, name: true, department: true, idCardNumber: true } } },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize, skip: (page - 1) * pageSize,
take: pageSize, take: pageSize,
@@ -187,6 +187,7 @@ export async function getEvidenceList(orgId: string, category?: string, page: nu
employeeId: r.employeeId, employeeId: r.employeeId,
employeeName: r.employee?.name || null, employeeName: r.employee?.name || null,
employeeDept: r.employee?.department || null, employeeDept: r.employee?.department || null,
employeeIdCardNumber: (() => { try { const v = r.employee?.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
eventCount: events.length, eventCount: events.length,
firstAction: firstEvent?.action || null, firstAction: firstEvent?.action || null,
lastAction: lastEvent?.action || null, lastAction: lastEvent?.action || null,
+6 -3
View File
@@ -171,7 +171,10 @@ export default function CommissionBonus() {
<tr><td colSpan={5} className="px-4 py-8 text-center text-gray-400">{month} </td></tr> <tr><td colSpan={5} className="px-4 py-8 text-center text-gray-400">{month} </td></tr>
) : paged.map((r: any) => ( ) : paged.map((r: any) => (
<tr key={r.id} className="hover:bg-gray-50"> <tr key={r.id} className="hover:bg-gray-50">
<td className="px-4 py-2.5 text-sm text-gray-900">{r.employee?.name || '-'}</td> <td className="px-4 py-2.5 text-sm text-gray-900">
{r.employee?.name || '-'}
{r.employee?.idCardNumber && <div className="text-xs text-gray-400 font-mono">{r.employee.idCardNumber}</div>}
</td>
<td className="px-4 py-2.5 text-sm text-gray-500">{r.employee?.department || '-'}</td> <td className="px-4 py-2.5 text-sm text-gray-500">{r.employee?.department || '-'}</td>
<td className={`px-4 py-2.5 text-sm text-right font-medium ${r.amount >= 0 ? 'text-safe' : 'text-danger'}`}> <td className={`px-4 py-2.5 text-sm text-right font-medium ${r.amount >= 0 ? 'text-safe' : 'text-danger'}`}>
{r.amount >= 0 ? '+' : ''}{fmt(r.amount)} {r.amount >= 0 ? '+' : ''}{fmt(r.amount)}
@@ -241,7 +244,7 @@ function AddModal({ employees, month, onClose, onSubmit, saving }: {
<Select value={employeeId} onChange={(e) => setEmployeeId(e.target.value)}> <Select value={employeeId} onChange={(e) => setEmployeeId(e.target.value)}>
<option value=""></option> <option value=""></option>
{employees.map((e: any) => ( {employees.map((e: any) => (
<option key={e.id} value={e.id}>{e.name}{e.department}</option> <option key={e.id} value={e.id}>{e.name}{e.department}{e.idCardNumber ? ` ${e.idCardNumber}` : ''}</option>
))} ))}
</Select> </Select>
</div> </div>
@@ -281,7 +284,7 @@ function EditModal({ record, onClose, onSubmit, saving }: {
const [remark, setRemark] = useState(record.remark || '') const [remark, setRemark] = useState(record.remark || '')
return ( return (
<Modal open onClose={onClose} title={`编辑 - ${record.employee?.name || ''}`} size="md"> <Modal open onClose={onClose} title={`编辑 - ${record.employee?.name || ''}${record.employee?.idCardNumber ? `${record.employee.idCardNumber}` : ''}`} size="md">
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<Label> *==</Label> <Label> *==</Label>
+1 -1
View File
@@ -121,7 +121,7 @@ export default function Evidence() {
</div> </div>
{item.employeeName && ( {item.employeeName && (
<div className="text-xs text-gray-500 mt-0.5"> <div className="text-xs text-gray-500 mt-0.5">
{item.employeeName}{item.employeeDept ? ` · ${item.employeeDept}` : ''} {item.employeeName}{item.employeeDept ? ` · ${item.employeeDept}` : ''}{item.employeeIdCardNumber && <span className="ml-1 font-mono text-gray-400">{item.employeeIdCardNumber}</span>}
</div> </div>
)} )}
<div className="text-xs text-gray-400 mt-0.5 flex items-center gap-2"> <div className="text-xs text-gray-400 mt-0.5 flex items-center gap-2">
@@ -48,6 +48,7 @@ export default function EvidenceChain({ employeeId }: { employeeId: string }) {
<h2 className="text-xs font-medium flex items-center gap-2"><Scale className="w-4 h-4" /></h2> <h2 className="text-xs font-medium flex items-center gap-2"><Scale className="w-4 h-4" /></h2>
<div className="text-xs text-gray-500 mt-1"> <div className="text-xs text-gray-500 mt-1">
{data.employee?.name || '未知'} · {data.employee?.department || '未知'} · {data.employee?.hireDate ? data.employee.hireDate.toString().slice(0, 10) : '未知'} {data.employee?.name || '未知'} · {data.employee?.department || '未知'} · {data.employee?.hireDate ? data.employee.hireDate.toString().slice(0, 10) : '未知'}
{data.employee?.idCardNumber && <span className="ml-1 font-mono text-gray-400">{data.employee.idCardNumber}</span>}
</div> </div>
</div> </div>
</div> </div>
@@ -104,6 +105,7 @@ export default function EvidenceChain({ employeeId }: { employeeId: string }) {
<h2 className="text-xs font-medium flex items-center gap-2"><Scale className="w-4 h-4" /></h2> <h2 className="text-xs font-medium flex items-center gap-2"><Scale className="w-4 h-4" /></h2>
<div className="text-xs text-gray-500 mt-1"> <div className="text-xs text-gray-500 mt-1">
{data.employee.name} · {data.employee.department} · {data.employee.hireDate} {data.employee.name} · {data.employee.department} · {data.employee.hireDate}
{data.employee.idCardNumber && <span className="ml-1 font-mono text-gray-400">{data.employee.idCardNumber}</span>}
</div> </div>
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
@@ -228,6 +230,7 @@ export function generateEvidenceText(data: any): string {
lines.push('========================================') lines.push('========================================')
lines.push('') lines.push('')
lines.push(`员工姓名:${data.employee.name}`) lines.push(`员工姓名:${data.employee.name}`)
if (data.employee.idCardNumber) lines.push(`身份证号:${data.employee.idCardNumber}`)
lines.push(`部门:${data.employee.department}`) lines.push(`部门:${data.employee.department}`)
lines.push(`入职日期:${data.employee.hireDate}`) lines.push(`入职日期:${data.employee.hireDate}`)
lines.push(`状态:${data.employee.status === 'ACTIVE' ? '在职' : '离职'}`) lines.push(`状态:${data.employee.status === 'ACTIVE' ? '在职' : '离职'}`)