feat: 完成全部11项优化需求 + 模板必填项标注 + 归档重算个税
- 高优先级: 花名册导入模板必填项标注、性别自动识别、导入结果反馈、证据链Excel导出、身份证搜索修复、试用期区分与转正提醒、薪税批次流程优化 - 中优先级: 专项附加扣除批量导入、文本模板库完善(Word下载/复制/使用说明)、用工体检评分标准说明、考勤页面导入入口 - 所有导入模板表头标注必填项(*后缀)并含示例行 - 导入逻辑统一改用getField兼容*后缀列名 - 批次归档时强制重算所有条目个税和社保,解决多未归档批次并存时累计计算不准问题 - 更新需求梳理文档
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user