fix: 优化文档16项问题修复

- 问题1/3: 绩效考核/培训记录员工姓名可点击跳转员工详情页
- 问题2: 离职证明模板支持自定义+员工端下载
- 问题4(P0): 修复工资填写后数据归零问题
- 问题5: 社保添加员工参保信息列表
- 问题6(P0): 商业保险支持为员工参保
- 问题7(P0): 员工福利支持为员工添加福利
- 问题8: 规章制度支持导入Word文档
- 问题9: 文本模板下载Word增加HTML格式
- 问题10: 模板下载变量替换修复(排除token参数)
- 问题11(P0): 电子签署发起时员工下拉框有选项
- 问题12: 新增绩效记录添加考评人选项
- 问题13: 违纪记录添加处罚执行细节
- 问题14: 特殊员工列表添加查看详情按钮和姓名链接
- 问题15: 员工福利汇总正确显示参保人员
- 问题16(P0): 证据链验证修复(递归排序key+自动修复历史哈希)
This commit is contained in:
freedakgmail
2026-08-11 21:24:43 +08:00
parent b682178549
commit 86e5526a83
27 changed files with 837 additions and 428 deletions
+76
View File
@@ -891,6 +891,82 @@ router.post('/resignation/:id/withdraw', portalAuth, async (req: any, res, next)
} catch (err) { next(err) }
})
// 下载离职证明(仅已完成的离职记录)
router.get('/resignation/:id/certificate', portalAuth, async (req: any, res, next) => {
try {
const { id: employeeId, orgId } = req.employee
const record = await (prisma as any).terminationRecord.findFirst({
where: { id: req.params.id, employeeId, orgId },
include: { employee: true },
})
if (!record) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '离职记录不存在' } })
if (record.status !== 'COMPLETED') {
return res.status(400).json({ success: false, error: { code: 'INVALID_STATUS', message: '离职流程未完成,无法下载证明' } })
}
const org = await prisma.organization.findFirst({ where: { id: orgId } })
const orgName = org?.name || ''
// 查找企业自定义的离职证明模板
const tpl = await (prisma as any).enterpriseTemplate.findFirst({
where: { orgId, category: 'LEAVING_CERT' },
})
const reasonLabel: Record<string, string> = {
RESIGNATION: '个人辞职', EXPIRY: '合同到期', DISMISSAL: '违纪辞退',
NEGOTIATED: '协商解除', RETIREMENT: '退休', DEATH: '死亡',
}
const reason = reasonLabel[record.reason] || record.reason || ''
const variables: Record<string, string> = {
employeeName: record.employee?.name || '',
idCardNumber: record.employee?.idCardNumber || '',
department: record.employee?.department || '',
position: record.employee?.position || '',
hireDate: record.employee?.hireDate ? new Date(record.employee.hireDate).toISOString().slice(0, 10) : '',
leaveDate: record.terminationDate ? new Date(record.terminationDate).toISOString().slice(0, 10) : '',
reason,
companyName: orgName,
compensation: String(record.compensation || 0),
socialInsEndMonth: record.socialInsEndMonth || '',
housingFundEndMonth: record.housingFundEndMonth || '',
}
let content: string
if (tpl) {
content = tpl.content
for (const [key, value] of Object.entries(variables)) {
content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value)
}
} else {
content = `<h1>解除/终止劳动合同证明书</h1>
<p>兹证明 ${variables.employeeName}(身份证号:${variables.idCardNumber}),原系我单位 ${variables.department} 部门员工,于 ${variables.leaveDate}${reason} 原因,正式解除/终止劳动合同。</p>
<p>经济补偿金已结清:¥${variables.compensation}。社保截止月份:${variables.socialInsEndMonth || '—'},公积金截止月份:${variables.housingFundEndMonth || '—'}。</p>
<p>特此证明。</p>
<div class="sign">公司(盖章)<br/>${new Date().toISOString().slice(0, 10)}</div>`
}
const htmlContent = `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40">
<head><meta charset="utf-8"><title>离职证明</title>
<!--[if gte mso 9]><xml>
<w:WordDocument><w:View>Print</w:View><w:Zoom>100</w:Zoom><w:DoNotOptimizeForBrowser/></w:WordDocument>
</xml><![endif]-->
<style>
@page { size: A4; margin: 2.54cm 3.17cm 2.54cm 3.17cm; }
body { font-family: SimSun, serif; font-size: 14pt; line-height: 2; text-align: justify; }
h1 { font-size: 22pt; font-weight: bold; text-align: center; margin: 30pt 0 20pt 0; font-family: SimHei, sans-serif; }
p { text-indent: 2em; margin: 0 0 10pt 0; }
.sign { text-align: right; margin-top: 30pt; margin-right: 20pt; text-indent: 0; }
</style></head>
<body>${content}</body></html>`
const encoded = encodeURIComponent(`离职证明-${variables.employeeName}.doc`)
res.setHeader('Content-Type', 'application/msword; charset=utf-8')
res.setHeader('Content-Disposition', `attachment; filename="${encoded}"; filename*=UTF-8''${encoded}`)
res.send(htmlContent)
} catch (err) { next(err) }
})
// ========== 员工端:休假申请 ==========
// 查看自己的休假申请列表
router.get('/leaves', portalAuth, async (req: any, res, next) => {