feat: 社保AI建议、合同附件多文件上传预览、删除合同、扩展上传格式

This commit is contained in:
freedakgmail
2026-07-27 23:45:22 +08:00
parent 71d7ab2de5
commit 5e22a82163
21 changed files with 1246 additions and 178 deletions
+49 -6
View File
@@ -7,6 +7,7 @@ import { encrypt, decrypt, sha256 } from '../lib/crypto'
import prisma from '../lib/prisma'
import { extractBirthDateFromIdCard, extractGenderFromIdCard } from '../services/retirement.service'
import { calcBatchEntry } from '../services/payroll.service'
import { createEvidence } from '../services/evidence.service'
const router = Router()
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 } })
@@ -274,9 +275,19 @@ router.post('/excel', authMiddleware, requireAdmin, upload.single('file'), async
await prisma.employeeHousingFundRecord.create({ data: { orgId, employeeId: emp.id, startMonth: dateToMonth(hireDate), endMonth: null, base: num(r['公积金基数']) || num(salary), changeType: 'ONBOARDING', createdBy: userId } })
await prisma.salaryChangeRecord.create({ data: { orgId, employeeId: emp.id, oldSalary: 0, newSalary: num(salary), effectiveDate: hireDate, effectiveMonth: dateToMonth(hireDate), endMonth: null, changeType: 'ONBOARDING', createdBy: userId } })
await prisma.employeeDepartmentRecord.create({ data: { orgId, employeeId: emp.id, oldDepartment: '', newDepartment: dept, effectiveMonth: dateToMonth(hireDate), endMonth: null, changeType: 'ONBOARDING', createdBy: userId } })
await createEvidence({
orgId, category: 'ONBOARD', refId: emp.id, employeeId: emp.id,
events: [{ action: '员工入职登记(批量导入)', timestamp: new Date().toISOString(), ip: req.ip || '', userAgent: req.get('User-Agent') || '' }],
createdBy: userId,
})
result.employees++
} catch (e: any) {
result.errors.push(`员工第${i + 2}行:${e?.message || '导入失败'}`)
const msg = e?.message || ''
if (msg.includes('Unique constraint')) result.errors.push(`员工第${i + 2}行:该员工已存在(身份证号重复),跳过`)
else if (msg.includes('invalid') || msg.includes('validation')) result.errors.push(`员工第${i + 2}行:数据格式不正确,请检查各项填写`)
else result.errors.push(`员工第${i + 2}行:导入失败 — ${msg || '未知错误'}`)
}
}
}
@@ -314,8 +325,15 @@ router.post('/excel', authMiddleware, requireAdmin, upload.single('file'), async
})
result.contracts++
}
await createEvidence({
orgId, category: 'CONTRACT_SIGN', refId: undefined, employeeId: empId,
events: [{ action: '合同签订(批量导入)', timestamp: new Date().toISOString(), ip: req.ip || '', userAgent: req.get('User-Agent') || '' }],
createdBy: userId,
})
} catch (e: any) {
result.errors.push(`合同第${i + 2}行:${e?.message || '导入失败'}`)
const msg = e?.message || ''
if (msg.includes('Unique constraint')) result.errors.push(`合同第${i + 2}行:该员工合同已存在,跳过`)
else result.errors.push(`合同第${i + 2}行:导入失败 — ${msg || '未知错误'}`)
}
}
}
@@ -340,9 +358,13 @@ router.post('/excel', authMiddleware, requireAdmin, upload.single('file'), async
const weekdayHours = num(r['工作日加班时长']) || (otType.includes('工作日') ? hours : 0)
const weekendHours = num(r['休息日加班时长']) || (otType.includes('休息日') ? hours : 0)
const holidayHours = num(r['法定节假日加班时长']) || (otType.includes('法定') ? hours : 0)
await prisma.overtimeRecord.create({ data: { orgId, employeeId: empId, month, weekdayHours, weekendHours, holidayHours, createdBy: userId } as any })
await prisma.overtimeRecord.create({ data: { orgId, employeeId: empId, month, weekdayHours, weekendHours, holidayHours } })
result.overtime++
} catch (e: any) { result.errors.push(`加班第${i + 2}行:${e?.message || '导入失败'}`) }
} catch (e: any) {
const msg = e?.message || ''
if (msg.includes('Unique constraint')) result.errors.push(`加班第${i + 2}行:该员工此月份已有加班记录,同一员工同一月只能导入一条,请合并后重试`)
else result.errors.push(`加班第${i + 2}行:导入失败 — ${msg || '未知错误'}`)
}
}
}
@@ -364,8 +386,18 @@ router.post('/excel', authMiddleware, requireAdmin, upload.single('file'), async
const sevMap: any = { '警告': 'WARNING', '严重': 'SERIOUS', '重度': 'SEVERE' }
const actMap: any = { '口头警告': 'ORAL_WARNING', '书面警告': 'WRITTEN_WARNING', '扣款': 'DEDUCTION', '降级': 'DEMOTION', '辞退': 'TERMINATION' }
await prisma.disciplinaryRecord.create({ data: { orgId, employeeId: empId, violationDate: date, violationType: typeMap[val(r['违纪类型'])] || 'OTHER', description: val(r['描述']), severity: sevMap[val(r['严重程度'])] || 'WARNING', action: actMap[val(r['处罚'])] || 'ORAL_WARNING', createdBy: userId } })
await createEvidence({
orgId, category: 'DISCIPLINARY', refId: undefined, employeeId: empId,
events: [{ action: `违纪记录创建(${val(r['违纪类型']) || '其他'}`, timestamp: new Date().toISOString(), ip: req.ip || '', userAgent: req.get('User-Agent') || '' }],
createdBy: userId,
})
result.disciplinary++
} catch (e: any) { result.errors.push(`违纪第${i + 2}行:${e?.message || '导入失败'}`) }
} catch (e: any) {
const msg = e?.message || ''
result.errors.push(`违纪第${i + 2}行:导入失败 — ${msg || '未知错误'}`)
}
}
}
@@ -385,8 +417,19 @@ router.post('/excel', authMiddleware, requireAdmin, upload.single('file'), async
if (!date) { result.errors.push(`考勤第${i + 2}行:日期格式错误`); continue }
const statusMap: any = { '正常': 'NORMAL', '迟到': 'LATE', '早退': 'EARLY_LEAVE', '缺勤': 'ABSENT', '请假': 'LEAVE', '出差': 'BUSINESS_TRIP' }
await prisma.attendanceRecord.create({ data: { orgId, employeeId: empId, date, status: statusMap[val(r['考勤状态'])] || 'NORMAL', checkInTime: val(r['上班时间']) || null, checkOutTime: val(r['下班时间']) || null, remark: val(r['备注']) || null, createdBy: userId } })
await createEvidence({
orgId, category: 'ATTENDANCE', refId: undefined, employeeId: empId,
events: [{ action: `考勤记录导入(${val(r['考勤状态']) || '正常'}`, timestamp: new Date().toISOString(), ip: req.ip || '', userAgent: req.get('User-Agent') || '' }],
createdBy: userId,
})
result.attendance++
} catch (e: any) { result.errors.push(`考勤第${i + 2}行:${e?.message || '导入失败'}`) }
} catch (e: any) {
const msg = e?.message || ''
if (msg.includes('Unique constraint')) result.errors.push(`考勤第${i + 2}行:该员工此日期已有考勤记录,跳过`)
else result.errors.push(`考勤第${i + 2}行:导入失败 — ${msg || '未知错误'}`)
}
}
}