feat: Phase 1-3 优化全部完成

Phase 1 紧急修复(8项):
- 社保城市选择改为可输入
- 社保上下限拆分(三险/医保独立基数)
- 公积金试算结果展示修复
- 花名册合同保存修复(日期ISO格式)
- 薪酬批次创建失败修复(城市过滤+错误处理)
- 证据链查看修复
- 个税计算修复(blank_employees读取基本工资)
- 加班费倍率读取配置

Phase 2 功能完善(3项):
- 批量导入per-row异常捕获+导入按钮
- 单人发薪UI入口优化
- 解除协议模板补充(员工提出离职版)

Phase 3 后期规划(4项):
- 工资表导入功能(POST /import/payroll + 前端入口)
- 大病险/长护险附加险种(extraInsurances JSON + 计算适配)
- 专项附加扣除按月录入(SpecialDeductionRecord模型 + 前端Tab)
- 预置河北省社保政策(seed数据)
This commit is contained in:
selfrelease
2026-07-27 18:55:08 +08:00
parent 034fcc4111
commit 255af519d2
16 changed files with 1464 additions and 77 deletions
+9 -1
View File
@@ -175,7 +175,15 @@ export default function ContractInfo({ employeeId, contracts, hireDate }: { empl
</>
)}
<div className="md:col-span-2 flex gap-2">
<Button onClick={() => addContractMutation.mutate(form)} disabled={
<Button onClick={() => {
const payload = {
...form,
signDate: form.signDate ? new Date(form.signDate).toISOString() : null,
startDate: new Date(form.startDate).toISOString(),
endDate: form.endDate ? new Date(form.endDate).toISOString() : null,
}
addContractMutation.mutate(payload)
}} disabled={
addContractMutation.isPending || !form.startDate ||
(form.signMethod === 'PAPER' && !form.attachmentUrl) ||
(form.signMethod === 'ELECTRONIC' && (!form.electronicContractNo || !form.electronicContractUrl))
@@ -12,6 +12,7 @@ import AttendanceOvertimeInfo from './AttendanceOvertimeInfo'
import PerformanceInfo from './PerformanceInfo'
import TerminationInfo from './TerminationInfo'
import ChangeHistoryTab from './ChangeHistoryTab'
import EvidenceChain from './EvidenceChain'
/**
* 员工详情档案页
@@ -99,6 +100,7 @@ export default function EmployeeProfile({ employeeId, onBack }: { employeeId: st
{tab === 'attendance' && <AttendanceOvertimeInfo employeeId={employeeId} attendanceRecords={profile.attendanceRecords} overtimeRecords={profile.overtimeRecords} trainingRecords={profile.trainingRecords} />}
{tab === 'performance' && <PerformanceInfo employeeId={employeeId} records={profile.performanceRecords} />}
{tab === 'termination' && <TerminationInfo employeeId={employeeId} profile={profile} records={profile.terminations} />}
{tab === 'evidence' && <EvidenceChain employeeId={employeeId} />}
{tab === 'history' && <ChangeHistoryTab profile={profile} />}
</div>
</div>
+16 -1
View File
@@ -21,7 +21,22 @@ export default function EvidenceChain({ employeeId }: { employeeId: string }) {
})
if (isLoading) return <div className="text-center py-8 text-gray-400">...</div>
if (!data) return <div className="text-center py-8 text-gray-400"></div>
if (!data) return <div className="text-center py-8 text-gray-400"></div>
if (!data.evidence || data.evidence.length === 0) return (
<div className="space-y-3">
<Card>
<div className="flex items-center justify-between">
<div>
<h2 className="text-xs font-medium flex items-center gap-2"><Scale className="w-4 h-4" /></h2>
<div className="text-xs text-gray-500 mt-1">
{data.employee?.name || '未知'} · {data.employee?.department || '未知'} · {data.employee?.hireDate ? data.employee.hireDate.toString().slice(0, 10) : '未知'}
</div>
</div>
</div>
</Card>
<Card><div className="text-center py-8 text-gray-400"></div></Card>
</div>
)
const categoryColor: Record<string, string> = {
'劳动关系': 'bg-blue-50 text-blue-700 border-blue-200',
+2 -1
View File
@@ -13,7 +13,7 @@ export const terminateReasonMap: Record<string, string> = {
}
/** 详情页 Tab 类型 */
export type DetailTab = 'basic' | 'contract' | 'payslip' | 'attendance' | 'disciplinary' | 'performance' | 'termination' | 'history'
export type DetailTab = 'basic' | 'contract' | 'payslip' | 'attendance' | 'disciplinary' | 'performance' | 'termination' | 'evidence' | 'history'
/** Tab 分组 */
export type TabGroup = '人事信息' | '考勤绩效' | '风险合规' | '薪酬' | '变更历史'
@@ -45,6 +45,7 @@ export const TAB_GROUPS: { group: TabGroup; tabs: { key: DetailTab; label: strin
tabs: [
{ key: 'disciplinary', label: '违纪记录', icon: null },
{ key: 'termination', label: '离职/解聘', icon: null },
{ key: 'evidence', label: '证据链', icon: null },
],
},
{