Files
work-leave-service/docs/教师请假模块-改造计划-v1.md
T
wanghu 25147ec74e
build / build-push (push) Successful in 50s
commit
2026-10-10 15:42:52 +08:00

43 KiB
Raw Blame History

教师请假(WorkLeave)模块改造计划 v1

目标:把当前「宿舍通知(DormNotice)」模板工程整体改造为教师请假子应用。 业务闭环:教师提交请假申请 → 平台流程审核器(流程中心)流转审批 → 回调推进单据状态 → 扣减教师时长。 本文件是执行依据:按 P0~P7 逐阶段落地,每阶段附验收标准。

  • 当前后端:E:\YHXT\java\WorkLeave(现为 com.yunhai.dormnotice,Spring Boot 2.7 + JPA + 基础平台 yh-spring-boot-starter)
  • 当前前端:E:\YHXT\view\WorkLeave(现为 dorm-notice,Vue3 + Vite + view-ui-plus + alova)
  • 历史前端(Vue2,只参考页面/菜单节点逻辑):H:\工作区\MYFile\备份\请假前端
  • 历史后端(C#/EF,参考实体字段与业务规则):H:\工作区\MYFile\备份\请假申请2.2
  • 流程对接手册:E:\xwechat_files\wxid_psk2hommdhtq22_310d\msg\file\2026-09\子应用流程与消息对接手册-20260929.md
  • 平台参考实现:E:\YHXT\java\UserclassService(UserContext / SysOperationLogAspect)、E:\YHXT\java\HomeLeave(同平台请假姊妹工程,风格参照)

1. 参考资产盘点(已勘察结论)

1.1 历史左侧菜单节点(请假前端 router,modId 100~190)

modId 页面 历史路径 新方案
100 我要请假(申请表单) /leave/ 保留 → /apply
110 我的请假 /leave/myleave 保留 → /myleave
111 请假编辑(子页) /leave/edit 合并进 /apply?serid=(隐藏路由)
120 审核列表(待我审核) /leave/shenhe 保留 → /audit(改用流程中心待办)
130 抄送列表 /leave/leave_send 保留 → /cc
140 全部请假(管理端) /leave/totallist 保留 → /all
150 指派代课 /leave/assignment P7 可选(依赖排课/课务数据,v1 暂缓)
160 流程管理(固定流程配置) /leave/liucheng 删除:新平台由「流程中心-流程编排」承担(管理员配置)
170 请假设置(6 个 tab) /leave/leave_timeSetting 保留 → /setting(作息/类型/教师时长/节次/其他)
180 时长日志 /leave/leave_amendant 保留 → /timelog
190 统计分析 /leave/tongji 保留 → /stats(本地实现,旧版跳外部站点不沿用)

1.2 历史状态机(C# 口径,新方案做了流程化收敛)

历史:0 待审核 / 1 通过 / 2 未通过 / 3 撤销(通过) / 4 撤销待审 / 5 通过(撤销被拒) / 6 修改待审, 并配套自建流程引擎(Common_flowinfo/flowstep/freeflowinfo/flowtranctInfo/Groupuser)实现固定流程与自由流程。

新方案:审核流转全部交给平台流程审核器,本地只保留单据状态;4/5/6 三个依赖自建引擎的状态取消:

新状态 枚举 进入事件
0 APPROVING 审批中 提交(flow.submitted / flow.task.created / flow.task.handled)
1 PASSED 已通过 flow.completed(扣减时长)
2 REJECTED 已拒绝 flow.rejected
3 CANCELED 已销假 已通过后教师发起销假(本模块动作,返还时长)
4 WITHDRAWN 已撤回 flow.withdrawn(提交人撤回,需无任务被办理)

1.3 历史核心业务规则(重写要点,详见 §6)

  • 请假类型:字典(历史 Common_Dictionary typeName='请假类别'),学校开关 + 排序 → 新方案 sys_dict(dict_type='leaveType')。
  • 时长(调休余额)池:类型进入「时长管控清单」时校验余额;在途占用 = 审批中单据时长之和;通过扣减、销假返还;加减流水留痕。
  • 时间/时长计算:按学校作息(上午 F1-F2、下午 A1-A2)折算小时,同日去午休、跨天按整天 8h;小时制(IsCalculateWay=true)为唯一口径,按天模式取消。
  • 冲突校验:同一申请人、状态 ∈ {0,1} 的时间区间不得重叠(历史含 5,新方案无该状态)。
  • 编辑:仅「审批中」「已拒绝」可编辑并重新提交(重新发起流程,bizId 追加尝试序号);已通过只能销假,不能改。
  • 详情快照:终态时把回调事件 data(含审批意见)序列化落库,详情页免二次查询。
  • 抄送:流程中心 NOTIFY 节点自动发消息;本模块落 leave_cc 留痕表供「抄送列表」渲染。
  • 删除:物理删除(历史口径),仅限审批中/已拒绝/已撤回,且仅本人。

1.4 历史缺陷清单(重写时修正,不复制)

  1. 半天换算 JsAPm 判成 KsAPm(C# 源码 bug)→ 新方案按天模式整体取消,不涉及。
  2. 拒绝时返还时长传错类型 → 新方案拒绝不返还(未扣过),无此分支。
  3. 附件绑定不落库 → 新方案仓储显式 save。
  4. 时长池新建记录 null + x = null → 新方案 default 0。
  5. 时间段校验用字符串拼 SQL → 新方案参数化 JPA + LocalDateTime。
  6. 列表时间过滤口径不一(重叠 vs 包含)→ 统一为重叠语义。

2. 关键决策

# 决策 理由
D1 后端包名 com.yunhai.workleave,artifactId/finalName=workleave,应用名WorkLeave 需求 1;与旧模板彻底切割
D2 删除宿舍业务(Activity/ActivityRead/ActivitySignup 及全部 Model/Repository/Service/Controller),保留 sys_operation_log(含切面)、sys_dict、up_file、contacts(自定义组)、base/dict/file/log 平台代理控制器 需求 2(删实体保留日志表);保留件是请假的通用底座
D3 业务人员字段只存平台数字 ID(userId),姓名/头像/类型名做冗余展示字段;BaseEntity.create_by/update_by 改存操作人 userId,不存账号 需求 3(不保存师生账号)
D4 审核流转对接平台流程审核器(yh-flow-callback-starter + YschFlowOpenApi),bizType=leave 全局唯一 需求 7 对接手册;替代历史自建流程引擎
D5 申请发起 bizId = {leaveId}-{attempt}(attempt 为第几次提交,从 1 起);回调按 - 前缀解析 leaveId 流程中心按 bizType+bizId 幂等,拒绝后重新提交必须换 bizId
D6 消息跳转 JumpTarget.page("detail").param("leaveId", id),前端提供 /detail?leaveId= 落地页 手册 §3.1 红线(query 形态、页面名两端一致)
D7 时长计算做成工具类前后端同规则(前端实时算、后端复核),小时制保留 2 位小数 需求 3 业务逻辑;历史算法在前端算、后端只校验,重写为双端一致
D8 前端保留现有技术栈与壳层(Vue3+Vite+view-ui-plus+alova),仅替换业务域与菜单契约 与当前前端工程一致,降低风险;"只参考页面逻辑"不搬 Vue2 代码
D9 节点 160 流程管理删除;审批意见/流程图/进度时间线用流程中心能力(progress/diagramUrl) 平台已提供,避免重复建设

3. 前置依赖(需平台管理员配合,先登记后联调)

# 事项 说明 状态
A1 projman 建应用 部署名称 leave(= app_code,逐字一致;公共组件勾 Redis) ☐ 待确认
A2 UserBase 注册应用 app_code=leave、app_key/app_secret、pc_url=http://<域名>/leave/front/index.html#/ ☐ 待确认
A3 流程中心编排 注册流程记 flowGuid(建议 leave-apply-v1;如需销假审批再加 leave-cancel-v1) ☐ 待确认
A4 白名单登记 flow-center trusted-apps 加 appKey:secret;UserMsg/UserBase 密钥链 ☐ 待确认
A5 菜单树配置 基础平台按 §4.3 的 modId/path 配置 9 个节点(对齐 GetUserMod) ☐ 待确认
A6 Nacos 配置 workleave.yaml(app-key/secret/flow-guid/open.base-host),本地用 application.yml 兜底 ☐ 待确认

本地开发不需要 A4;open-host 指向 http://open.yunhaixingtong.com(hosts 加 192.168.10.160 open.yunhaixingtong.com)可直连真实服务(手册 §5.0)。


4. 目标架构

4.1 后端目录结构(com.yunhai.workleave)

src/main/java/com/yunhai/workleave/
├── WorkLeaveApplication.java
├── aspect/            ControllerUserContextAspect(Order1)· SysOperationLogAspect(Order3)
├── common/            listener/BaseAuditListener · validation/ValidationGroups
├── config/            Config(API_PATH) · JacksonConfig · SwaggerConfig
├── entity/            BaseEntity · SysDict · UpFile · SysOperationLog
│                      LeaveApply · LeaveSetting · LeaveTimeSet · LeaveUserTime · LeaveUserTimeRecord
│                      LeaveSection · LeaveCc · (P7) LeaveCourse · LeaveCourseExchange · LeaveApplyOld
├── enums/             LeaveStatusEnum · LeaveActionEnum · UserType
├── flow/              LeaveFlowService(apply/withdraw/todo/approve/reject/progress/diagram)
│                      LeaveFlowCallbackHandler(事件状态机 + 终态守卫 + 快照 + 扣减/返还 + 抄送留痕)
├── model/             BasePageQuery/BasePageResult · bsch(SchTeaList/ClassTreeNodeVo) · file(UpFileVo/Result)
│   └── leave/         LeaveVo · LeaveDetailVo · LeaveQuery · LeaveRequest · LeaveCancelRequest
│                      LeaveAuditRequest · LeaveProgressVo · LeaveStatsVo · TimeSetVo/TimeSetRequest
│                      LeaveSettingVo · UserTimeVo · UserTimeRecordVo · UserTimeAdjustRequest · SectionVo ...
├── repository/        LeaveApplyRepository · LeaveSettingRepository · LeaveTimeSetRepository
│                      LeaveUserTimeRepository · LeaveUserTimeRecordRepository · LeaveSectionRepository
│                      LeaveCcRepository · SysDictRepository · UpFileRepository · SysOperationLogRepository
├── service/           I/DictService · I/FileService · I/ContactsService · I/UserCenterService · I/ClassService
│                      I/LeaveApplyService · I/LeaveSettingService · I/LeaveUserTimeService
│                      I/LeaveStatsService · I/SysOperationLogService
│   └── impl/          同名实现 + LeaveTimeCalculator(时长算法)
├── controller/api/    BaseApiController · DictController · FileController · LogController · ContactsController
│                      LeaveController(申请域)· LeaveAuditController(待办/审核/进度)
│                      LeaveSettingController(设置域)· LeaveTimeController(时长域)· LeaveStatsController
└── utils/             UserContext · PageUtils · GzipUtil · IpUtils · LeaveTimeText

4.2 数据表(sql/workleave_schema.sql;ddl-auto=update 兜底建表,脚本供人工核对)

表 说明 关键字段
leave_apply 请假申请主表 id, sch_id, user_id, user_name, user_photo, leave_type(字典key), leave_type_name, start_time, end_time, total_hours, reason, file_ids, cc_user_ids, status, biz_id, instance_id, instance_version, flow_guid, flow_attempt, snapshot(终态JSON), apply_time + BaseEntity 审计列
leave_setting 学校设置(一校一行) sch_id, reason_must, show_explain, enable_course, allow_exchange, time_managed_types(CSV 字典key), time_limit_enabled
leave_time_set 作息时间(一校一行) sch_id, forenoon1, forenoon2, afternoon1, afternoon2, interval_minutes(默认 08:00/12:00/13:00/17:00/10)
leave_user_time 教师时长池 sch_id, user_id, user_name, usable_hours, remark;唯一键 (sch_id,user_id)
leave_user_time_record 时长流水 sch_id, user_id, user_name, leave_id, leave_type, edit_hours(±), old_hours, new_hours, describe, operator_id, create_time
leave_section 节次表(P7 课务用,设置页可维护) sch_id, name, rank, begin_time, end_time
leave_cc 抄送留痕 sch_id, leave_id, cc_user_id, cc_user_name, instance_id, create_time
sys_dict 字典(保留):新增 leaveType (sch_id, dict_type='leaveType', dict_key, dict_value, sort_order, enable_flag)
up_file / sys_operation_log 附件 / 审计日志(保留) 现状不动
flow_callback_event / flow_callback_reg 回调 starter 自动建表 不手工维护

4.3 前端路由与 modId 契约(对齐平台菜单树)

modId path 页面 说明
100 /apply 我要请假 新建/编辑(?serid=),含时长试算、附件、抄送人
110 /myleave 我的请假 本人列表:查看/编辑/销假/撤回/删除
111 /detail 请假详情 ?leaveId=,流程消息跳转落地页;meta.parentId=110(菜单不单列)
120 /audit 审核列表 tab:待我审核(流程待办)/ 我已审核;同意/拒绝/流程图
130 /cc 抄送列表 leave_cc 渲染
140 /all 全部请假 管理端全量 + 导出 CSV
170 /setting 请假设置 tab:作息时间/请假类型/教师时长/节次/其他设置
180 /timelog 时长日志 时长流水 + 合计(新加/剩余),支持加减时长
190 /stats 统计分析 数量/时长按类型与月份、明细
(150) /assignment 指派代课 P7 可选
— / → /apply 重定向 —

平台菜单若分配了不同 modId:优先按 path 解析(menuRoute.ts 已具备),仅需把上表 modId 改为实际值即可。

4.4 前端文件结构(E:\YHXT\view\WorkLeave)

src/
├── api/module/leave.ts        # 全部接口封装(沿用 get/postForm/postJSON 约定)
├── types/leave.ts             # 类型契约(枚举、VO/DTO、请求入参)
├── router/routes.ts           # 上表路由 + meta.modId/parentId
├── utils/leaveTime.ts         # 时长计算(与后端同规则)
├── views/leave/
│   ├── ApplyLeave.vue         # 我要请假(新建/编辑)
│   ├── MyLeave.vue            # 我的请假
│   ├── LeaveDetail.vue        # 详情(信息+进度时间线+附件+抄送人)
│   ├── LeaveAudit.vue         # 审核列表
│   ├── LeaveCc.vue            # 抄送列表
│   ├── AllLeave.vue           # 全部请假
│   ├── LeaveSetting.vue       # 请假设置(多 tab)
│   ├── TimeLog.vue            # 时长日志
│   └── LeaveStats.vue         # 统计分析
└── (保留)components/、utils/、assets/ 等基础设施;删除 views/dorm/*

5. 接口契约(前后端对齐)

统一前缀:/{platform}/api(前端 serviceHost + /api/...;平台值 api/manage/mobiapi)。响应由平台 ApiResponseAdvice 包装 {code,msg,data},alova 解包 data。

5.1 申请域 leave/

方法 接口 入参 出参
GET /leave/List scope(user=我的/all=全部), status, keyword, startTime, endTime, pageIndex, pageSize {content:[LeaveVo], total, ...}
GET /leave/Detail leaveId LeaveDetailVo(含进度、抄送、附件、快照、当前用户能否审核)
POST /leave/Create LeaveRequest(JSON) leaveId(内部:落库→发起流程→回写 bizId/instanceId)
POST /leave/Edit LeaveRequest + id(JSON) void(仅 0/2/4 状态;重新发起流程 attempt+1)
POST /leave/Delete ids(JSON 数组) void
POST /leave/Withdraw id void(调流程中心 withdraw)
POST /leave/Cancel id + reason(JSON) void(销假:状态 1 → 3,返还时长,落流水)
GET /leave/Progress leaveId {steps:[...], tasks:[...]}(流程中心 progress 包装)
GET /leave/DiagramUrl leaveId {url}(流程图 10 分钟 token)
GET /leave/MyTime leaveType {usable, inTransit, remaining}(表单页余额提示)

5.2 审核域 leave/

方法 接口 入参 出参
GET /leave/TodoList pageIndex, pageSize, keyword {content:[TodoVo], total}(todoTasks(bizType=leave) + 本地单号补全)
GET /leave/AuditedList pageIndex, pageSize, keyword 已办:本地按 instance 与审核记录反查
POST /leave/Audit taskId, action(approve/reject), opinion(JSON) void(approveTask/rejectTask;状态由回调推进)

5.3 抄送 / 统计 / 设置 / 时长

方法 接口 说明
GET /leave/CcList 抄送给我的列表(leave_cc 关联 leave_apply)
GET /leave/Statis dateStart/dateEnd → 总数量、总时长、按类型分布、按月趋势、明细 Top
GET /leave/setting/GetTimeSet / SetTimeSet 学校作息
GET /leave/setting/GetOption / SetOption 其他设置(含时长管控类型)
GET /leave/setting/SectionList 节次列表(P7 预览,设置页可维护)
POST /leave/setting/SectionSave / SectionDelete / SectionSort 节次维护
GET /leave/time/UserTimeList 教师时长列表(按组/关键字;可用 base/GetSchoolAll 组树过滤)
POST /leave/time/Adjust 加减时长(单人或批量,JSON;写流水)
GET /leave/time/Records 时长流水(rqType 0今日/1本周/2本月/3累计 + 关键字 + 分页)
GET /leave/time/Sum 合计:新加总时长、剩余总时长
GET/POST /api/dict/*(复用) type=leaveType 的字典增删改查排序(请假类型 tab)

5.4 平台代理接口(保留,改包名)

base/GetUserInfo、base/GetUserMod、base/GetSchoolAll(选人树)、base/SetRole、base/FileUpload、base/Logout、 dict/*、file/*、contacts/*(自定义组)、log/*(审计日志查询)。


6. 业务规则明细

6.1 时长计算(前后端同规则,工具类 LeaveTimeCalculator / leaveTime.ts)

输入:起止时间(yyyy-MM-dd HH:mm)、作息(F1/F2/A1/A2)。输出:小时(四舍五入 2 位)。

  • 同日:start clamp 到工作时段(早于 F1→F1;F1F2 之间保持;F2A1 之间→A1;晚于 A2→A2;A1A2 保持); end 同理(F2A1 之间→F2);minutes = end - start;若 start<=F2 && end>=A1 再减午休 (A1-F2)。
  • 跨天:首日 = A2 - start(start 落在 F1F2 时再减午休);末日 = end - F1(end 落在 A1A2 时再减午休); 中间整天按 8h/天(= A2-A1 + F2-F1);合计取小时。
  • 校验:结束必须晚于开始;单条时长 > 0;跨天视为按天折算(不再区分上下午字段)。

6.2 申请与校验(Create/Edit)

  1. 类型必须为启用的 leaveType 字典项;事由在 reason_must=1 时必填且 ≤50 字。
  2. 时间冲突:同人、status in (0,1)、(start_time < 已有.end_time) && (已有.start_time < end_time) → 报"与已有请假时间冲突"。
  3. 时长管控:类型 ∈ time_managed_types 时,在途(0状态且管控类型) + 本次 <= usable(无记录按 0),否则报"可申请 X 小时,时间不足"。
  4. total_hours 后端按 §6.1 复核(与前端不一致以服务端为准落库)。
  5. 提交成功 → 组装 FlowApplyBody:bizType=leave、bizId={id}-{attempt}、bizTitle={姓名}的{类型}申请、bizJump=page("detail").param("leaveId",id)、bizSnapshot=摘要(类型/起止/时长/事由)、submitterId/Name、formData(类型、时长、天数)。
  6. 发起成功回写 biz_id/instance_id/flow_guid/status=0;duplicated=true 时视为幂等成功(不新建实例)。

6.3 回调状态机(LeaveFlowCallbackHandler)

事件 处理
flow.submitted / flow.task.created / flow.task.handled status→0(审批中);终态不回退:已 1/2/3/4 则忽略
flow.completed status→1;扣时长(管控类型才执行)+ 流水"扣除";落终态快照
flow.rejected status→2;不扣不返还;落终态快照
flow.withdrawn status→4(仅当时为 0 时)
flow.cc.sent 写 leave_cc 留痕(幂等:同 leaveId+ccUserId+instanceId 不重复落)
  • 乱序守卫:事件带 instanceVersion,小于单据已记录版本的事件忽略(终态快照之外)。
  • 事务:回调方法内异常 → 整体回滚 + 流程中心重推;确定性脏消息(bizId 解析失败等)记录 warn 后正常 return,不抛异常。
  • 注册:首次发起前 callbackRegistrar.ensureRegistered(schId, flowGuid)(结果落 flow_callback_reg 镜像表)。

6.4 销假(Cancel)与撤回(Withdraw)

  • 销假:仅 status=1;置 3;管控类型返还时长 + 流水"销假返还";记 cancel_time/reason(快照字段)。已通过 50 天时效沿用(可配置为 50 天常量)。
  • 撤回:仅 status=0;调 YschFlowOpenApi.withdraw(schId, instanceId, submitterId);等回调置 4(不本地直改,避免与流程中心不一致)。

6.5 时长池管理

  • 列表:leave_user_time LEFT JOIN 教师名单(base/GetSchoolAll 同源,按组过滤)。
  • 调整:edit_hours 可正可负,now = old + edit(不低于 0),写流水(describe=备注或"新加"),operator 取当前用户。
  • 流水统计:Sum 返回 新加总=Σ(±)、剩余总=Σ usable(按过滤区间口径)。

7. 流程审核器对接要点(照手册执行)

  1. pom:yh-flow-callback-spring-boot-starter + spring-boot-starter-data-redis(验签密钥链必需,optional 不传递)。
  2. 配置:
    yh:
      app-auth: { enabled: true, app-key: "1000xx", app-secret: "******" }
      flow-callback:
        enabled: true
        biz-types: [leave]
        open-host: ${yh.open.base-host}      # 未配 callback-url 时用于拼回调地址
    management.health.redis.enabled: false
    leave.flow.guid: leave-apply-v1            # 编排所得
    
    回调地址默认 /oapi/flow/callback(starter 自证 context-path 生成);yh.sso.url-pattern=/api/* 不含 oapi,无需放行改造。
  3. 发起:YschFlowOpenApi.apply(schId, body, FlowApplyResult.class)。
  4. 回调:实现 FlowCallbackHandler#onEvent(§6.3);schId 从事件取,不信任本地上下文。
  5. 待办/审核:todoTasks(schId, assigneeId, "leave", page, size)、approveTask(schId, taskId, operatorId, operatorName, opinion[, edgeName])、rejectTask(...)。
  6. 进度/流程图:progress(null, "leave", bizId, schId, FlowProgress.class)、diagramUrl(instanceId, flowGuid, "track", schId, FlowDiagramUrl.class)。
  7. 自检(手册 §5.1):本地单测(mock 回调事件 + apply 出参断言)→ 160 环境全链路 9 项验收。

8. 分阶段任务与验收

P0 骨架重整

  • 后端:包 com.yunhai.dormnotice → com.yunhai.workleave(目录迁移 + 全量 import 替换);DormNoticeApplication → WorkLeaveApplication;pom artifactId/name/finalName → workleave;bootstrap.yml spring.application.name → workleave;日志/库名对齐
  • 后端:删除宿舍业务(ActivityController、Activity 系实体/模型/仓储/服务);保留 log/dict/file/base/contacts 底座
  • 前端:package.json name、vite.config 标题、index.html、README、App.vue appName → 教师请假
  • 验收:后端 mvn -q compile 通过;前端构建通过;全局搜索无 dormnotice 残留

P1 实体与数据层

  • BaseEntity 审计人改存 userId(create_by/update_by)
  • 新实体 + Repository(LeaveApply/LeaveSetting/LeaveTimeSet/LeaveUserTime/LeaveUserTimeRecord/LeaveSection/LeaveCc/LeaveAuditTrace)
  • sql/workleave_schema.sql(建表 + leaveType 字典种子:事假/病假/调休/公假/婚假/产假)
  • 验收:编译通过;启动自动建表成功;leaveType 字典种子可查

P2 设置域 + 时长域

  • LeaveSettingController/Service:作息时间、其他设置、节次
  • LeaveTimeController/Service:时长列表/调整/流水/合计;LeaveTimeCalculator
  • 验收:单测覆盖时长算法(同日/跨天/边界 clamp/午休)—— 16 例全绿

P3 申请域 + 流程对接

  • LeaveController/Service:Create/Edit/Delete/Withdraw/Cancel/List/Detail/MyTime/Statis/TodoList/AuditedList/Audit
  • flow/LeaveFlowService + flow/LeaveFlowCallbackHandler(§6.3)
  • pom/application.yml 流程依赖与配置(§7)
  • 验收:编译通过;回调单测(mock FlowCallbackEvent 覆盖 5 类事件 + 终态不回退 + 乱序门 + 抄送幂等)—— 12 例全绿

P4 前端壳层与路由

  • api/module/leave.ts、types/leave.ts、router/routes.ts(§4.3)、menuRoute.ts 别名更新
  • 删除 views/dorm/*、api/module/dorm.ts、types/dorm.ts 及依赖其的组件
  • 验收:npm run build 通过;菜单解析器对新节点 path 命中

P5 前端页面

  • ApplyLeave:表单 + 时长实时试算 + 余额提示 + 附件 + 抄送人 + 提交/重新提交
  • MyLeave / LeaveDetail / LeaveAudit / LeaveCc / AllLeave
  • LeaveSetting(5 个 tab)/ TimeLog / LeaveStats
  • 验收:构建通过(联调需本地后端 + DB 环境,见 P6 说明)

P6 联调与验收

  • 后端 mvn -o compile / 单测全绿;前端 npm run build
  • 160 环境按手册 §5.1 九项清单验收(含消息卡片跳转 /detail?leaveId=)—— 需管理员提供 A1~A6 前置项
  • 验收:清单全过;proj34.sys_message 最新行 jump_path=/detail?leaveId=xxx、jump_app=leave

P6.5 本地冒烟测试(2026-10-09 新增)

目标:在不依赖平台管理员前置项(A1~A6)的前提下,对已交付代码做本地全链路冒烟, 验证后端服务、数据库、核心业务接口、前端构建与页面渲染的可运行性。 160 全链路(真实 SSO 登录 + 流程审批闭环)仍归 P6,等管理员补齐前置项。

冒烟辅助件(开发专用,生产零影响):

  • 新增 config/DevMockUserFilter:仅当 smoke.auth.enabled=true 时注册,向 BoaUserContext 注入可配置的模拟用户(smoke.auth.user-id/user-name/sch-id/user-type),默认关闭。
  • 本地启动时用 --yh.sso.url-pattern=/__smoke_off__/* 关闭 CAS 拦截,使 /api/* 可直连 (模拟用户由上述过滤器提供,业务代码无需感知)。

冒烟清单(S1~S6):

# 项 内容 通过标准 结果
S1 构建 mvn -o test(含 28 例单测) BUILD SUCCESS,0 失败 ✅ 28 例全绿(修复后复跑仍全绿)
S2 启动 smoke 模式 java -jar 起 8105 日志无 ERROR,/actuator/health 200 ✅
S3 数据 DB work_leave 13 张表 + leaveType 种子 表齐全,LT001~LT006 可查 ✅ 13 表 + 6 种子
S4 设置域 API GetTimeSet/SetTimeSet、GetOption/SetOption、dict 全链路 读默认值→写入→回读一致 ✅(修复 2 个 bug 后通过,见 §10)
S5 申请/时长域 API List 空态、time/Adjust+Sum+Records、MyTime、Statis、Create(流程中心链路) 只读接口正常;Adjust 后 Sum/Records 联动;Create 失败时事务回滚无孤儿单(flowGuid 占位期预期),若流程中心可用则全链路通过 ✅ Adjust +8h→Sum/Records/MyTime 联动一致;Create 报"流程失败 404"且回滚(leave_apply=0);操作日志记录失败尝试
S6 前端 npm run build + dev server 浏览器访问核心页面 构建通过;页面壳层渲染、控制台无阻断性报错 ✅ 9 页面全渲染;申请表单可交互(类型下拉 6 项/时长试算 4h/事由 50 字计数/提交按钮联动);提交失败 toast 正确透传后端错误;"我的请假"空态表格/统计卡正常;设置页 5 tab 正常

执行结论随做随记入 §10。

P7 可选扩展(v1 后)

  • 指派代课/调课(课务安排表 + 教务安排人 + 通知);节次/班级/科目数据源接入
  • 修改待审流程(快照表 leave_apply_old + 二次流程)
  • 发起销假审批(第二 flowGuid leave-cancel-v1)
  • 教师时长 Excel 导入导出;统计图表增强

P8 对齐旧版生产行为(2026-10-09,参考 oa.bestsch.com/leave 实地勘察)

背景:登录旧版生产系统(lwj 账号)实地核对,发现原计划 v1 裁剪掉的两个行为在生产实际在用, 且旧版"其他设置"里有请假计算(按小时/按天)开关(该学校当前为按天)。本阶段把这两块补齐。 旧版算法结论:后端永远存小时(leave_totaltime),"按天"只是输入/显示模式(显示=小时/8), 与新架构小时制天然兼容,改造聚焦在输入侧与配置面。

后端:

  • leave_setting.calc_way(1 按小时[默认] / 0 按天,历史 IsCalculateWay 同义)
  • leave_time_set.min_minutes(最少请假时间,分钟,0 不限制;历史"学校时间设置"项)
  • leave_apply.course_arrange(0 无课务安排 / 1 自行安排 / 2 教务安排;不传默认 0)
  • Create/Edit 校验:时长分钟 < min_minutes → 拒绝"请假时长不能少于 X 分钟"(边界=值本身放行)
  • VO:TimeSetVo+minMinutes、LeaveSettingVo+calcWay、LeaveRequest/LeaveVo/LeaveDetailVo+courseArrange(+Name)

前端:

  • ApplyLeave:calcWay=0 时表单切换为「半天/半天以上 + 日期 + 上午/下午」输入(按作息组合起止 datetime 后复用既有小时试算),时长主显天(=小时/8,同旧版);calcWay=1 保持 datetime 输入。 新增「时间说明」行(showExplain=1 时):上午(F1-F2)-下午(A1-A2),固定X小时工作制。 新增「课务安排」三选一(enableCourse=1 时,默认无课务安排)。
  • LeaveSetting:作息 tab + 「最少请假时间(分钟)」;其他设置 tab 置顶 + 「请假计算:按小时/按天」 radio;课务安排/允许换课去掉 P7 标注(课务字段已落地输入侧,代课执行仍属 P7)。
  • LeaveDetail:显示课务安排。

验证(2026-10-09):28 例单测全绿;smoke API 实测 calcWay/minMinutes 读写回显一致、 20 分钟申请被拒(30 下限)、4h 半天口径(08:00-12:00)正确、courseArrange 落库字段就绪(流程 404 回滚期无孤儿单);浏览器(真实账号李晶 + 8108 IDEA 实例 + devtools 热载):时间说明行、按小时表单、 左侧 9 菜单节点渲染正常。

端口变更:application.yml server.port 8105 → 8108(IDEA debug 实例),vite 代理同步改 8108。 用户另起 dev server 于 8079(与 8078 同源,均为 vite --mode dev)。

学校 3(李晶,对齐旧版生产)已切按天:calc_way=0、enable_course=1(2026-10-09 经 DB 设置)。 8079 实测:半天=0.5 天(合 4 小时)、半天以上 10-12 上午~10-14 下午=3 天(合 24 小时),与旧版算法一致。

按小时模式对齐旧版(2026-10-09 二次勘察旧系统按小时形态后): 起止时间改为旧版同款四控件一行(开始日期+开始时间 至 结束日期+结束时间),TimePicker 分钟按 最短请假间隔 步进(:steps=[1,intervalMinutes,1],对应旧版分钟过滤);时长标签「时长(小时)」 纯小时显示(去掉天副注)。8079 实测 10-12 08:00~12:00 = 4 小时。验证后学校 3 已切回按天。

附件 → 图片上传(2026-10-09,对齐旧版):AttachmentUpload 组件加 imageOnly 模式 (仅 jpg/jpeg/bmp/png、单张 ≤20MB、accept 过滤、错误文案"只能上传 jpg/bmp/png 格式的图片!"); 申请页 FormItem「附件」→「图片」,按钮「上传图片」+ 提示 「(上传图片格式:.jpg、.bmp、*.png 图片大小不要超过20MB)」逐字对齐旧版; 详情页「附件」→「图片」,图片文件渲染 60×60 缩略图(点击预览直链打开)。 后端 FileUpload 保持通用(格式限制在前端,P6 联调时可评估是否服务端加固)。

申请页样式对齐旧版(2026-10-09 四轮,末轮按旧版真实 CSS 值精调):① label 统一加全角「:」 (CSS ::after,不逐个改);② label-width 100px、标签色 #272829(旧版实测值); ③ 表单铺满内容区(取消窄卡片约束,旧版控件实测宽 ~946px);④ 控件宽度按旧版实测: 类型下拉/事由文本框满宽、日期 190px(旧版 193px)、时间 130px、时长 180px(旧版 180px); ⑤ 行距 20px(旧版实测);⑥ 时长由蓝色文字改为禁用 Input(占位「自动获取时长」); ⑦ 按天模式「起止时间」合并为一行:半天/半天以上 + 开始日期 + 上午/下午(半天以上再接 「至」+ 结束日期 + 上午/下午);按小时模式同为四控件一行;⑧ 事由占位「请输入请假事由……」(旧版原文); ⑨ 图片上传按钮改旧版同款方形图标按钮;⑩ 抄送人未选时显示灰色提示 「请选择抄送人,审批通过后通知抄送人」;⑪ 时间说明文字色 #515a6e。 8079 浏览器复核:满宽下拉/事由、100px 冒号标签、180px 时长框、一行起止时间、方形图片按钮均与旧版一致。

P9 流程一致性与安全加固(2026-10-09 现状审计新增)

审计范围:以《子应用流程与消息对接手册-20260929》§2.3、§2.4、§5.1 为准,复核 LeaveFlowService、LeaveFlowCallbackHandler、申请身份边界及现有单测。P0~P8 已交付功能不重做。

审计发现:

  1. 流程发起返回 null 或缺少 instanceId 时,当前代码仍可能提交为“审批中”,形成无真实流程实例的孤儿单。
  2. 拒绝/撤回后重新提交会复用同一请假主键;旧实例迟到事件若按主键兜底定位,可能误推进新 attempt。
  3. LeaveRequest.userId 来自客户端请求体,普通教师可构造该字段替其他用户发起申请;申请人必须只信任登录态。
  4. 手册 §5.1 要求 apply 请求契约断言;现有 12 例流程测试只覆盖回调状态机,没有覆盖 bizType/bizId/bizJump/bizSnapshot/formData/submitter。

实施任务:

  • P9.1 发起结果强校验:FlowApplyResult 及 instanceId 必须非空,否则抛业务异常并由事务回滚。
  • P9.2 回调实例隔离:事件 bizId/instanceId 必须与当前 attempt 一致;旧实例迟到事件正常忽略,不触发重推。
  • P9.3 回调终态幂等:重复终态事件不重复产生副作用,冲突终态事件不得覆盖已落终态。
  • P9.4 申请身份收口:Create 始终使用登录态 currentUserId/userName,删除前后端请求契约中的 userId。
  • P9.5 补齐流程发起请求构造测试、空实例回滚边界测试、旧 attempt/终态冲突回调测试。
  • P9.6 验证:后端 mvn test 34 例全绿;前端 npm run typecheck:leave 通过;npm run build 通过。 全工程 vue-tsc --noEmit 已执行,另发现 74 个模板历史错误(知识树/年级班级/Vant 等未接入请假路由的遗留件), 与本次请假业务文件无关,登记为 R7,不混入本阶段改造。

验收标准:

  • 流程发起无实例 ID 时接口失败且事务不留下申请记录;正常结果完整回写 bizId/instanceId。
  • 100-1 的迟到回调不能修改当前 bizId=100-2 的单据;重复 completed 只扣减一次。
  • 请求体即使伪造 userId 也不进入后端 DTO,申请人只取登录态。
  • 单测显式断言跳转为 /detail?leaveId=xxx、bizType=leave,以及快照/表单/提交人字段。

9. 风险与未决项

# 风险/未决 处置
R1 app_key/app_secret、flowGuid、菜单 modId 实际值未定(依赖管理员) 全部走配置项;本地先以占位值 + mock 单测开发;P6 前与管理员确认
R2 回调在本地收不到(flow-center 不能推回本机) 回调逻辑以单测 mock 覆盖;真链路 160 验收
R3 Redis 不可达导致启动/健康检查异常 management.health.redis.enabled=false;starter 只在回调验签用 Redis,本地联调可用 docker 起一个
R4 历史「按天/半天」模式与「课务安排」不影响 v1 核心闭环 已在决策中裁剪,P7 按需恢复
R5 教师名单来源(bschclass GetSchteaList)不可达时选人/时长列表降级 沿用现有 ClassService 缓存 + 空列表降级策略
R6 时长算法双端实现漂移 同一份规则单测用例(P2 后端 + P4 前端各一份对照)
R7 前端模板遗留 74 个全量类型错误(知识树/年级班级/移动端 Vant 等未使用模块) 新增 tsconfig.leave.json + npm run typecheck:leave 作为请假模块严格门禁;遗留模板另立清理任务,不影响当前构建

10. 执行记录(随实施更新)

日期 阶段 说明
2026-10-08 计划 本文档创建,勘察完成
2026-10-08 P0 完成 后端包名 com.yunhai.workleave、宿舍业务全删、工程标识更名 workleave;前端标识更名教师请假
2026-10-08 P1 完成 8 个业务实体 + 仓储 + 建库脚本(含 leaveType 字典种子);审计人改存 userId
2026-10-08 P2 完成 设置域(作息/其他/节次)+ 时长域(列表/调整/流水/合计)+ LeaveTimeCalculator;16 例单测通过
2026-10-08 P3 完成 申请域(申请/编辑/删除/撤回/销假/待办/审核/统计)+ 流程门面 + 回调状态机;12 例单测通过(共 28 例)
2026-10-08 P4/P5 完成 前端 types/api/routes/壳层重建;9 个业务页面落地;宿舍遗留全删
2026-10-08 P6 完成 后端 mvn package 出包 target/workleave.jar(105MB fat jar);28 例单测 + 前端 npm run build + vue-tsc 新增文件全绿;启动验证:java -jar 起 8105,Hibernate 自动建 13 张表(leave_apply/leave_setting/leave_time_set/leave_user_time/leave_user_time_record/leave_section/leave_cc/leave_audit_trace + 底座表),/actuator/health 返回 200,leaveType 字典种子可查(LT001~LT006)
2026-10-08 待办 160 环境全链路验收依赖 §3 前置项 A1~A6(app_key/flowGuid/菜单 modId 由管理员分配);/api/* 请求按平台 CAS 约定 302 跳 SSO 登录(浏览器带票据后正常,与改造前行为一致)
2026-10-09 P6.5 冒烟 S1~S6 全部执行完毕(结果见 §8 P6.5 表)。新增冒烟辅助件 config/DevMockUserFilter(smoke.auth.enabled=true + --yh.sso.url-pattern=/__smoke_off__/* 启动即可本地免 CAS 冒烟)。修复 3 个 bug:① DictService.normalizeType 大小写比对错误("leaveType".equals("leavetype") 恒 false)导致字典写接口全挂 → 改为忽略大小写比对;② LeaveSettingService.saveOption 显式 null 无法清空 timeManagedTypes → 改为无条件写入(null 视为清空);③ LeaveRequest.userId 多余的 @NotNull 与前端契约冲突(前端从不传 userId、后端本就从 UserContext 取)导致前端 Create/Edit 100% 被 Bean Validation 拦截报"不能为null" → 删除该注解。前端修复:ApplyLeave/LeaveSetting 共 6 处裸 required FormItem 补 :model/prop/rules,消除常驻 "undefined is required" 错误文案。修复后 28 例单测复跑全绿、前端重建通过、浏览器复验通过(提交失败 toast 正确透传"发起审批流程失败:OpenAPI 404",DB 无孤儿单)。流程中心 404 为 flowGuid 占位预期(待 A3)
2026-10-09 P8 完成 对照旧版生产系统(oa.bestsch.com/leave,lwj 登录勘察)补齐:按天模式(calc_way + 半天/半天以上表单 + 天显示)、最少请假时间(min_minutes + 校验)、课务安排三选一(course_arrange)、申请页时间说明行;详见 §8 P8。真实账号(李晶)浏览器全链路验证通过;application.yml 端口改 8108(IDEA 实例),vite 代理同步
2026-10-09 P8 微调 ① 移除申请页顶部「我要请假/编辑请假申请」标题(页面更贴近旧版留白);② 时长开放编辑:申请页时长输入框由禁用改为可编辑(默认自动试算值、后缀带单位,起止时间变化后恢复自动值),提交携带手工值;后端 resolveHours:请求 totalHours>0 时以手工值为准(原逻辑一律服务端重算覆盖),最少请假时间/余额校验按最终落库值执行;后端编译 + 前端构建通过(后端需重启生效)
2026-10-09 P8 教师时长设置对齐旧版 按旧版 oa.bestsch.com/leave/leave_timeSetting「教师时长设置」tab 实地截图 + 历史源码(请假前端 TeaTimeImport.vue、C# leave_System.cs ImportQJSchUserTime/ExportQJSchUserTime)重做:① 管控类型勾选迁入教师时长 tab(「请选择需要使用时长的请假类型」勾选即存,timeLimitEnabled 随勾选联动;其他设置中原两项移除);② 左侧教师组树过滤(base/GetSchoolAll 组节点);③ 表格逐行「新加时长(小时)InputNumber + 情况备注」,底部组内合计剩余时长 + 统一提交(复用 Adjust 批量接口),替代原逐人弹窗;④ 新增 Excel 导入:GET leave/time/ImportTemplate(easyexcel 出模板:部门/教师名称/新加时长(小时)/备注,教师按组预填)+ POST leave/time/Import(表头校验、按部门+姓名定位教师、任一行错整体不入账并返回行级错误明细,语义同旧版 C#);前端 LeaveSetting.vue 重构该 tab + 导入弹窗;mvn test 28 例全绿 + 前端 build 通过(后端需重启)
2026-10-09 P9 完成 流程发起空实例强制失败回滚;回调增加当前 attempt/instance 隔离、重复终态幂等与冲突终态保护;Create 申请人只取登录态并移除前后端 LeaveRequest.userId;新增 6 例流程契约/边界测试(总计 34 例全绿),npm run typecheck:leave 与生产构建通过。全工程类型检查另检出 74 个未使用模板历史错误,见 R7。

交付物清单

后端(E:\YHXT\java\WorkLeave)

  • docs/教师请假模块-改造计划-v1.md(本文件)
  • src/main/resources/sql/workleave_schema.sql(建表 + 字典种子)
  • src/main/java/com/yunhai/workleave/(8 实体 + 8 仓储 + 4 控制器 + 5 服务 + 2 门面/回调)
  • src/test/java/com/yunhai/workleave/(28 例单测)
  • target/workleave.jar(可执行 fat jar)

前端(E:\YHXT\view\WorkLeave)

  • src/types/leave.ts、src/api/module/leave.ts、src/router/routes.ts
  • src/utils/leaveTime.ts(与后端同规则的时长试算)
  • src/views/leave/*.vue(9 个页面)
  • dist/(构建产物,vite build 直出)

启动与联调说明

# 后端(本地库 work_leave,root/123123,见 application.yml)
cd E:\YHXT\java\WorkLeave
mvn -o -DskipTests package
java -jar target/workleave.jar            # 首启自动建表;8108 端口

# 前端
cd E:\YHXT\view\WorkLeave
npm install && npm run dev                # 8078,/api 代理到 8108
npm run typecheck:leave                   # 请假模块严格类型检查

本地未起 Redis / 未配 flow-callback 时 yh.flow-callback.enabled=false, 申请提交会因流程中心不可达而整体回滚(设计如此:不留"不回流"的孤儿单)。 需要本地跑通提交链路时,按手册 §5.0 配 yh.open.base-host 指向 open 子域名并联通 160。

# 本地冒烟模式(免 CAS + 模拟用户 1001/冒烟测试教师/schId=1,见 P6.5)
java -jar target/workleave.jar --smoke.auth.enabled=true --yh.sso.url-pattern=/__smoke_off__/*