Files
guoju0808/会议状态流转.md
T

20 KiB
Raw Blame History

会议状态流转 —— 设计与实现总结

本文档是对「会议状态机」从单一 current_stage 枚举重构为事实驱动模型的完整设计说明。 供后续维护、二次开发、排查问题时查阅。最后更新时间:2026-08-24。


1. 背景与动机

1.1 旧模型的问题

旧实现用 biz_meeting.current_stage 一个枚举字段承载整个状态机,存在三类结构性缺陷:

  1. 单字段多语义:一个字段既要表达「执行/审核/结算/冻结」等事实,又要表达「每个角色看到什么」,二者耦合,任何角色差异都要靠 if/else 硬编码中文标签,导致历史 bug(如 BizDashboardController 拿枚举码去比中文标签,统计恒为 0)。
  2. 审核链路无法表达:材料/凭证需要「两级审核」(合规 → 支持方),单枚举 AWAITING_SPONSOR 之类无法区分「合规审中」还是「支持方审中」,也无法区分「材料退回」与「凭证退回」。
  3. 时间驱动状态靠手改:冻结、待结算(24h)这类时间门槛没有落地字段,只能靠人工或外置脚本改状态。

1.2 新模型的核心思想

事实与展示分离

  • biz_meeting 只存事实is_executed/is_frozen/is_settled/is_finished + 材料审核阶段 + 审核时间 + 两级审核标记)。
  • 各角色看到的「阶段名称」由这些事实实时推导,不再是一个固定枚举投影。

类比:数据库不存「年龄」,只存「出生日期」,「年龄」是算出来的。这里 biz_meeting 只存「发生了什么」,各角色看到的「当前处于什么阶段」是算出来的。


2. 数据模型(事实字段)

2.1 biz_meeting 新增/复用的状态事实字段

