feat: 优化退休提醒功能 - 个性化退休年龄计算

- 添加 FemaleWorkerType enum (CADRE/WORKER) 和 Employee.femaleWorkerType 字段
- 新增 calcIndividualRetireAge: 按个人出生日期逐人计算退休年龄
- 重写 calcRetirementDaysLeft: 基于个人出生日期+性别+岗位类型
- updateEmployeesRetirementDays 使用 femaleWorkerType 区分女干部/女工人
- 前端新增/编辑员工表单添加女性岗位类型选择器
- 前端政策展示改为改革规则卡片(基准→目标年龄)
- 前端距退休天数改为退休日期显示(XXXX年XX月XX日)
- 移除AI退休政策计算相关代码和手动刷新路由
- 导入支持女性岗位类型列
This commit is contained in:
freedakgmail
2026-07-25 09:27:45 +08:00
parent b892df1d52
commit 41a2c665ed
9 changed files with 208 additions and 277 deletions
+2
View File
@@ -246,6 +246,8 @@ router.post('/excel', authMiddleware, requireAdmin, upload.single('file'), async
orgId, name, department: dept, hireDate,
monthlySalary: encrypt(salary),
gender: val(r['性别']) || (idCard ? extractGenderFromIdCard(idCard) : null),
femaleWorkerType: (val(r['女性岗位类型']) === '工人' || val(r['女性岗位类型']) === 'WORKER') ? 'WORKER'
: (val(r['女性岗位类型']) === '干部' || val(r['女性岗位类型']) === 'CADRE') ? 'CADRE' : null,
phone: val(r['手机号']) || null,
idCardNumber: idCard ? encrypt(idCard) : null,
idCardHash: idCard ? sha256(idCard) : null,
-54
View File
@@ -241,57 +241,3 @@ router.post('/retirement-policy/:id/confirm', requireAdmin, async (req: AuthRequ
}
})
// 手动触发获取最新退休政策(SSE 流式)
router.post('/retirement-policy/refresh', requireAdmin, async (req: AuthRequest, res, next) => {
try {
const orgId = req.user!.orgId
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
const { fetchRetirementPolicyStream } = await import('../services/ai.service')
const crypto = await import('crypto')
let fullContent = ''
for await (const chunk of fetchRetirementPolicyStream()) {
fullContent += chunk
res.write(`data: ${JSON.stringify({ type: 'chunk', content: chunk })}\n\n`)
}
// 解析 JSON
let maleRetireAge = 60
let femaleRetireAge = 55
let femaleWorkerAge = 50
const jsonMatch = fullContent.match(/<JSON>([\s\S]*?)<\/JSON>/)
if (jsonMatch) {
try {
const parsed = JSON.parse(jsonMatch[1].trim())
if (parsed.maleRetireAge) maleRetireAge = Number(parsed.maleRetireAge)
if (parsed.femaleRetireAge) femaleRetireAge = Number(parsed.femaleRetireAge)
if (parsed.femaleWorkerAge) femaleWorkerAge = Number(parsed.femaleWorkerAge)
} catch {}
}
const contentHash = crypto.createHash('sha256').update(fullContent).digest('hex')
const latestPolicy = await prisma.retirementPolicy.findFirst({ where: { orgId }, orderBy: { createdAt: 'desc' } })
if (latestPolicy && latestPolicy.contentHash === contentHash) {
res.write(`data: ${JSON.stringify({ type: 'done', message: '政策内容未变化' })}\n\n`)
res.end()
return
}
const version = latestPolicy ? latestPolicy.version + 1 : 1
const policy = await prisma.retirementPolicy.create({
data: {
orgId, version, content: fullContent, contentHash, status: 'PENDING',
maleRetireAge, femaleRetireAge, femaleWorkerAge,
},
})
res.write(`data: ${JSON.stringify({ type: 'done', data: policy, message: '已获取新政策,待确认' })}\n\n`)
res.end()
} catch (err) {
res.write(`data: ${JSON.stringify({ type: 'error', message: '获取失败' })}\n\n`)
res.end()
}
})