feat: 20260809 系统优化 - 全部28项问题修复(P0×6+P1×16+P2×6)
P0: 福利批量参保/离职证明下载防乱码/考勤模板合并Sheet/补卡修改/附件在线查看删除 P1: 分页pageSize修复/离职导出筛选/撤回删除草稿/加班费自动计算/考勤加班汇总/证据链异常详情/制度催办/模板导入Word/社保封顶保底/校验字段提示/职务字段/社保费用明细/弹窗防误关/身份证查重/证明员工下拉/培训批量 P2: 离职流程去重/社保基数覆盖输入/薪税入口改名/添加员工引导/绩效模板清理
This commit is contained in:
@@ -129,6 +129,88 @@ router.put('/overtime/:id', async (req: AuthRequest, res: Response, next: NextFu
|
||||
}
|
||||
})
|
||||
|
||||
// 从考勤记录同步加班工时
|
||||
router.post('/overtime/sync-from-attendance', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const orgId = req.user!.orgId
|
||||
const { month } = req.body as { month: string }
|
||||
if (!month || !/^\d{4}-\d{2}$/.test(month)) {
|
||||
return res.status(400).json({ success: false, message: '请提供有效的月份(YYYY-MM)' })
|
||||
}
|
||||
|
||||
const monthStart = new Date(month + '-01')
|
||||
const monthEnd = new Date(monthStart)
|
||||
monthEnd.setMonth(monthEnd.getMonth() + 1)
|
||||
|
||||
// 获取该月所有考勤记录(含加班工时)
|
||||
const records = await prisma.attendanceRecord.findMany({
|
||||
where: { orgId, date: { gte: monthStart, lt: monthEnd }, overtimeHours: { gt: 0 } },
|
||||
})
|
||||
|
||||
if (records.length === 0) {
|
||||
return res.json({ success: false, message: '该月考勤记录中无加班工时' })
|
||||
}
|
||||
|
||||
// 按员工汇总加班工时,按日期类型分类
|
||||
const empMap = new Map<string, { weekday: number; weekend: number; holiday: number }>()
|
||||
for (const r of records) {
|
||||
const day = new Date(r.date)
|
||||
const dayOfWeek = day.getDay() // 0=周日, 6=周六
|
||||
let type: 'weekday' | 'weekend' | 'holiday' = 'weekday'
|
||||
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
||||
type = 'weekend'
|
||||
}
|
||||
// 简单判断法定节假日:这里使用周末判断,实际法定节假日需要额外配置
|
||||
// 如果有 holidayHours 字段在 attendanceRecord 中,优先使用
|
||||
|
||||
if (!empMap.has(r.employeeId)) {
|
||||
empMap.set(r.employeeId, { weekday: 0, weekend: 0, holiday: 0 })
|
||||
}
|
||||
const entry = empMap.get(r.employeeId)!
|
||||
entry[type] += r.overtimeHours || 0
|
||||
}
|
||||
|
||||
// 获取员工月工资用于计算加班费
|
||||
let config = await prisma.overtimeConfig.findUnique({ where: { orgId } })
|
||||
if (!config) config = await prisma.overtimeConfig.create({ data: { orgId } })
|
||||
|
||||
let synced = 0
|
||||
for (const [employeeId, hours] of empMap) {
|
||||
const emp = await prisma.employee.findFirst({ where: { id: employeeId }, select: { monthlySalary: true } })
|
||||
let monthlyWage = 0
|
||||
try { monthlyWage = emp?.monthlySalary ? Number(decrypt(emp.monthlySalary)) : 0 } catch { monthlyWage = Number(emp?.monthlySalary) || 0 }
|
||||
|
||||
const hourlyWage = monthlyWage / config.monthlyDays / config.dailyHours
|
||||
const weekdayPay = hourlyWage * config.weekdayRate * hours.weekday
|
||||
const weekendPay = hourlyWage * config.weekendRate * hours.weekend
|
||||
const holidayPay = hourlyWage * config.holidayRate * hours.holiday
|
||||
const totalPay = weekdayPay + weekendPay + holidayPay
|
||||
|
||||
await prisma.overtimeRecord.upsert({
|
||||
where: { employeeId_month: { employeeId, month } },
|
||||
update: {
|
||||
weekdayHours: hours.weekday,
|
||||
weekendHours: hours.weekend,
|
||||
holidayHours: hours.holiday,
|
||||
weekdayPay, weekendPay, holidayPay, totalPay,
|
||||
},
|
||||
create: {
|
||||
orgId, employeeId, month,
|
||||
weekdayHours: hours.weekday,
|
||||
weekendHours: hours.weekend,
|
||||
holidayHours: hours.holiday,
|
||||
weekdayPay, weekendPay, holidayPay, totalPay,
|
||||
},
|
||||
})
|
||||
synced++
|
||||
}
|
||||
|
||||
res.json({ success: true, data: { synced, totalEmployees: empMap.size } })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// ========== 工资条管理 ==========
|
||||
|
||||
const payslipSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user