fix: 非列表页面姓名显示处也加上身份证号
后端: - employee.routes.ts: checkPhone 返回 idCardNumber - risk.service.ts: 风险项查询加上 idCardNumber 并解密 - dashboard.routes.ts: 风险中心返回 employeeIdCardNumber 前端: - Termination.tsx: 离职管理所有姓名显示处加上身份证号(员工概况、通知书、PDF导出、下拉选项) - Roster.tsx: 弹窗标题加上身份证号,signChoice 传递 idCardNumber - SignMethodChoice.tsx: 签署方式弹窗显示身份证号 - Contracts.tsx: 手机查重提示加上身份证号 - RiskCenter.tsx: 风险卡片员工姓名后显示身份证号
This commit is contained in:
@@ -546,7 +546,7 @@ router.get('/risks', authMiddleware, async (req: AuthRequest, res: Response, nex
|
||||
actionUrl: t.actionUrl || '',
|
||||
estimatedLoss: t.estimatedLoss || 0,
|
||||
deadline: t.deadline || null,
|
||||
employee: t.employeeName ? { name: t.employeeName, department: t.employeeDepartment || '' } : null,
|
||||
employee: t.employeeName ? { name: t.employeeName, department: t.employeeDepartment || '', idCardNumber: t.employeeIdCardNumber || null } : null,
|
||||
}))
|
||||
res.json({ success: true, data: risks })
|
||||
} catch (err) {
|
||||
|
||||
@@ -141,12 +141,12 @@ router.get('/check-phone', authMiddleware, async (req: AuthRequest, res, next) =
|
||||
// 手机号加密存储,需遍历匹配(量小可接受)
|
||||
const employees = await prisma.employee.findMany({
|
||||
where: { orgId: req.user!.orgId },
|
||||
select: { id: true, name: true, department: true, status: true, phone: true },
|
||||
select: { id: true, name: true, department: true, status: true, phone: true, idCardNumber: true },
|
||||
})
|
||||
const matched = employees.find(e => {
|
||||
try { return e.phone ? decrypt(e.phone) === phone : false } catch { return false }
|
||||
})
|
||||
res.json({ success: true, data: { exists: !!matched, employee: matched ? { id: matched.id, name: matched.name, department: matched.department, status: matched.status } : undefined } })
|
||||
res.json({ success: true, data: { exists: !!matched, employee: matched ? { id: matched.id, name: matched.name, department: matched.department, status: matched.status, idCardNumber: (() => { try { return matched.idCardNumber ? (matched.idCardNumber.includes(':') ? decrypt(matched.idCardNumber) : matched.idCardNumber) : null } catch { return null } })() } : undefined } })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
|
||||
@@ -699,7 +699,7 @@ export async function getDashboardData(orgId: string) {
|
||||
prisma.riskItem.count({ where: { orgId, status: 'PENDING' } }),
|
||||
prisma.riskItem.findMany({
|
||||
where: { orgId, status: 'PENDING' },
|
||||
include: { employee: { select: { id: true, name: true, department: true, idCardHash: true } } },
|
||||
include: { employee: { select: { id: true, name: true, department: true, idCardHash: true, idCardNumber: true } } },
|
||||
orderBy: [{ level: 'asc' }, { estimatedLoss: 'desc' }],
|
||||
}),
|
||||
prisma.riskItem.findMany({
|
||||
@@ -930,6 +930,7 @@ export async function getDashboardData(orgId: string) {
|
||||
employeeName: r.employee?.name || null,
|
||||
employeeDepartment: r.employee?.department || null,
|
||||
employeeIdCardHash: r.employee?.idCardHash || null,
|
||||
employeeIdCardNumber: r.employee?.idCardNumber ? (() => { try { return r.employee.idCardNumber.includes(':') ? decrypt(r.employee.idCardNumber) : r.employee.idCardNumber } catch { return null } })() : null,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ interface SignMethodChoiceProps {
|
||||
employeeId: string
|
||||
/** 员工姓名(用于显示) */
|
||||
employeeName: string
|
||||
/** 员工身份证号(用于显示) */
|
||||
employeeIdCardNumber?: string
|
||||
/** 签署场景 */
|
||||
scene: 'CONTRACT' | 'RESIGNATION' | 'POLICY' | 'PAYSLIP' | 'ONBOARDING'
|
||||
/** 文件标题 */
|
||||
@@ -48,6 +50,7 @@ export default function SignMethodChoice({
|
||||
onClose,
|
||||
employeeId,
|
||||
employeeName,
|
||||
employeeIdCardNumber,
|
||||
scene,
|
||||
documentTitle,
|
||||
contractId,
|
||||
@@ -107,7 +110,7 @@ export default function SignMethodChoice({
|
||||
<Modal open={open} onClose={onClose} title={`${actionName}成功 — 选择签署方式`} size="sm">
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm text-gray-600 bg-gray-50 rounded-md p-3">
|
||||
<div className="font-medium text-gray-700">{employeeName} · {SCENE_LABELS[scene] || scene}</div>
|
||||
<div className="font-medium text-gray-700">{employeeName} · {SCENE_LABELS[scene] || scene}{employeeIdCardNumber && <span className="ml-1 text-gray-400 font-mono text-xs">{employeeIdCardNumber}</span>}</div>
|
||||
<div className="text-xs text-gray-400 mt-1">{documentTitle}</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ function AddEmployeeModal({ open, onClose, onSubmit, loading, error }: {
|
||||
{phoneDuplicate?.exists && (
|
||||
<div className="px-3 py-2 rounded-md bg-amber-50 text-amber-700 text-xs flex items-center gap-2">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0" />
|
||||
<span>该手机号已存在:{phoneDuplicate.employee?.name}({phoneDuplicate.employee?.department}),请确认是否重复录入</span>
|
||||
<span>该手机号已存在:{phoneDuplicate.employee?.name}({phoneDuplicate.employee?.department}){phoneDuplicate.employee?.idCardNumber && `(${phoneDuplicate.employee.idCardNumber})` },请确认是否重复录入</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ export default function Roster() {
|
||||
open: boolean
|
||||
employeeId: string
|
||||
employeeName: string
|
||||
employeeIdCardNumber?: string
|
||||
scene: 'CONTRACT' | 'RESIGNATION' | 'POLICY' | 'PAYSLIP' | 'ONBOARDING'
|
||||
documentTitle: string
|
||||
contractId?: string
|
||||
@@ -226,6 +227,7 @@ export default function Roster() {
|
||||
open: true,
|
||||
employeeId: rehireEmployee?.id || '',
|
||||
employeeName: rehireEmployee?.name || '',
|
||||
employeeIdCardNumber: rehireEmployee?.idCardNumber,
|
||||
scene: 'CONTRACT',
|
||||
documentTitle: `${rehireEmployee?.name || ''}的劳动合同`,
|
||||
remark: '重新入职时发起',
|
||||
@@ -247,6 +249,7 @@ export default function Roster() {
|
||||
open: true,
|
||||
employeeId: salaryEmployee?.id || '',
|
||||
employeeName: salaryEmployee?.name || '',
|
||||
employeeIdCardNumber: salaryEmployee?.idCardNumber,
|
||||
scene: 'POLICY',
|
||||
documentTitle: `${salaryEmployee?.name || ''}的薪酬调整确认书`,
|
||||
remark: '薪酬变更时发起',
|
||||
@@ -268,6 +271,7 @@ export default function Roster() {
|
||||
open: true,
|
||||
employeeId: deptEmployee?.id || '',
|
||||
employeeName: deptEmployee?.name || '',
|
||||
employeeIdCardNumber: deptEmployee?.idCardNumber,
|
||||
scene: 'POLICY',
|
||||
documentTitle: `${deptEmployee?.name || ''}的调岗确认书`,
|
||||
remark: '部门/岗位调动时发起',
|
||||
@@ -294,6 +298,7 @@ export default function Roster() {
|
||||
open: true,
|
||||
employeeId: variables.employeeId,
|
||||
employeeName: confirmEmployee?.name || '',
|
||||
employeeIdCardNumber: confirmEmployee?.idCardNumber,
|
||||
scene: 'POLICY',
|
||||
documentTitle: `${confirmEmployee?.name || ''}的转正薪酬调整确认书`,
|
||||
remark: `转正薪资调整:¥${originalSalary || 0} → ¥${newSalary}`,
|
||||
@@ -404,6 +409,7 @@ export default function Roster() {
|
||||
open: true,
|
||||
employeeId: renewEmployee?.id || '',
|
||||
employeeName: renewEmployee?.name || '',
|
||||
employeeIdCardNumber: renewEmployee?.idCardNumber,
|
||||
scene: 'CONTRACT',
|
||||
documentTitle: `${renewEmployee?.name || ''}的续签劳动合同`,
|
||||
remark: '合同续签时发起',
|
||||
@@ -1186,7 +1192,7 @@ export default function Roster() {
|
||||
|
||||
{/* 开具证明弹窗 */}
|
||||
{showCertModal && certEmployee && (
|
||||
<Modal open={showCertModal} onClose={() => { setShowCertModal(false); setCertEmployee(null) }} title={`为 ${certEmployee.name} 开具证明`}>
|
||||
<Modal open={showCertModal} onClose={() => { setShowCertModal(false); setCertEmployee(null) }} title={`为 ${certEmployee.name}${certEmployee.idCardNumber ? `(${certEmployee.idCardNumber})` : ''} 开具证明`}>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label>证明类型</Label>
|
||||
@@ -1228,7 +1234,7 @@ export default function Roster() {
|
||||
|
||||
{/* 续签合同弹窗 */}
|
||||
{showRenewModal && renewEmployee && (
|
||||
<Modal open={showRenewModal} onClose={() => { setShowRenewModal(false); setRenewEmployee(null) }} title={`为 ${renewEmployee.name} 续签合同`}>
|
||||
<Modal open={showRenewModal} onClose={() => { setShowRenewModal(false); setRenewEmployee(null) }} title={`为 ${renewEmployee.name}${renewEmployee.idCardNumber ? `(${renewEmployee.idCardNumber})` : ''} 续签合同`}>
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs text-gray-500">
|
||||
原合同到期日:{renewEmployee.latestContract?.endDate ? new Date(renewEmployee.latestContract.endDate).toISOString().slice(0, 10) : '无'}
|
||||
@@ -1523,6 +1529,7 @@ export default function Roster() {
|
||||
onClose={() => setSignChoice(null)}
|
||||
employeeId={signChoice.employeeId}
|
||||
employeeName={signChoice.employeeName}
|
||||
employeeIdCardNumber={signChoice.employeeIdCardNumber}
|
||||
scene={signChoice.scene}
|
||||
documentTitle={signChoice.documentTitle}
|
||||
contractId={signChoice.contractId}
|
||||
|
||||
@@ -230,6 +230,7 @@ export default function Termination() {
|
||||
id: string
|
||||
employeeId: string
|
||||
name: string
|
||||
idCardNumber?: string
|
||||
department: string
|
||||
reason: string
|
||||
reasonLabel: string
|
||||
@@ -758,6 +759,7 @@ export default function Termination() {
|
||||
id: `saved-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
employeeId,
|
||||
name: selectedEmployee.name,
|
||||
idCardNumber: selectedEmployee.idCardNumber,
|
||||
department: selectedEmployee.department,
|
||||
reason,
|
||||
reasonLabel,
|
||||
@@ -1284,13 +1286,13 @@ ${items}
|
||||
<Select value={employeeId} onChange={(e) => setEmployeeId(e.target.value)}>
|
||||
<option value="">请选择</option>
|
||||
{employees?.map((emp) => (
|
||||
<option key={emp.id} value={emp.id}>{emp.name} - {emp.department}</option>
|
||||
<option key={emp.id} value={emp.id}>{emp.name} - {emp.department}{emp.idCardNumber ? ` (${emp.idCardNumber})` : ''}</option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
{selectedEmployee && (
|
||||
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
|
||||
<div className="font-medium">{selectedEmployee.name}({selectedEmployee.department})</div>
|
||||
<div className="font-medium">{selectedEmployee.name}({selectedEmployee.department}){selectedEmployee.idCardNumber && <span className="text-gray-400 font-mono ml-1">{selectedEmployee.idCardNumber}</span>}</div>
|
||||
<div>入职日期:{selectedEmployee.hireDate?.toString().slice(0, 10)}</div>
|
||||
<div>月工资:¥{fmt(selectedEmployee.monthlySalary)}</div>
|
||||
{selectedEmployee.hasTermination && selectedEmployee.latestTerminationStatus !== 'CANCELLED' && selectedEmployee.latestTerminationStatus !== 'COMPLETED' && (
|
||||
@@ -1667,7 +1669,7 @@ ${items}
|
||||
<div className="space-y-3">
|
||||
{/* 员工概况 */}
|
||||
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
|
||||
<div className="font-medium">{selectedEmployee?.name}({selectedEmployee?.department})</div>
|
||||
<div className="font-medium">{selectedEmployee?.name}({selectedEmployee?.department}){selectedEmployee?.idCardNumber && <span className="text-gray-400 font-mono ml-1">{selectedEmployee.idCardNumber}</span>}</div>
|
||||
<div>工作年限:{costResult.years}年{costResult.remainingMonths}个月</div>
|
||||
<div>月工资:¥{fmt(costResult.wage)}/月</div>
|
||||
{costResult.capped && (
|
||||
@@ -1988,7 +1990,7 @@ ${items}
|
||||
<div className="space-y-3">
|
||||
{/* 汇总信息 */}
|
||||
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
|
||||
<div className="font-medium">{selectedEmployee?.name}({selectedEmployee?.department})</div>
|
||||
<div className="font-medium">{selectedEmployee?.name}({selectedEmployee?.department}){selectedEmployee?.idCardNumber && <span className="text-gray-400 font-mono ml-1">{selectedEmployee.idCardNumber}</span>}</div>
|
||||
<div>解聘原因:{reasonLabel}</div>
|
||||
<div>解聘日期:{terminationDate}</div>
|
||||
<div>社保截止:{socialInsEndMonth || terminationDate.slice(0, 7)}</div>
|
||||
@@ -2100,7 +2102,7 @@ ${items}
|
||||
<h2 className="text-base font-bold">解除劳动合同通知书</h2>
|
||||
</div>
|
||||
<div className="text-xs text-gray-700 space-y-3">
|
||||
<p><strong>{selectedEmployee?.name}</strong> 先生/女士:</p>
|
||||
<p><strong>{selectedEmployee?.name}</strong> 先生/女士(证件号码:{selectedEmployee?.idCardNumber || '—'}):</p>
|
||||
<p>
|
||||
您于 <strong>{selectedEmployee?.hireDate?.toString().slice(0, 10)}</strong> 入职我公司{selectedEmployee?.department}部门。
|
||||
因 <strong>{reasonLabel}</strong> 原因,公司决定于 <strong>{terminationDate}</strong> 起解除与您的劳动合同。
|
||||
@@ -2293,7 +2295,7 @@ ${items}
|
||||
savedItems.forEach((item, i) => {
|
||||
if (y > 270) { doc.addPage(); y = 20 }
|
||||
doc.setFontSize(11)
|
||||
doc.text(`${i + 1}. ${item.name} (${item.department})`, 14, y)
|
||||
doc.text(`${i + 1}. ${item.name} (${item.department}) ${item.idCardNumber || ''}`, 14, y)
|
||||
y += 6
|
||||
doc.setFontSize(9)
|
||||
doc.text(`解聘原因: ${item.reason || '-'}`, 20, y); y += 5
|
||||
|
||||
@@ -282,6 +282,7 @@ export default function RiskCenter() {
|
||||
{r.employee && (
|
||||
<div className="text-xs text-gray-400 mt-0.5">
|
||||
{r.employee.name} · {r.employee.department || ''}
|
||||
{r.employee.idCardNumber && <span className="ml-1 font-mono">{r.employee.idCardNumber}</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user