feat: 完成全部11项优化需求 + 模板必填项标注 + 归档重算个税

- 高优先级: 花名册导入模板必填项标注、性别自动识别、导入结果反馈、证据链Excel导出、身份证搜索修复、试用期区分与转正提醒、薪税批次流程优化
- 中优先级: 专项附加扣除批量导入、文本模板库完善(Word下载/复制/使用说明)、用工体检评分标准说明、考勤页面导入入口
- 所有导入模板表头标注必填项(*后缀)并含示例行
- 导入逻辑统一改用getField兼容*后缀列名
- 批次归档时强制重算所有条目个税和社保,解决多未归档批次并存时累计计算不准问题
- 更新需求梳理文档
This commit is contained in:
freedakgmail
2026-07-29 19:08:45 +08:00
parent 7c24ebe3d9
commit 0372cbe243
14 changed files with 1275 additions and 171 deletions
+41 -7
View File
@@ -1115,16 +1115,50 @@ function InitImport() {
{result && (
<div className="px-3 py-2 rounded-md bg-green-50 text-green-700 text-xs space-y-1">
<div className="font-medium"></div>
<div>{result.employees} </div>
<div>{result.contracts} </div>
{result.overtime > 0 && <div>{result.overtime} </div>}
{result.disciplinary > 0 && <div>{result.disciplinary} </div>}
{result.attendance > 0 && <div>{result.attendance} </div>}
<div className="flex gap-4">
<span> {result.employees} {result.contracts} </span>
{result.overtime > 0 && <span> {result.overtime} </span>}
{result.disciplinary > 0 && <span> {result.disciplinary} </span>}
{result.attendance > 0 && <span> {result.attendance} </span>}
</div>
{result.skipped > 0 && (
<div className="text-amber-600"> {result.skipped} </div>
)}
{result.duplicates > 0 && (
<div className="text-amber-600"> {result.duplicates} </div>
)}
{result.errors?.length > 0 && (
<div className="mt-2 pt-2 border-t border-green-200">
<div className="font-medium text-amber-600">{result.errors.length}</div>
<div className="font-medium text-amber-600 flex items-center justify-between">
<span>{result.errors.length}</span>
<button className="text-xs text-primary hover:underline" onClick={async () => {
try {
const token = useAuthStore.getState().accessToken
const errorList = result.errors.map((e: string, i: number) => {
const detail = result.details?.[i] || {}
return { sheet: detail.sheet || '员工信息', row: detail.row || i + 2, name: detail.name || '', errors: [e] }
})
const res = await fetch('/api/v1/import/excel/error-log', {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
body: JSON.stringify({ errors: errorList }),
})
const blob = await res.blob()
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = '导入错误日志.xlsx'
a.click()
URL.revokeObjectURL(url)
} catch {
toast.error('导出错误日志失败')
}
}}>
</button>
</div>
{result.errors.slice(0, 10).map((e: string, i: number) => (<div key={i} className="text-amber-600">{e}</div>))}
{result.errors.length > 10 && <div className="text-amber-600">... {result.errors.length - 10} </div>}
{result.errors.length > 10 && <div className="text-amber-600">... {result.errors.length - 10} </div>}
</div>
)}
</div>