feat: 节假日配置、排班管理独立页面、考勤加班显示优化

- 新增 HolidayConfig 模型,支持法定节假日和调休工作日配置
- 加班费同步逻辑改用 HolidayConfig 判断日期类型
- 员工端考勤显示加班工时、费率和日期类型
- 周末/节假日出勤状态显示为"周末出勤"/"节假日出勤"
- 新增 Employee.defaultShiftId 字段,支持长期排班(工作日班次)
- 排班管理拆分为独立页面(班次管理+排班),考勤管理保留出勤相关功能
- 排班和每日出勤页面增加身份证号列
- 修复岗位和部门编辑失败问题(POST 改 PUT)
- 新增 backfill 脚本:合同薪资回填、默认班次回填

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
selfrelease
2026-08-18 11:00:53 +08:00
parent eb91c2d8fb
commit 1feada76d1
20 changed files with 1482 additions and 484 deletions
+66 -2
View File
@@ -257,7 +257,24 @@ router.get('/contract', portalAuth, async (req: any, res, next) => {
if (!contract) {
return res.json({ success: true, data: null })
}
res.json({ success: true, data: contract })
// 补充员工工资结构(合同中 baseSalary/performanceSalary 为 0 时作为 fallback
const employee = await prisma.employee.findFirst({
where: { id: req.employee.id },
select: { monthlySalary: true, baseSalary: true, performanceSalary: true },
})
const empBase = employee?.baseSalary ? Number(decrypt(employee.baseSalary)) || 0 : 0
const empPerf = employee?.performanceSalary ? Number(decrypt(employee.performanceSalary)) || 0 : 0
const empMonthly = employee?.monthlySalary ? Number(decrypt(employee.monthlySalary)) || 0 : 0
res.json({
success: true,
data: {
...contract,
// 合同中工资为 0 时用员工档案的工资补齐
baseSalary: contract.baseSalary || empBase,
performanceSalary: contract.performanceSalary || empPerf,
monthlySalary: empMonthly,
},
})
} catch (err) {
next(err)
}
@@ -730,7 +747,54 @@ router.get('/attendance', portalAuth, async (req: any, res, next) => {
},
orderBy: { date: 'asc' },
})
res.json({ success: true, data: { published: true, records, title: publish.title } })
// 查询节假日配置,判断每日日期类型和加班费率
const holidays = await prisma.holidayConfig.findMany({
where: { orgId: req.employee.orgId, date: { gte: startDate, lt: endDate } },
})
const holidayMap = new Map<string, string>() // dateStr -> type
for (const h of holidays) {
holidayMap.set(h.date.toISOString().slice(0, 10), h.type)
}
// 获取加班费配置
const otConfig = await prisma.overtimeConfig.findUnique({ where: { orgId: req.employee.orgId } })
const rates = {
weekday: otConfig?.weekdayRate ?? 1.5,
weekend: otConfig?.weekendRate ?? 2.0,
holiday: otConfig?.holidayRate ?? 3.0,
}
// 为每条记录附加日期类型和加班费率
const enrichedRecords = records.map(r => {
const dateStr = r.date.toISOString().slice(0, 10)
const day = new Date(dateStr + 'T00:00:00')
const dayOfWeek = day.getDay()
const holidayType = holidayMap.get(dateStr)
let dateType: 'weekday' | 'weekend' | 'holiday' = 'weekday'
let overtimeRate = rates.weekday
if (holidayType === 'HOLIDAY') {
dateType = 'holiday'
overtimeRate = rates.holiday
} else if (holidayType === 'WORKDAY') {
dateType = 'weekday'
overtimeRate = rates.weekday
} else if (dayOfWeek === 0 || dayOfWeek === 6) {
dateType = 'weekend'
overtimeRate = rates.weekend
}
return {
...r,
dateType,
overtimeRate,
hasOvertime: (r.overtimeHours || 0) > 0,
}
})
res.json({ success: true, data: { published: true, records: enrichedRecords, title: publish.title, overtimeRates: rates } })
} catch (err) {
next(err)
}