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 || '',
|
actionUrl: t.actionUrl || '',
|
||||||
estimatedLoss: t.estimatedLoss || 0,
|
estimatedLoss: t.estimatedLoss || 0,
|
||||||
deadline: t.deadline || null,
|
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 })
|
res.json({ success: true, data: risks })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -141,12 +141,12 @@ router.get('/check-phone', authMiddleware, async (req: AuthRequest, res, next) =
|
|||||||
// 手机号加密存储,需遍历匹配(量小可接受)
|
// 手机号加密存储,需遍历匹配(量小可接受)
|
||||||
const employees = await prisma.employee.findMany({
|
const employees = await prisma.employee.findMany({
|
||||||
where: { orgId: req.user!.orgId },
|
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 => {
|
const matched = employees.find(e => {
|
||||||
try { return e.phone ? decrypt(e.phone) === phone : false } catch { return false }
|
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) {
|
} catch (err) {
|
||||||
next(err)
|
next(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -699,7 +699,7 @@ export async function getDashboardData(orgId: string) {
|
|||||||
prisma.riskItem.count({ where: { orgId, status: 'PENDING' } }),
|
prisma.riskItem.count({ where: { orgId, status: 'PENDING' } }),
|
||||||
prisma.riskItem.findMany({
|
prisma.riskItem.findMany({
|
||||||
where: { orgId, status: 'PENDING' },
|
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' }],
|
orderBy: [{ level: 'asc' }, { estimatedLoss: 'desc' }],
|
||||||
}),
|
}),
|
||||||
prisma.riskItem.findMany({
|
prisma.riskItem.findMany({
|
||||||
@@ -930,6 +930,7 @@ export async function getDashboardData(orgId: string) {
|
|||||||
employeeName: r.employee?.name || null,
|
employeeName: r.employee?.name || null,
|
||||||
employeeDepartment: r.employee?.department || null,
|
employeeDepartment: r.employee?.department || null,
|
||||||
employeeIdCardHash: r.employee?.idCardHash || 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
|
employeeId: string
|
||||||
/** 员工姓名(用于显示) */
|
/** 员工姓名(用于显示) */
|
||||||
employeeName: string
|
employeeName: string
|
||||||
|
/** 员工身份证号(用于显示) */
|
||||||
|
employeeIdCardNumber?: string
|
||||||
/** 签署场景 */
|
/** 签署场景 */
|
||||||
scene: 'CONTRACT' | 'RESIGNATION' | 'POLICY' | 'PAYSLIP' | 'ONBOARDING'
|
scene: 'CONTRACT' | 'RESIGNATION' | 'POLICY' | 'PAYSLIP' | 'ONBOARDING'
|
||||||
/** 文件标题 */
|
/** 文件标题 */
|
||||||
@@ -48,6 +50,7 @@ export default function SignMethodChoice({
|
|||||||
onClose,
|
onClose,
|
||||||
employeeId,
|
employeeId,
|
||||||
employeeName,
|
employeeName,
|
||||||
|
employeeIdCardNumber,
|
||||||
scene,
|
scene,
|
||||||
documentTitle,
|
documentTitle,
|
||||||
contractId,
|
contractId,
|
||||||
@@ -107,7 +110,7 @@ export default function SignMethodChoice({
|
|||||||
<Modal open={open} onClose={onClose} title={`${actionName}成功 — 选择签署方式`} size="sm">
|
<Modal open={open} onClose={onClose} title={`${actionName}成功 — 选择签署方式`} size="sm">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="text-sm text-gray-600 bg-gray-50 rounded-md p-3">
|
<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 className="text-xs text-gray-400 mt-1">{documentTitle}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ function AddEmployeeModal({ open, onClose, onSubmit, loading, error }: {
|
|||||||
{phoneDuplicate?.exists && (
|
{phoneDuplicate?.exists && (
|
||||||
<div className="px-3 py-2 rounded-md bg-amber-50 text-amber-700 text-xs flex items-center gap-2">
|
<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" />
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export default function Roster() {
|
|||||||
open: boolean
|
open: boolean
|
||||||
employeeId: string
|
employeeId: string
|
||||||
employeeName: string
|
employeeName: string
|
||||||
|
employeeIdCardNumber?: string
|
||||||
scene: 'CONTRACT' | 'RESIGNATION' | 'POLICY' | 'PAYSLIP' | 'ONBOARDING'
|
scene: 'CONTRACT' | 'RESIGNATION' | 'POLICY' | 'PAYSLIP' | 'ONBOARDING'
|
||||||
documentTitle: string
|
documentTitle: string
|
||||||
contractId?: string
|
contractId?: string
|
||||||
@@ -226,6 +227,7 @@ export default function Roster() {
|
|||||||
open: true,
|
open: true,
|
||||||
employeeId: rehireEmployee?.id || '',
|
employeeId: rehireEmployee?.id || '',
|
||||||
employeeName: rehireEmployee?.name || '',
|
employeeName: rehireEmployee?.name || '',
|
||||||
|
employeeIdCardNumber: rehireEmployee?.idCardNumber,
|
||||||
scene: 'CONTRACT',
|
scene: 'CONTRACT',
|
||||||
documentTitle: `${rehireEmployee?.name || ''}的劳动合同`,
|
documentTitle: `${rehireEmployee?.name || ''}的劳动合同`,
|
||||||
remark: '重新入职时发起',
|
remark: '重新入职时发起',
|
||||||
@@ -247,6 +249,7 @@ export default function Roster() {
|
|||||||
open: true,
|
open: true,
|
||||||
employeeId: salaryEmployee?.id || '',
|
employeeId: salaryEmployee?.id || '',
|
||||||
employeeName: salaryEmployee?.name || '',
|
employeeName: salaryEmployee?.name || '',
|
||||||
|
employeeIdCardNumber: salaryEmployee?.idCardNumber,
|
||||||
scene: 'POLICY',
|
scene: 'POLICY',
|
||||||
documentTitle: `${salaryEmployee?.name || ''}的薪酬调整确认书`,
|
documentTitle: `${salaryEmployee?.name || ''}的薪酬调整确认书`,
|
||||||
remark: '薪酬变更时发起',
|
remark: '薪酬变更时发起',
|
||||||
@@ -268,6 +271,7 @@ export default function Roster() {
|
|||||||
open: true,
|
open: true,
|
||||||
employeeId: deptEmployee?.id || '',
|
employeeId: deptEmployee?.id || '',
|
||||||
employeeName: deptEmployee?.name || '',
|
employeeName: deptEmployee?.name || '',
|
||||||
|
employeeIdCardNumber: deptEmployee?.idCardNumber,
|
||||||
scene: 'POLICY',
|
scene: 'POLICY',
|
||||||
documentTitle: `${deptEmployee?.name || ''}的调岗确认书`,
|
documentTitle: `${deptEmployee?.name || ''}的调岗确认书`,
|
||||||
remark: '部门/岗位调动时发起',
|
remark: '部门/岗位调动时发起',
|
||||||
@@ -294,6 +298,7 @@ export default function Roster() {
|
|||||||
open: true,
|
open: true,
|
||||||
employeeId: variables.employeeId,
|
employeeId: variables.employeeId,
|
||||||
employeeName: confirmEmployee?.name || '',
|
employeeName: confirmEmployee?.name || '',
|
||||||
|
employeeIdCardNumber: confirmEmployee?.idCardNumber,
|
||||||
scene: 'POLICY',
|
scene: 'POLICY',
|
||||||
documentTitle: `${confirmEmployee?.name || ''}的转正薪酬调整确认书`,
|
documentTitle: `${confirmEmployee?.name || ''}的转正薪酬调整确认书`,
|
||||||
remark: `转正薪资调整:¥${originalSalary || 0} → ¥${newSalary}`,
|
remark: `转正薪资调整:¥${originalSalary || 0} → ¥${newSalary}`,
|
||||||
@@ -404,6 +409,7 @@ export default function Roster() {
|
|||||||
open: true,
|
open: true,
|
||||||
employeeId: renewEmployee?.id || '',
|
employeeId: renewEmployee?.id || '',
|
||||||
employeeName: renewEmployee?.name || '',
|
employeeName: renewEmployee?.name || '',
|
||||||
|
employeeIdCardNumber: renewEmployee?.idCardNumber,
|
||||||
scene: 'CONTRACT',
|
scene: 'CONTRACT',
|
||||||
documentTitle: `${renewEmployee?.name || ''}的续签劳动合同`,
|
documentTitle: `${renewEmployee?.name || ''}的续签劳动合同`,
|
||||||
remark: '合同续签时发起',
|
remark: '合同续签时发起',
|
||||||
@@ -1186,7 +1192,7 @@ export default function Roster() {
|
|||||||
|
|
||||||
{/* 开具证明弹窗 */}
|
{/* 开具证明弹窗 */}
|
||||||
{showCertModal && certEmployee && (
|
{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 className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<Label>证明类型</Label>
|
<Label>证明类型</Label>
|
||||||
@@ -1228,7 +1234,7 @@ export default function Roster() {
|
|||||||
|
|
||||||
{/* 续签合同弹窗 */}
|
{/* 续签合同弹窗 */}
|
||||||
{showRenewModal && renewEmployee && (
|
{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="space-y-3">
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
原合同到期日:{renewEmployee.latestContract?.endDate ? new Date(renewEmployee.latestContract.endDate).toISOString().slice(0, 10) : '无'}
|
原合同到期日:{renewEmployee.latestContract?.endDate ? new Date(renewEmployee.latestContract.endDate).toISOString().slice(0, 10) : '无'}
|
||||||
@@ -1523,6 +1529,7 @@ export default function Roster() {
|
|||||||
onClose={() => setSignChoice(null)}
|
onClose={() => setSignChoice(null)}
|
||||||
employeeId={signChoice.employeeId}
|
employeeId={signChoice.employeeId}
|
||||||
employeeName={signChoice.employeeName}
|
employeeName={signChoice.employeeName}
|
||||||
|
employeeIdCardNumber={signChoice.employeeIdCardNumber}
|
||||||
scene={signChoice.scene}
|
scene={signChoice.scene}
|
||||||
documentTitle={signChoice.documentTitle}
|
documentTitle={signChoice.documentTitle}
|
||||||
contractId={signChoice.contractId}
|
contractId={signChoice.contractId}
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ export default function Termination() {
|
|||||||
id: string
|
id: string
|
||||||
employeeId: string
|
employeeId: string
|
||||||
name: string
|
name: string
|
||||||
|
idCardNumber?: string
|
||||||
department: string
|
department: string
|
||||||
reason: string
|
reason: string
|
||||||
reasonLabel: string
|
reasonLabel: string
|
||||||
@@ -758,6 +759,7 @@ export default function Termination() {
|
|||||||
id: `saved-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
id: `saved-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||||
employeeId,
|
employeeId,
|
||||||
name: selectedEmployee.name,
|
name: selectedEmployee.name,
|
||||||
|
idCardNumber: selectedEmployee.idCardNumber,
|
||||||
department: selectedEmployee.department,
|
department: selectedEmployee.department,
|
||||||
reason,
|
reason,
|
||||||
reasonLabel,
|
reasonLabel,
|
||||||
@@ -1284,13 +1286,13 @@ ${items}
|
|||||||
<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((emp) => (
|
{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>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
{selectedEmployee && (
|
{selectedEmployee && (
|
||||||
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
|
<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>入职日期:{selectedEmployee.hireDate?.toString().slice(0, 10)}</div>
|
||||||
<div>月工资:¥{fmt(selectedEmployee.monthlySalary)}</div>
|
<div>月工资:¥{fmt(selectedEmployee.monthlySalary)}</div>
|
||||||
{selectedEmployee.hasTermination && selectedEmployee.latestTerminationStatus !== 'CANCELLED' && selectedEmployee.latestTerminationStatus !== 'COMPLETED' && (
|
{selectedEmployee.hasTermination && selectedEmployee.latestTerminationStatus !== 'CANCELLED' && selectedEmployee.latestTerminationStatus !== 'COMPLETED' && (
|
||||||
@@ -1667,7 +1669,7 @@ ${items}
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* 员工概况 */}
|
{/* 员工概况 */}
|
||||||
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
|
<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>工作年限:{costResult.years}年{costResult.remainingMonths}个月</div>
|
||||||
<div>月工资:¥{fmt(costResult.wage)}/月</div>
|
<div>月工资:¥{fmt(costResult.wage)}/月</div>
|
||||||
{costResult.capped && (
|
{costResult.capped && (
|
||||||
@@ -1988,7 +1990,7 @@ ${items}
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* 汇总信息 */}
|
{/* 汇总信息 */}
|
||||||
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
|
<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>解聘原因:{reasonLabel}</div>
|
||||||
<div>解聘日期:{terminationDate}</div>
|
<div>解聘日期:{terminationDate}</div>
|
||||||
<div>社保截止:{socialInsEndMonth || terminationDate.slice(0, 7)}</div>
|
<div>社保截止:{socialInsEndMonth || terminationDate.slice(0, 7)}</div>
|
||||||
@@ -2100,7 +2102,7 @@ ${items}
|
|||||||
<h2 className="text-base font-bold">解除劳动合同通知书</h2>
|
<h2 className="text-base font-bold">解除劳动合同通知书</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-700 space-y-3">
|
<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>
|
<p>
|
||||||
您于 <strong>{selectedEmployee?.hireDate?.toString().slice(0, 10)}</strong> 入职我公司{selectedEmployee?.department}部门。
|
您于 <strong>{selectedEmployee?.hireDate?.toString().slice(0, 10)}</strong> 入职我公司{selectedEmployee?.department}部门。
|
||||||
因 <strong>{reasonLabel}</strong> 原因,公司决定于 <strong>{terminationDate}</strong> 起解除与您的劳动合同。
|
因 <strong>{reasonLabel}</strong> 原因,公司决定于 <strong>{terminationDate}</strong> 起解除与您的劳动合同。
|
||||||
@@ -2293,7 +2295,7 @@ ${items}
|
|||||||
savedItems.forEach((item, i) => {
|
savedItems.forEach((item, i) => {
|
||||||
if (y > 270) { doc.addPage(); y = 20 }
|
if (y > 270) { doc.addPage(); y = 20 }
|
||||||
doc.setFontSize(11)
|
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
|
y += 6
|
||||||
doc.setFontSize(9)
|
doc.setFontSize(9)
|
||||||
doc.text(`解聘原因: ${item.reason || '-'}`, 20, y); y += 5
|
doc.text(`解聘原因: ${item.reason || '-'}`, 20, y); y += 5
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ export default function RiskCenter() {
|
|||||||
{r.employee && (
|
{r.employee && (
|
||||||
<div className="text-xs text-gray-400 mt-0.5">
|
<div className="text-xs text-gray-400 mt-0.5">
|
||||||
{r.employee.name} · {r.employee.department || ''}
|
{r.employee.name} · {r.employee.department || ''}
|
||||||
|
{r.employee.idCardNumber && <span className="ml-1 font-mono">{r.employee.idCardNumber}</span>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user