日期: 2026-08-10
状态: 已实现
范围: web-admin 加班套件运行时时长联动;backend 时长重算/校验;全国节假日日历同步与落库
前置: 既有加班套件(手动时长);请假套件手改联动模式(oa-leave-kit / oa_leave_hours)
界面模式: 对齐请假套件发起态交互;视觉 Fresh Mint
当前「加班」套件含加班人、开始时间、结束时间、时长(h),时长仅手动填写。客户要求:
apply + validate。holiday.ailcc.com 拉取并 upsert 到本地表;计算只读本地库。| 项 | 决策 |
|---|---|
| 实现方案 | 前后端双端计算 + 本地节假日缓存 |
| 小数 | 按分钟累计,四舍五入 1 位小数(同请假) |
| 工作日窗口 | 只统计与 [workdayOvertimeStart, 次日 workdayOvertimeEnd) 的交集;白天正常工时不算加班 |
| 默认工作日窗口 | 17:30~次日 08:30 |
| 休息日 | 结束−开始,扣除与 [restBreakStart, restBreakEnd) 的交集 |
| 默认午休 | 11:30~13:30 |
| 跨天 | 按自然日切段,分段套用当日规则后相加 |
| 调休补班(ailcc type=4) | 按工作日规则 |
| 法定放假 / 周末 / 调休放假(1/2/3) | 按休息日规则 |
| 时段配置 | 套件属性可配,默认值如上 |
| 手改 | __manual_<durationField>;未手改自动覆盖;手改仅规范化一位小数;可恢复自动 |
| 节假日源 | https://holiday.ailcc.com |
| 同步时机 | 启动拉一次 + 进程内循环 sleep 至次日 00:00 再拉;不注册 cron |
| 库无日历行 | 回退:周一~五工作日,周六日休息日 |
读 sys_cn_holiday_day:
day_type |
含义 | 加班规则 |
|---|---|---|
| 0 | 工作日 | 工作日 |
| 4 | 补班 | 工作日 |
| 1 | 周末 | 休息日 |
| 2 | 节日 | 休息日 |
| 3 | 调休放假 | 休息日 |
| 无记录 | — | 回退周末规则 |
is_rest_day 冗余列:type ∈ {1,2,3} → 1,否则 0。
输入:start、end(YYYY-MM-DD HH:mm)、套件四时段、is_rest_day(date)。
end <= start → 返回 null(不写时长)。[start, end) 内每一时刻 t(实现可用区间合并,语义等同逐分钟):
A:A 为工作日(含补班),且 t ∈ [A + workdayOvertimeStart, (A+1) + workdayOvertimeEnd) → 计入;t 所在日历日为休息日,且 t 不在该日 [restBreakStart, restBreakEnd) → 计入;说明:工作日窗口允许跨到次日早晨;即使次日是休息日,落在「上一工作日窗口」内的凌晨时段仍按加班计入(不再扣次日午休,因已走工作日窗口分支)。
| 场景 | 期望 |
|---|---|
| 工作日 18:00–20:00 | 2.0 |
| 工作日 14:00–18:00 | 0.5(仅 17:30 后) |
| 工作日 17:30–次日 08:30 | 15.0 |
| 周六 09:00–15:00 | 4.0(扣 11:30–13:30) |
| 周五 18:00–周六 12:00 | 17.5 |
跨天拆解(周五 18:00–周六 12:00):
sys_cn_holiday_day全国共用参考数据,无 tenant_no(系统日历豁免租户隔离)。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
VARCHAR(64) | UUID |
day_date |
DATE | 唯一 |
day_type |
TINYINT | 0/1/2/3/4,对齐 ailcc |
day_name |
VARCHAR(64) | 展示名 |
week |
TINYINT | 1–7 |
is_rest_day |
TINYINT | 0/1 |
raw_json |
TEXT | 原始片段 |
sync_time |
DATETIME | 最近同步 |
create_time / update_time |
DATETIME |
唯一键:uk_day_date(day_date)。DDL 写入 docs/sql/。
CN_HOLIDAY_API_BASE(默认 https://holiday.ailcc.com)。/api/holiday/year/{year}(文档明确不返回补班)。GET /api/holiday/batch?d=日期列表&type=Y,按月拆批(每月 ≤31,低于 50 上限)。day_date;失败打日志并保留旧缓存。不在 APScheduler 注册 Job。
在 FastAPI lifespan:
sync_cn_holidays()(不阻塞 yield 前的就绪:可先起线程再 yield);sleep 至下一个本地 00:00 → 再 sync → 直到 stop 事件;同步互斥锁:避免启动同步与 00:00 重叠双写。
多实例:各进程各自同步,upsert 幂等;本期不加分布式锁。
is_rest_day(date) -> bool;list_days(start, end)。GET /api/public/cn-holiday/range?start=YYYY-MM-DD&end=YYYY-MM-DDdayType / isRestDay(或仅返回有行的日期 + 前端回退)。无敏感数据,可走 public。{
"type": "overtimeKit",
"props": {
"personField": "...",
"startField": "...",
"endField": "...",
"durationField": "...",
"workdayOvertimeStart": "17:30",
"workdayOvertimeEnd": "08:30",
"restBreakStart": "11:30",
"restBreakEnd": "13:30"
},
"children": [ "...既有四字段..." ]
}
设计器右侧可选编辑上述四时段;时长字段 info 改为提示可自动计算、可手改。
与请假一致:__manual_<durationField>: true。
| 文件 | 职责 |
|---|---|
oa-overtime-kit-rule.ts |
默认属性 + DragRule props |
oa-overtime-hours.ts |
纯函数计算(注入休息日判断 map/回调) |
oa-overtime-kit-runtime.ts |
binding、apply、manual、对齐样式 |
OaStartFormDialog.vue |
联动与「恢复自动计算」(对齐 leave) |
OaOvertimeKit.vue |
容器;可声明新 props |
发起时:按起止预取 holiday range 到内存,再算时长。
| 文件 | 职责 |
|---|---|
| model + SQL | sys_cn_holiday_day |
cn_holiday_sync.py |
拉 ailcc、upsert、后台循环入口 |
cn_holiday.py |
只读判定 |
oa_overtime_hours.py |
与前端对齐的 calc / apply / validate |
oa_engine 提交路径 |
在 leave/trip 旁调用 overtime apply + validate |
public API |
range 查询 |
main.py lifespan |
启动/停止后台同步线程 |
apply:未手改则覆盖时长;已手改则仅 _round1。
validate:结束晚于开始;手改时长合法。
oa-overtime-hours.test.ts(假日 map 内存注入,不访问外网)。test_oa_overtime_hours.py、test_cn_holiday_sync.py(mock HTTP)。