diff --git a/backend/src/routes/employee.routes.ts b/backend/src/routes/employee.routes.ts index e45b58a..dda3451 100644 --- a/backend/src/routes/employee.routes.ts +++ b/backend/src/routes/employee.routes.ts @@ -94,7 +94,8 @@ router.post('/:id/rehire', authMiddleware, async (req: AuthRequest, res, next) = router.delete('/:id', authMiddleware, async (req: AuthRequest, res, next) => { try { const result = await deleteEmployee(req.user!.orgId, req.params.id) - await auditLog(req, 'DELETE', 'EMPLOYEE', req.params.id) + const emp = await prisma.employee.findFirst({ where: { id: req.params.id }, select: { name: true, department: true } }) + await auditLog(req, 'DELETE', 'EMPLOYEE', req.params.id, { employeeName: emp?.name || '', department: emp?.department || '' }) res.json({ success: true, data: result }) } catch (err) { next(err) @@ -196,7 +197,7 @@ router.post('/contracts/batch-renew', authMiddleware, async (req: AuthRequest, r try { const data = batchRenewSchema.parse(req.body) const result = await batchRenew(req.user!.orgId, req.user!.id, data.contractIds, data.years) - await auditLog(req, 'BATCH_RENEW', 'CONTRACT', undefined, { count: data.contractIds.length }) + await auditLog(req, 'BATCH_RENEW', 'CONTRACT', undefined, { count: data.contractIds.length, years: data.years, contractIds: data.contractIds }) res.json({ success: true, data: result }) } catch (err) { next(err) @@ -207,7 +208,8 @@ router.post('/contracts', authMiddleware, async (req: AuthRequest, res, next) => try { const data = addContractSchema.parse(req.body) const result = await addContract(req.user!.orgId, req.user!.id, data) - await auditLog(req, 'ADD_CONTRACT', 'CONTRACT', result.id, { employeeId: data.employeeId }) + const emp = await prisma.employee.findFirst({ where: { id: data.employeeId }, select: { name: true } }) + await auditLog(req, 'ADD_CONTRACT', 'CONTRACT', result.id, { employeeName: emp?.name || '', employeeId: data.employeeId, contractType: data.contractType, startDate: data.startDate, endDate: data.endDate }) await createEvidence({ orgId: req.user!.orgId, category: 'CONTRACT_SIGN', @@ -231,7 +233,8 @@ router.delete('/contracts/:contractId', authMiddleware, async (req: AuthRequest, return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '合同不存在' } }) } await prisma.laborContract.delete({ where: { id: req.params.contractId } }) - await auditLog(req, 'DELETE_CONTRACT', 'CONTRACT', req.params.contractId, { employeeId: contract.employeeId }) + const emp = await prisma.employee.findFirst({ where: { id: contract.employeeId }, select: { name: true } }) + await auditLog(req, 'DELETE_CONTRACT', 'CONTRACT', req.params.contractId, { employeeName: emp?.name || '', employeeId: contract.employeeId, contractType: contract.contractType, startDate: contract.startDate, endDate: contract.endDate }) res.json({ success: true }) } catch (err) { next(err) diff --git a/backend/src/routes/roster.routes.ts b/backend/src/routes/roster.routes.ts index 0cd0119..93067e3 100644 --- a/backend/src/routes/roster.routes.ts +++ b/backend/src/routes/roster.routes.ts @@ -526,7 +526,7 @@ router.post('/:employeeId/disciplinary', authMiddleware, async (req: AuthRequest createdBy: req.user!.id, }, }) - await auditLog(req, 'CREATE', 'DISCIPLINARY', record.id, { employeeId: req.params.employeeId }) + await auditLog(req, 'CREATE', 'DISCIPLINARY', record.id, { employeeId: req.params.employeeId, violationType, severity, action }) await createEvidence({ orgId: req.user!.orgId, category: 'DISCIPLINARY', @@ -666,7 +666,7 @@ router.post('/:employeeId/training', authMiddleware, async (req: AuthRequest, re createdBy: req.user!.id, }, }) - await auditLog(req, 'CREATE', 'TRAINING', record.id, { employeeId: req.params.employeeId }) + await auditLog(req, 'CREATE', 'TRAINING', record.id, { employeeId: req.params.employeeId, topic, trainer, duration }) res.json({ success: true, data: record }) } catch (err) { next(err) } }) @@ -749,7 +749,7 @@ router.post('/:employeeId/performance', authMiddleware, async (req: AuthRequest, reviewer, }, }) - await auditLog(req, 'CREATE', 'PERFORMANCE', record.id, { employeeId: req.params.employeeId }) + await auditLog(req, 'CREATE', 'PERFORMANCE', record.id, { employeeId: req.params.employeeId, period, score, grade, reviewer }) res.json({ success: true, data: record }) } catch (err) { next(err) } }) diff --git a/backend/src/routes/termination.routes.ts b/backend/src/routes/termination.routes.ts index c70db6d..4798eac 100644 --- a/backend/src/routes/termination.routes.ts +++ b/backend/src/routes/termination.routes.ts @@ -98,7 +98,13 @@ router.post('/resignation', authMiddleware, async (req: AuthRequest, res, next) router.delete('/:id/revoke', authMiddleware, async (req: AuthRequest, res, next) => { try { const result = await revokeTermination(req.user!.orgId, req.params.id) - await auditLog(req, 'REVOKE_TERMINATION', 'TERMINATION_RECORD', req.params.id, {}) + const termRecord = await prisma.terminationRecord.findFirst({ where: { id: req.params.id }, select: { employeeId: true, reason: true, type: true } }) + const emp = termRecord ? await prisma.employee.findFirst({ where: { id: termRecord.employeeId }, select: { name: true } }) : null + await auditLog(req, 'REVOKE_TERMINATION', 'TERMINATION_RECORD', req.params.id, { + employeeName: emp?.name || '', + reason: termRecord?.reason || '', + type: termRecord?.type || '', + }) res.json({ success: true, data: result }) } catch (err: any) { if (err?.code === 'CONFLICT') { @@ -183,7 +189,15 @@ router.get('/handover-template', authMiddleware, async (req: AuthRequest, res) = router.post('/draft', authMiddleware, async (req: AuthRequest, res, next) => { try { const result = await createDraft(req.user!.orgId, req.user!.id, req.body) - await auditLog(req, 'CREATE_DRAFT', 'TERMINATION_RECORD', result.id, { reason: req.body.reason }) + const emp = await prisma.employee.findFirst({ where: { id: req.body.employeeId }, select: { name: true, department: true } }) + await auditLog(req, 'CREATE_DRAFT', 'TERMINATION_RECORD', result.id, { + employeeName: emp?.name || '', + department: emp?.department || '', + reason: req.body.reason || '', + type: req.body.type || 'TERMINATION', + terminationDate: req.body.terminationDate || '', + compensation: req.body.compensation || 0, + }) res.json({ success: true, data: result }) } catch (err: any) { if (err?.code === 'NOT_FOUND') { @@ -210,7 +224,13 @@ router.put('/draft/:id', authMiddleware, async (req: AuthRequest, res, next) => router.post('/draft/:id/submit', authMiddleware, async (req: AuthRequest, res, next) => { try { const result = await submitForApproval(req.user!.orgId, req.params.id, req.user!.id) - await auditLog(req, 'SUBMIT_TERMINATION', 'TERMINATION_RECORD', req.params.id, {}) + const termRecord = await prisma.terminationRecord.findFirst({ where: { id: req.params.id }, select: { employeeId: true, reason: true, type: true } }) + const emp = termRecord ? await prisma.employee.findFirst({ where: { id: termRecord.employeeId }, select: { name: true } }) : null + await auditLog(req, 'SUBMIT_TERMINATION', 'TERMINATION_RECORD', req.params.id, { + employeeName: emp?.name || '', + reason: termRecord?.reason || '', + type: termRecord?.type || '', + }) res.json({ success: true, data: result }) } catch (err: any) { if (err?.code === 'CONFLICT' || err?.code === 'NOT_FOUND') { @@ -225,7 +245,13 @@ router.post('/draft/:id/approve', authMiddleware, async (req: AuthRequest, res, try { const { comment } = req.body const result = await approveTermination(req.user!.orgId, req.params.id, req.user!.id, comment || '') - await auditLog(req, 'APPROVE_TERMINATION', 'TERMINATION_RECORD', req.params.id, { comment }) + const termRecord = await prisma.terminationRecord.findFirst({ where: { id: req.params.id }, select: { employeeId: true, reason: true } }) + const emp = termRecord ? await prisma.employee.findFirst({ where: { id: termRecord.employeeId }, select: { name: true } }) : null + await auditLog(req, 'APPROVE_TERMINATION', 'TERMINATION_RECORD', req.params.id, { + employeeName: emp?.name || '', + reason: termRecord?.reason || '', + comment: comment || '', + }) res.json({ success: true, data: result }) } catch (err: any) { if (err?.code === 'CONFLICT' || err?.code === 'NOT_FOUND') { @@ -240,7 +266,13 @@ router.post('/draft/:id/reject', authMiddleware, async (req: AuthRequest, res, n try { const { comment } = req.body const result = await rejectTermination(req.user!.orgId, req.params.id, req.user!.id, comment || '') - await auditLog(req, 'REJECT_TERMINATION', 'TERMINATION_RECORD', req.params.id, { comment }) + const termRecord = await prisma.terminationRecord.findFirst({ where: { id: req.params.id }, select: { employeeId: true, reason: true } }) + const emp = termRecord ? await prisma.employee.findFirst({ where: { id: termRecord.employeeId }, select: { name: true } }) : null + await auditLog(req, 'REJECT_TERMINATION', 'TERMINATION_RECORD', req.params.id, { + employeeName: emp?.name || '', + reason: termRecord?.reason || '', + comment: comment || '', + }) res.json({ success: true, data: result }) } catch (err: any) { if (err?.code === 'CONFLICT' || err?.code === 'NOT_FOUND') { @@ -254,7 +286,16 @@ router.post('/draft/:id/reject', authMiddleware, async (req: AuthRequest, res, n router.post('/draft/:id/execute', authMiddleware, async (req: AuthRequest, res, next) => { try { const result = await executeTermination(req.user!.orgId, req.params.id, req.user!.id) - await auditLog(req, 'EXECUTE_TERMINATION', 'TERMINATION_RECORD', req.params.id, {}) + const termRecord = await prisma.terminationRecord.findFirst({ where: { id: req.params.id }, select: { employeeId: true, reason: true, type: true, terminationDate: true, compensation: true } }) + const emp = termRecord ? await prisma.employee.findFirst({ where: { id: termRecord.employeeId }, select: { name: true, department: true } }) : null + await auditLog(req, 'EXECUTE_TERMINATION', 'TERMINATION_RECORD', req.params.id, { + employeeName: emp?.name || '', + department: emp?.department || '', + reason: termRecord?.reason || '', + type: termRecord?.type || '', + terminationDate: termRecord?.terminationDate?.toISOString().slice(0, 10) || '', + compensation: termRecord?.compensation || 0, + }) await createEvidence({ orgId: req.user!.orgId, category: 'TERMINATION', @@ -276,7 +317,13 @@ router.post('/draft/:id/execute', authMiddleware, async (req: AuthRequest, res, router.post('/draft/:id/cancel', authMiddleware, async (req: AuthRequest, res, next) => { try { const result = await cancelTermination(req.user!.orgId, req.params.id, req.user!.id) - await auditLog(req, 'CANCEL_TERMINATION', 'TERMINATION_RECORD', req.params.id, {}) + const termRecord = await prisma.terminationRecord.findFirst({ where: { id: req.params.id }, select: { employeeId: true, reason: true, type: true } }) + const emp = termRecord ? await prisma.employee.findFirst({ where: { id: termRecord.employeeId }, select: { name: true } }) : null + await auditLog(req, 'CANCEL_TERMINATION', 'TERMINATION_RECORD', req.params.id, { + employeeName: emp?.name || '', + reason: termRecord?.reason || '', + type: termRecord?.type || '', + }) res.json({ success: true, data: result }) } catch (err: any) { if (err?.code === 'CONFLICT' || err?.code === 'NOT_FOUND') { diff --git a/frontend/src/pages/AuditLog.tsx b/frontend/src/pages/AuditLog.tsx index a9dc14f..7e219c1 100644 --- a/frontend/src/pages/AuditLog.tsx +++ b/frontend/src/pages/AuditLog.tsx @@ -63,6 +63,57 @@ const ENTITY_LABELS: Record = { NotificationSetting: '通知设置', } +/** 审计日志 detail 字段中文标签映射 */ +const DETAIL_LABELS: Record = { + employeeName: '员工', + department: '部门', + name: '姓名', + reason: '原因', + type: '类型', + terminationDate: '离职日期', + compensation: '补偿金', + contractType: '合同类型', + startDate: '开始日期', + endDate: '结束日期', + employeeId: '员工ID', + count: '数量', + years: '续签年限', + hireDate: '入职日期', + oldSalary: '原薪资', + newSalary: '新薪资', + oldDepartment: '原部门', + newDepartment: '新部门', + comment: '备注', + resignationReason: '离职原因', + batch: '批量', + violationType: '违纪类型', + severity: '严重程度', + action: '处理措施', + topic: '培训主题', + trainer: '培训师', + duration: '时长', + period: '考核周期', + score: '得分', + grade: '等级', + reviewer: '考评人', +} + +/** 将 detail 对象格式化为可读字符串 */ +function formatDetail(detail: any): string { + if (!detail || typeof detail !== 'object') return '' + const entries = Object.entries(detail).filter(([, v]) => v !== '' && v !== null && v !== undefined) + if (entries.length === 0) return '' + return entries.map(([k, v]) => { + const label = DETAIL_LABELS[k] || k + let val = String(v) + if (k === 'compensation' && val && val !== '0') val = `¥${val}` + if (k === 'startDate' || k === 'endDate' || k === 'terminationDate' || k === 'hireDate') { + val = String(v).slice(0, 10) + } + return `${label}: ${val}` + }).join(' | ') +} + /** * 系统操作日志页面 */ @@ -168,11 +219,11 @@ export default function AuditLog() { {ACTION_LABELS[log.action] || log.action} {ENTITY_LABELS[log.entity] || log.entity} - {log.detail && ( + {log.detail && (() => { const formatted = formatDetail(log.detail); return formatted ? (
- {typeof log.detail === 'object' ? JSON.stringify(log.detail).slice(0, 100) : String(log.detail).slice(0, 100)} + {formatted}
- )} + ) : null; })()}
{new Date(log.createdAt).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}