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
+33 -2
View File
@@ -844,6 +844,7 @@ function BasicInfo({ profile }: { profile: any }) {
const [form, setForm] = useState({
department: profile.department || '',
gender: profile.gender || '男',
femaleWorkerType: profile.femaleWorkerType || '',
phone: profile.phone || '',
hireDate: profile.hireDate?.toString().slice(0, 10) || '',
monthlySalary: profile.monthlySalary || '',
@@ -873,6 +874,7 @@ function BasicInfo({ profile }: { profile: any }) {
const data: any = {
department: form.department,
gender: form.gender,
femaleWorkerType: form.femaleWorkerType || undefined,
phone: form.phone || undefined,
hireDate: new Date(form.hireDate).toISOString(),
monthlySalary: String(form.monthlySalary),
@@ -895,6 +897,9 @@ function BasicInfo({ profile }: { profile: any }) {
{ label: '姓名', value: profile.name },
{ label: '部门', value: profile.department },
{ label: '性别', value: profile.gender || '未填写' },
...(profile.gender === '女'
? [{ label: '女性岗位', value: profile.femaleWorkerType === 'CADRE' ? '干部/管理岗' : profile.femaleWorkerType === 'WORKER' ? '工人/操作岗' : '未填写' }]
: []),
{ label: '身份证号', value: profile.idCardNumber || '未填写' },
{ label: '手机号', value: profile.phone || '未填写' },
{ label: '入职日期', value: profile.hireDate?.toString().slice(0, 10) },
@@ -906,7 +911,26 @@ function BasicInfo({ profile }: { profile: any }) {
{ label: '银行账号', value: profile.bankAccount || '未填写' },
{ label: '状态', value: profile.status === 'ACTIVE' ? '在职' : '离职' },
...(profile.retirementDaysLeft != null
? [{ label: '距退休', value: profile.retirementDaysLeft > 0 ? `${profile.retirementDaysLeft}` : '已到退休年龄' }]
? (() => {
if (profile.retirementDaysLeft <= 0) return [{ label: '距退休', value: '已到退休年龄' }]
if (profile.birthDate) {
const bd = new Date(profile.birthDate)
const gender = profile.gender || '男'
const fwt = profile.femaleWorkerType || null
const baseAge = gender === '男' ? 60 : (fwt === 'WORKER' ? 50 : 55)
const delayInterval = gender === '男' ? 4 : (fwt === 'WORKER' ? 2 : 4)
const maxDelay = gender === '男' ? 36 : (fwt === 'WORKER' ? 60 : 36)
const baseRetireDate = new Date(bd)
baseRetireDate.setFullYear(baseRetireDate.getFullYear() + baseAge)
const reformStart = new Date(2025, 0, 1)
const monthsSince = Math.max(0, (baseRetireDate.getFullYear() - reformStart.getFullYear()) * 12 + (baseRetireDate.getMonth() - reformStart.getMonth()))
const delayMonths = Math.min(maxDelay, Math.floor(monthsSince / delayInterval))
const retireDate = new Date(baseRetireDate)
retireDate.setMonth(retireDate.getMonth() + delayMonths)
return [{ label: '退休日期', value: `${retireDate.getFullYear()}${retireDate.getMonth() + 1}${retireDate.getDate()}` }]
}
return [{ label: '距退休', value: `${profile.retirementDaysLeft}` }]
})()
: []),
...(profile.status !== 'ACTIVE' && profile.terminations && profile.terminations.length > 0
? [{ label: '离职日期', value: profile.terminations
@@ -951,6 +975,9 @@ function BasicInfo({ profile }: { profile: any }) {
<div><Label></Label><Input value={profile.idCardNumber || ''} disabled /></div>
<div><Label></Label><Input value={form.department} onChange={(e) => setForm({ ...form, department: e.target.value })} /></div>
<div><Label></Label><Select value={form.gender} onChange={(e) => setForm({ ...form, gender: e.target.value as '男' | '女' })}><option value="男"></option><option value="女"></option></Select></div>
{form.gender === '女' && (
<div><Label></Label><Select value={form.femaleWorkerType} onChange={(e) => setForm({ ...form, femaleWorkerType: e.target.value as '' | 'CADRE' | 'WORKER' })}><option value=""></option><option value="CADRE">/</option><option value="WORKER">/</option></Select></div>
)}
<div><Label></Label><Input value={form.phone} onChange={(e) => setForm({ ...form, phone: e.target.value })} placeholder="选填" maxLength={11} /></div>
<div><Label></Label><Input type="date" value={form.hireDate} onChange={(e) => setForm({ ...form, hireDate: e.target.value })} /></div>
<div><Label></Label><Input type="number" value={form.monthlySalary} onChange={(e) => setForm({ ...form, monthlySalary: Number(e.target.value) })} /></div>
@@ -1795,7 +1822,7 @@ function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
})()
const [form, setForm] = useState({
name: '', department: '', hireDate: todayStr, monthlySalary: '',
idCardNumber: '', gender: '男' as '男' | '女', phone: '',
idCardNumber: '', gender: '男' as '男' | '女', femaleWorkerType: '' as '' | 'CADRE' | 'WORKER', phone: '',
city: '北京',
contractType: 'FIXED' as 'FIXED' | 'UNFIXED' | 'UNSIGNED',
signDate: '', startDate: todayStr, endDate: defaultEndDate,
@@ -1895,6 +1922,7 @@ function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
name: form.name, department: form.department,
hireDate: new Date(form.hireDate).toISOString(),
monthlySalary: form.monthlySalary, gender: form.gender,
femaleWorkerType: form.gender === '女' && form.femaleWorkerType ? form.femaleWorkerType : undefined,
idCardNumber: form.idCardNumber || undefined,
phone: form.phone || undefined,
socialInsBase: form.socialInsBase ? parseFloat(form.socialInsBase) : undefined,
@@ -1936,6 +1964,9 @@ function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
<div><Label> *</Label><Input value={form.department} onChange={(e) => setForm({ ...form, department: e.target.value })} placeholder="如:技术部" /></div>
<div><Label> *</Label><Input value={form.idCardNumber} onChange={(e) => handleIdCardChange(e.target.value)} placeholder="18位" maxLength={18} /></div>
<div><Label></Label><div className="text-sm text-gray-600 py-2">{form.idCardNumber.length >= 17 ? form.gender : '自动识别'}</div></div>
{form.gender === '女' && (
<div><Label></Label><Select value={form.femaleWorkerType} onChange={(e) => setForm({ ...form, femaleWorkerType: e.target.value as '' | 'CADRE' | 'WORKER' })}><option value=""></option><option value="CADRE">/</option><option value="WORKER">/</option></Select></div>
)}
</div>
<div className="grid grid-cols-4 gap-4">
<div><Label> *</Label><Input type="date" value={form.hireDate} onChange={(e) => handleHireDateChange(e.target.value)} /></div>