fix: HR系统优化批次1 - P0/P1问题修复

P0-3: 修复合同状态判断逻辑,有合同记录但signDate为null时不再误判未签
P0-5: 修复参保城市默认北京问题,导入和预览均改为null
P0-11: 添加全局ErrorBoundary防止白屏,三处布局均包裹
P1-2: 合同附件改为可选,允许先保存再补充上传
P1-7.2: 排班弹窗增加员工搜索(姓名/部门)
P1-8.2: 加班费导入支持Excel(xlsx/xls)格式,兼容中英文列名
P1-9: 社保/公积金基数月度办理支持逐人修改,后端返回recordId
This commit is contained in:
selfrelease
2026-07-30 18:34:36 +08:00
parent 411adb7ddc
commit 55286819ae
11 changed files with 480 additions and 39 deletions
+2 -2
View File
@@ -124,7 +124,7 @@ router.post('/excel/preview', authMiddleware, requireAdmin, upload.single('file'
const rows = XLSX.utils.sheet_to_json(empSheet)
for (let i = 0; i < rows.length; i++) {
const r = rows[i] as any
const row: any = { rowNo: i + 2, name: val(getField(r, '姓名')), department: val(getField(r, '部门')) || '未分配', hireDate: getField(r, '入职日期'), salary: num(getField(r, '月工资')), phone: val(getField(r, '手机号')), idCard: val(getField(r, '身份证号')), city: val(getField(r, '参保城市')) || '北京', status: 'normal', errors: [] as string[], warnings: [] as string[] }
const row: any = { rowNo: i + 2, name: val(getField(r, '姓名')), department: val(getField(r, '部门')) || '未分配', hireDate: getField(r, '入职日期'), salary: num(getField(r, '月工资')), phone: val(getField(r, '手机号')), idCard: val(getField(r, '身份证号')), city: val(getField(r, '参保城市')) || null, status: 'normal', errors: [] as string[], warnings: [] as string[] }
if (!row.name) { row.status = 'error'; row.errors.push('姓名为空') }
const hireDate = parseDate(getField(r, '入职日期'))
if (!hireDate) { row.status = 'error'; row.errors.push('入职日期格式错误') }
@@ -285,7 +285,7 @@ router.post('/excel', authMiddleware, requireAdmin, upload.single('file'), async
socialInsBase: num(getField(r, '社保基数')) || num(salary),
housingFundBase: num(getField(r, '公积金基数')) || num(salary),
specialDeduction: num(getField(r, '专项附加扣除')) || 0,
city: val(getField(r, '参保城市')) || '北京',
city: val(getField(r, '参保城市')) || null,
isPregnant: val(getField(r, '孕期')) === '是',
isInMedicalPeriod: val(getField(r, '医疗期')) === '是',
isWorkInjured: val(getField(r, '工伤')) === '是',
+2
View File
@@ -119,6 +119,7 @@ router.get('/', authMiddleware, async (req: AuthRequest, res, next) => {
endDate: latestContract.endDate,
contractType: latestContract.contractType,
hireDate: e.hireDate,
hasRecord: true,
})
: getContractStatus({
signDate: null,
@@ -126,6 +127,7 @@ router.get('/', authMiddleware, async (req: AuthRequest, res, next) => {
endDate: null,
contractType: 'UNSIGNED',
hireDate: e.hireDate,
hasRecord: false,
})
const isResigned = e.terminations.some((t) => t.status === 'COMPLETED' && t.terminationDate <= today)
const isPreHire = !isResigned && e.hireDate > todayEnd
+4
View File
@@ -878,6 +878,7 @@ router.get('/monthly-changes', async (req: AuthRequest, res: Response, next: Nex
const config = await getConfigForCity(r.city)
const detail = config ? calcSocialDetail(r.base, config) : null
return {
recordId: r.id,
employeeId: r.employeeId,
name: r.employee.name,
department: r.employee.department,
@@ -948,6 +949,7 @@ router.get('/housing/monthly-changes', async (req: AuthRequest, res: Response, n
const config = await getConfigForCity(r.city)
const detail = config ? calcHousingDetail(r.base, config) : null
return {
recordId: r.id,
employeeId: r.employeeId,
name: r.employee.name,
department: r.employee.department,
@@ -1012,6 +1014,7 @@ router.get('/active-declaration', async (req: AuthRequest, res: Response, next:
const config = await getConfigForCity(r.city)
const detail = config ? calcSocialDetail(r.base, config) : null
return {
recordId: r.id,
employeeId: r.employeeId,
name: r.employee.name,
department: r.employee.department,
@@ -1073,6 +1076,7 @@ router.get('/housing/active-declaration', async (req: AuthRequest, res: Response
const config = await getConfigForCity(r.city)
const detail = config ? calcHousingDetail(r.base, config) : null
return {
recordId: r.id,
employeeId: r.employeeId,
name: r.employee.name,
department: r.employee.department,