字段 类型 含义 谁来写
is_executed int(0/1) 是否执行 调度器 markExecutedstart_time 到点)
execute_time datetime 执行时间 调度器
is_frozen int(0/1) 是否冻结 调度器 markFrozen(逾期未提交)
freeze_time datetime 冻结时间 调度器
is_settled int(0/1) 是否结算 合规/管理员点击「结算」
settle_time datetime 结算时间 同上
is_finished int(0/1) 是否完结 合规/管理员点击「完结」
finish_time datetime 完结时间 同上
material_audit_stage varchar 材料审核阶段(4 值,见 §3 执行方提交 / 审核动作
material_audit_time datetime 材料最近一次审核动作时间 同上
material_compliance_approved int(0/1) 材料合规(一级)是否通过 合规审核通过时置 1
current_stage varchar 派生缓存9 值,见 §4 仅由 StageDeriver/控制器/调度器写,业务代码不得直接改

is_executed 为数据库既有字段,本次直接复用,未删未重加。 新增字段默认值:material_audit_stage 默认 'NOT_SUBMITTED'(已 ALTER),其余 int 默认 0、时间默认 NULL。

2.2 审核阶段枚举(4 值)

material_audit_stage 使用枚举 MeetingAuditStageEnum

中文 含义
NOT_SUBMITTED 未提交 执行方尚未提交(含提交后被退回、尚未重提的初始态)
SUBMITTED 已提交 已提交,处于审核链路中(合规审或支持方审)
APPROVED 审核通过 支持方(二级)审核通过
REJECTED 审核驳回 任一级驳回,退回执行方

注:用户拍板「已提交」和「待审核」合并SUBMITTED,因此审核阶段是 4 值而非 5 值。审核链路中「合规审中 vs 支持方审中」由 *_compliance_approved 布尔位区分(见 §5)。


3. 两级审核(合规 → 支持方)

SUBMITTED 内部再拆两级,由 material_compliance_approved 标记:

执行方提交 (SUBMITTED, compliance_approved=0)
        │
        ▼
  ┌─────────────────┐
  │  合规审核 (manager) │  audit-compliance
  └─────────────────┘
        │ 通过 → compliance_approved=1
        │ 拒绝 → REJECTED(退回执行方)
        ▼
  ┌─────────────────┐
  │ 支持方审核 (监察员) │  audit-supervision
  └─────────────────┘
        │ 通过 → APPROVED
        │ 拒绝 → REJECTED(退回执行方,并通知)
        ▼
      APPROVED
  • 材料单独走这套两级链路(凭证审核链路已移除,凭证改由合规人员在结算时上传)。
  • 「材料审核通过」后,若审核时间已超 24h,则进入「待结算」。

4. 派生逻辑(单一可信源:StageDeriver

后端 com.ruoyi.business.service.StageDeriver唯一推导逻辑所在地,前端 utils/meetingStage.js 是其镜像。

4.1 物理阶段(9 值,derivePhysicalStage

用于 current_stage 缓存 + 列表筛选精确匹配,按优先级自上而下:

优先级 事实条件 物理阶段
1 is_frozen=1 FROZEN 冻结中
2 is_finished=1is_settled=1 SETTLED 已结算
3 material_audit_stage=REJECTED RECTIFYING 待整改
4 material=APPROVEDsettlementReady AWAITING_SETTLEMENT 待结算
5 material=APPROVED 且未满足 24h SUPERVISION_APPROVED 审核通过
6 material=SUBMITTEDmaterial_compliance_approved=1 AWAITING_SUPERVISION 待支持方审核
7 material=SUBMITTEDmaterial_compliance_approved=0 AWAITING_COMPLIANCE 待合规审核
8 material=NOT_SUBMITTEDis_executed=1 RUNNING 执行中
9 material=NOT_SUBMITTEDis_executed=0 NOT_STARTED 未执行

settlementReady 判据(待结算,用户拍板口径):

material_audit_stage = APPROVED
AND material_audit_time 距今 >= 24 小时(自然日)

即:材料通过,且通过的那个时间点已超 24h。

4.2 各角色展示名(deriveDisplay

展示层按角色折叠,优先级自上而下:

事实条件 executor sponsor(支持方) manager(合规) admin / doctor / expert
is_frozen=1 冻结中 冻结中 冻结中 冻结中
is_finished=1 已完结 已完结 已完结 已完结
is_settled=1 已结算 已结算 已结算 已结算
material=REJECTED 已退回 待整改 待整改 待整改
material=APPROVED 且 24h 待结算 待结算 待结算 待结算
material=APPROVED 未满 24h 审核通过 审核通过 审核通过 审核通过
material=SUBMITTED 且合规已过 待审核 待审核 审核通过 待审核
material=SUBMITTED 且合规审中 待审核 已执行未传材料 待审核 待审核
material=NOT_SUBMITTED 且已执行 执行中 已执行未传材料 已执行未传材料 已执行未传材料
material=NOT_SUBMITTED 且未执行 未执行 未执行 未执行 未执行

关键差异点:

  • 退回:只有执行方看「已退回」,其余方看「待整改」。
  • 合规审中:支持方(只读旁观者)看「已执行未传材料」。
  • 支持方审中:合规(manager)看「审核通过」,其余方看「待审核」。
  • 展示名按材料阶段驱动。

5. 状态机流转总图

                              ┌─────────────────────────────┐
                              │ NOT_STARTED 未执行            │
                              └──────────────┬──────────────┘
                          start_time 到点(调度器 markExecuted
                                             ▼
                              ┌─────────────────────────────┐
                              │ RUNNING 执行中                │
                              └──────┬───────────────┬──────┘
              执行方提交材料              │               │ end_time+submit_deadline_days 到
               (submit-material)         │               │ 且 material 未提交(调度器 markFrozen
                                          │               ▼
                              ▼         │        ┌─────────────────┐
                    ┌──────────────────┐ │        │ FROZEN 冻结中     │(终止,不可提交)
                    │ SUBMITTED 已提交  │◄┘        └─────────────────┘
                    │ (compliance=0)    │
                    └────────┬─────────┘
                     合规审核 (audit-compliance, manager)
                       ├─ 拒绝 → REJECTED ────────────┐
                       └─ 通过 → compliance_approved=1 │
                                    ▼                  │
                    ┌──────────────────────┐           │
                    │ 支持方审核 (audit-    │           │
                    │ supervision, 监察员)  │           │
                    └────────┬─────────────┘           │
                       ├─ 拒绝 → REJECTED ─────────────┤
                       └─ 通过 → APPROVED               │
                                    │                  │
                        material APPROVED              │
                                    │                  │
                  ┌─────────────────┴──┐               │
                  │ 未满 24h:           │               │
                  │ SUPERVISION_APPROVED│               │
                  │ 审核通过            │               │
                  └────────┬──────────┘               │
                    满 24h(调度器 markSettlementReady)│
                           ▼                            │
                  ┌─────────────────────┐               │
                  │ AWAITING_SETTLEMENT │               │
                  │ 待结算              │               │
                  └────────┬────────────┘               │
             合规/管理员点「结算」(settle)                │
                           ▼                            │
                  ┌─────────────────────┐               │
                  │ SETTLED 已结算        │               │
                  └────────┬────────────┘               │
             合规/管理员点「完结」(finish)                │
                           ▼                            │
                  ┌─────────────────────┐               │
                  │ 已完结 (is_finished)  │               │
                  └─────────────────────┘               │
                                                        │
  REJECTED 退回执行方 → 执行方看「已退回」, 可重新提交 ◄──┘
  (material 回到 NOT_SUBMITTED 语义, 重新走 SUBMITTED)

6. 后端改动清单

文件 改动
service/StageDeriver.java新增 单一可信源:derivePhysicalStage + deriveDisplay + settlementReady
domain/BizMeeting.java 新增事实字段 + getter/setter(凭证 3 字段已删,见 §12)
mapper/BizMeetingMapper.java + .xml resultMap/selectFields/updateByPrimaryKey 补事实字段;调度 SQL 重写为 3 个 update
scheduler/MeetingStageScheduler.java重写 3 个 @Scheduled 每分钟任务:markExecuted / markFrozen / markSettlementReady
controller/BizMeetingController.java 重写审核段(见 §7);新增 StageDeriver 注入与 appendAuditLog 4 列版本
service/impl/BizMeetingServiceImpl.java 新建会议默认 material_audit_stage = 'NOT_SUBMITTED'(原 INIT
mapper/BizMeetingAuditLogMapper.java + .xml current_stage 拆为 4 列:executor_stage/sponsor_stage/manager_stage/admin_stage
controller/BizDashboardController.java 修复历史 bug:中文标签比对 → 枚举码比对

6.1 调度器 SQL 语义

  • markExecutedstart_time <= NOW()material_audit_stage='NOT_SUBMITTED'is_executed=0is_frozen=0is_executed=1, execute_time=NOW(), current_stage='RUNNING'
  • markFrozenmaterial='NOT_SUBMITTED'date_add(end_time, interval p.submit_deadline_days day) <= NOW()is_frozen=1, freeze_time=NOW(), current_stage='FROZEN'submit_deadline_days 取自 biz_projectjoin 软删过滤)。
  • markSettlementReady:材料 APPROVEDmaterial_audit_time <= NOW() - INTERVAL 1 DAYcurrent_stage='AWAITING_SETTLEMENT'

7. 后端接口(审核链路)

接口 角色 前置校验 通过后 拒绝后
POST /{meetingId}/submit-material 执行方(isExecutorOfProject 已执行 + 未冻结 + stage∈{NOT_SUBMITTED,REJECTED} + 至少 1 条 L_* 材料 + 1 条 M_* 材料 material→SUBMITTED, compliance=0
POST /{meetingId}/audit-compliance 合规(manager stage=SUBMITTED 且 compliance=0;拒绝需意见 compliance=1(转入支持方) →REJECTED(退回)
POST /{meetingId}/audit-supervision 监察员(biz_meeting_supervisor stage=SUBMITTED 且 compliance=1;拒绝需意见 →APPROVED(材料通过时一并写监管意见) →REJECTED(退回并通知执行方)
POST /{meetingId}/settle 合规/管理员 材料 APPROVED + 费用汇总完成 + 上传劳务凭证/会务凭证 保存凭证 + is_settled=1, settle_time
POST /{meetingId}/finish 合规/管理员 is_settled=1 is_finished=1, finish_time
  • 结算接口已二合一:合规/管理员在结算时上传劳务凭证(LV_PAYMENT+ 会务凭证(SV_PAYMENT),缺任一报错「请上传凭证」;结算后凭证只读可见。
  • 结算 → 完结是两个独立的手动动作(用户规则 1),不能跳步。
  • supervision-opinion 旧接口已删除,支持方审批统一走 audit-supervision

7.1 审核日志(biz_meeting_audit_log

appendAuditLog(m, auditType, result, opinion) 不再写单一 current_stage,改为写 4 个角色列,每个记录流转那一刻各角色看到的展示态(auditType 现仅 MATERIAL 审核链路与 SETTLE 结算两类):

executor_stage / sponsor_stage / manager_stage / admin_stage

(由 stageDeriver.deriveDisplay(role, m) 实时生成)。旧 current_stage 列保留在库里但不再读写(遗留,见 §10)。


8. 前端改动清单

文件 改动
utils/meetingStage.js重写 镜像 StageDeriverderivePhysicalStage / deriveStage / stageLabel / stageClass / stageTag / STAGE_OPTIONS
views/meetings/Meetings.vue admin/manager/sponsor 共享列表:stageLabel(roleSegment, row) + stageClass(row)sponsor「审批」按钮条件 derivePhysicalStage(row)==='AWAITING_SUPERVISION',点击 POST audit-supervision
views/meetings/MeetingDetail.vue 审核 dialog 仅材料;新增「结算」「完结」按钮(结算 dialog 内含劳务/会务凭证上传,二合一)+ canSettle/canFinish + onSettle/onFinish;凭证结算后只读展示
views/executor/Meetings.vue stageLabel('executor', row)(列表 + 监督意见 dialog
views/doctor/Meetings.vue stageTag(row) + stageLabel('doctor', row);筛选下拉改用 STAGE_OPTIONS

meetingStage.js 与后端 StageDeriver严格镜像:任何一处改了推导规则,另一处必须同步,否则列表显示与详情/日志不一致。


9. 关键设计决策(拍板记录)

  1. 结算 → 完结需手动点击settlefinish 是两个独立动作,合规/管理员各点一次。
  2. 待结算 24h 门槛:材料通过且「通过时间」超 24h 才进「待结算」。
  3. 冻结判据end_time + project.submit_deadline_days 天;未提交判据 = material_audit_stage == NOT_SUBMITTED
  4. 已提交 / 待审核合并:审核阶段 4 值(SUBMITTED 内部靠 compliance_approved 分两级)。
  5. is_executed(0,1) 复用DB 已有,直接使用,未删未重加。
  6. 凭证改由结算时上传:执行方不再提交/审核凭证,凭证由合规/管理员在结算时一并上传(二合一),需同时有劳务凭证与会务凭证。

10. 已知问题 / 待办 / 注意事项 ⚠️

  1. 24h 是软门槛settle 接口和前端「结算」按钮只校验「材料 APPROVED + 费用汇总完成 + 凭证齐全」,未强制校验 24h。24h 目前只体现在展示名(「审核通过」→「待结算」)和调度器翻转 current_stage。若需硬校验,需给 canSettle + settlesettlementReady 判据。
  2. 执行方新增「审核通过」态:材料支持方通过后、满 24h 前,执行方看到「审核通过」,此态不在原始需求清单中,需确认是否可接受。
  3. views/sponsor/Meetings.vue 是死代码:路由未引用(sponsor 复用共享 meetings/Meetings.vue),内含非法枚举 AWAITING_SPONSOR、不存在的 auditStatus/auditOpinion 字段、直接写 current_stage 的「结算/解冻」逻辑。建议删除。
  4. current_stage 是派生缓存:仅 StageDeriver + 控制器转换端点 + 调度器可写,任何业务代码不得直接 update current_stage(写了也会被下次推导覆盖)。
  5. biz_meeting_audit_log.current_stage 遗留列:新 mapper 已改用 4 角色列,旧列未删(按约定未执行 DROP)。
  6. 未编译:本次开发未跑 mvn install / npm buildStageDeriver 是新增 @Component,走 .m2 jar 加载时需先 mvn install 才生效。
  7. 前端/后端镜像同步meetingStage.jsStageDeriver 必须同步修改,改推导规则时两处一起改。

11. 附:涉及文件速查

后端ry-api/ruoyi-business/.../business/

  • service/StageDeriver.java(新增)
  • controller/BizMeetingController.java
  • scheduler/MeetingStageScheduler.java
  • domain/BizMeeting.java
  • mapper/BizMeetingMapper.java + resources/mapper/business/BizMeetingMapper.xml
  • mapper/BizMeetingAuditLogMapper.java + .xml
  • service/impl/BizMeetingServiceImpl.java
  • controller/BizDashboardController.java

前端ry-vue3/src/

  • utils/meetingStage.js
  • views/meetings/Meetings.vue
  • views/meetings/MeetingDetail.vue
  • views/executor/Meetings.vue
  • views/doctor/Meetings.vue

12. 近期变更:凭证流程移除(2026-08-24)

改动动机:执行方不再保存/提交/审核凭证,凭证改为由合规人员在结算时一并上传。

  1. 移除执行方凭证链路:删除 POST /{meetingId}/submit-voucher;审核接口不再支持 auditType=VOUCHER/BOTH 多选,审核链路只剩材料一条(auditType 固定 MATERIAL)。
  2. 结算二合一POST /{meetingId}/settle 改为「上传凭证 + 结算」合并。合规/管理员点击结算时,必须同时上传劳务凭证(LV_PAYMENT)与会务凭证(SV_PAYMENT),缺任一报错「请上传凭证」;前置校验:材料 APPROVED + fee_calc_status=1 + 未结算。
  3. 凭证落库:凭证作为 biz_meeting_materialLABOR_VOUCHER/SERVICE_VOUCHER)保存,不参与费用汇总;结算后凭证只读可见。
  4. DDLbiz_meeting 已 DROP 三个死列 voucher_audit_stage / voucher_audit_time / voucher_compliance_approved,对应 domain/mapper/service 字段与映射同步删除。