feat: 会议劳务/会务材料分轨审核 + 注册页标题 + 下拉空值修复

会议材料分轨 (material_audit_stage 单轨 → labor/service 双轨):
- biz_meeting: labor_audit_stage/labor_compliance_approved + service_audit_stage/service_compliance_approved 四列替换原单轨两列
- StageDeriver: 单轨状态机(R/N/C0/C1/A) + 角色优先级选代表轨 + 会议物理态取两轨最小进度
- MeetingAuditStageEnum / 前端 meetingStage.js 镜像: 分轨推导 + 颜色跟随角色展示阶段(避免文案与颜色错位)
- BizMeetingController/ServiceImpl/AuditLog + mapper + MeetingStageScheduler 同步分轨字段
- MeetingDetail/Meetings/doctor/executor Meetings/ManagerProjectDetail/ManagerProjectsAssign/PublicityDetail 同步双轨

其它:
- 注册页 (执行单位/专家/支持单位) 加居中页标题
- DoctorDeptSelect/DoctorTitleSelect 空字符串统一 null, 避免挂载必填项飘红
- BizOrgMapper sponsor/executor options 加 status 字段
- sponsor/NewPerson 编辑态 orgName 只读
This commit is contained in:
郭庆泰
2026-08-26 18:13:22 +08:00
parent b5b3e3a213
commit b0256ccac3
25 changed files with 752 additions and 449 deletions
@@ -31,6 +31,7 @@ import com.ruoyi.business.notify.BizNotifyService;
import com.ruoyi.business.service.IBizMeetingService;
import com.ruoyi.business.service.IBizOrgService;
import com.ruoyi.business.service.IBizProjectService;
import com.ruoyi.business.service.IBizProjectSponsorAssignService;
import com.ruoyi.business.service.IBizMeetingAttendeeService;
import com.ruoyi.business.service.StageDeriver;
import com.ruoyi.business.service.PosterService;
@@ -63,6 +64,8 @@ public class BizMeetingController extends BaseController {
@Autowired
private IBizProjectService bizProjectService;
@Autowired
private IBizProjectSponsorAssignService bizProjectSponsorAssignService;
@Autowired
private IBizOrgService bizOrgService;
@Autowired
private StageDeriver stageDeriver;
@@ -87,12 +90,12 @@ public class BizMeetingController extends BaseController {
bizMeeting.getParams().put("sponsorAdminUserId", uid);
}
}
// executor 数据权限: 只看"我的项目"下的会议 (与项目列表 selectExecutorList/selectExecutorStaffList 同源).
// MAIN 走 biz_project_assign (execution_unit_id), SUB(执行人) 走 biz_project_executor_assign.staff_user_id.
// executor 数据权限: MAIN 看"我的执行单位"下的会议 (走 biz_project_assign.execution_unit_id);
// SUB(执行人) 只看"本人创建的会议" (按 create_by 过滤, 不再看整个分配项目的会议).
else if ("executor".equals(roleType)) {
SysUser current = uid == null ? null : sysUserMapper.selectUserById(uid);
if (current != null && "SUB".equals(current.getAccountType())) {
bizMeeting.getParams().put("executorStaffUserId", uid);
bizMeeting.getParams().put("executorCreatorUsername", SecurityUtils.getUsername());
} else {
bizMeeting.getParams().put("executorUserId", uid);
}
@@ -233,18 +236,19 @@ public class BizMeetingController extends BaseController {
// ===================================================================
/**
* 执行人员提交材料
* 执行人员提交材料 (分轨: 劳务 LABOR / 会务 SERVICE 可多选, 每轨独立提交).
* <ul>
* <li>校验 1: 当前用户是该会议执行方 (项目级归属, 强校验)</li>
* <li>校验 2: 已执行 (is_executed=1) 且未冻结</li>
* <li>校验 3: material_audit_stage ∈ {NOT_SUBMITTED, REJECTED}</li>
* <li>校验 4: biz_meeting_material 劳务(L_*)与会务(M_*)各至少 1 条, 不必全部子类型填满</li>
* <li>校验 3: 所选轨 audit_stage ∈ {NOT_SUBMITTED, REJECTED}</li>
* <li>校验 4: 所选轨在 biz_meeting_material 各至少 1 条 (劳务 L_* / 会务 M_*)</li>
* </ul>
* 通过后 material → SUBMITTED (compliance_approved=0), 记 audit_log.
* body: { "types": ["LABOR","SERVICE"] }
* 通过后该轨 → SUBMITTED (compliance_approved=0), 每轨记一条 audit_log.
*/
@Log(title = "会议审核", businessType = BusinessType.UPDATE)
@PostMapping("/{meetingId}/submit-material")
public AjaxResult submitMaterial(@PathVariable("meetingId") Long meetingId) {
public AjaxResult submitMaterial(@PathVariable("meetingId") Long meetingId, @RequestBody SubmitBody body) {
BizMeeting m = bizMeetingService.getById(meetingId);
if (m == null) throw new ServiceException("会议不存在");
@@ -254,33 +258,52 @@ public class BizMeetingController extends BaseController {
}
if (!isExecuted(m)) throw new ServiceException("会议尚未执行, 不能提交材料");
if (isFrozen(m)) throw new ServiceException("会议已冻结, 不能提交材料");
String stage = m.getMaterialAuditStage();
if (!"NOT_SUBMITTED".equals(stage) && !"REJECTED".equals(stage)) {
throw new ServiceException("当前阶段 (" + stage + ") 不允许提交材料");
List<String> types = body == null ? null : body.getTypes();
if (types == null || types.isEmpty()) throw new ServiceException("请选择要提交材料类型");
for (String type : types) {
if (!"LABOR".equals(type) && !"SERVICE".equals(type)) throw new ServiceException("未知材料类型: " + type);
}
List<BizMeetingMaterial> mats = bizMeetingMaterialService.selectByMeetingId(meetingId);
boolean hasLabor = mats.stream().anyMatch(x -> x.getSubType() != null && x.getSubType().startsWith("L_"));
boolean hasService = mats.stream().anyMatch(x -> x.getSubType() != null && x.getSubType().startsWith("M_"));
if (!hasLabor || !hasService) {
throw new ServiceException("劳务材料和会务材料各至少上传一条");
for (String type : types) {
String label = "LABOR".equals(type) ? "劳务材料" : "会务材料";
String stage = "LABOR".equals(type) ? m.getLaborAuditStage() : m.getServiceAuditStage();
if (!"NOT_SUBMITTED".equals(stage) && !"REJECTED".equals(stage)) {
throw new ServiceException(label + "当前阶段 (" + stage + ") 不允许提交");
}
boolean has = "LABOR".equals(type) ? hasLabor : hasService;
if (!has) throw new ServiceException(label + "至少上传一条");
}
m.setMaterialAuditStage("SUBMITTED");
m.setMaterialComplianceApproved(0);
for (String type : types) {
if ("LABOR".equals(type)) {
m.setLaborAuditStage("SUBMITTED");
m.setLaborComplianceApproved(0);
} else {
m.setServiceAuditStage("SUBMITTED");
m.setServiceComplianceApproved(0);
}
}
m.setMaterialAuditTime(new Date());
m.setCurrentStage(stageDeriver.derivePhysicalStage(m));
bizMeetingService.updateByPrimaryKey(m);
appendAuditLog(m, "MATERIAL", "SUBMITTED", "执行人员提交材料");
for (String type : types) {
appendAuditLog(m, "MATERIAL", "SUBMITTED", "执行人员提交" + ("LABOR".equals(type) ? "劳务" : "会务") + "材料", type);
}
return success("SUBMITTED");
}
/**
* 合规审核 (role_type=manager), 材料两级审核中的第一级.
* 合规审核 (role_type=manager), 材料两级审核中的第一级 (分轨).
* <p>
* body: { "approved": true|false, "opinion": "..." }
* <p>合规审中判据: material_audit_stage=SUBMITTED 且 compliance_approved=0.
* 通过 → compliance_approved=1 (转支持方审), 拒绝 → REJECTED (退回执行方).
* body: { "items": [{ "type": "LABOR|SERVICE", "approved": true|false }], "opinion": "..." }
* <p>逐轨审核: 该轨 C0 (SUBMITTED 且 compliance_approved=0) 才可审;
* 通过 → compliance_approved=1 (转支持方审), 拒绝 → REJECTED (退回执行方).
* 一轨给通过、另一轨给拒绝 在同一 body 里各自表达.
*/
@Log(title = "会议审核", businessType = BusinessType.UPDATE)
@PostMapping("/{meetingId}/audit-compliance")
@@ -290,17 +313,25 @@ public class BizMeetingController extends BaseController {
}
BizMeeting m = bizMeetingService.getById(meetingId);
if (m == null) throw new ServiceException("会议不存在");
boolean approved = Boolean.TRUE.equals(body.getApproved());
if (!approved && (body.getOpinion() == null || body.getOpinion().isEmpty())) {
throw new ServiceException("拒绝时意见不能为空");
List<AuditItem> items = body == null ? null : body.getItems();
if (items == null || items.isEmpty()) throw new ServiceException("请选择要审核的材料类型");
for (AuditItem it : items) {
if (!"LABOR".equals(it.getType()) && !"SERVICE".equals(it.getType())) throw new ServiceException("未知材料类型: " + it.getType());
if (!Boolean.TRUE.equals(it.getApproved()) && (body.getOpinion() == null || body.getOpinion().isEmpty())) {
throw new ServiceException("拒绝时意见不能为空");
}
}
return success(doComplianceAudit(m, approved, body.getOpinion()));
for (AuditItem it : items) {
doComplianceAuditTrack(m, it.getType(), Boolean.TRUE.equals(it.getApproved()), body.getOpinion());
}
return success("OK");
}
/**
* 批量合规审核 (role_type=manager): 一次对多个会议执行材料一级审核.
* <p>
* body: { "meetingIds": [..], "approved": true|false, "opinion": "..." }
* <p>分轨简化: 一个结果应用到每个会议所有处于合规审中 (C0) 的轨 (劳务/会务), 不做逐轨不同结果.
* <p>best-effort: 逐条审核, 状态不符的会议跳过并回执失败原因, 不整批回滚.
*/
@Log(title = "会议审核", businessType = BusinessType.UPDATE)
@@ -325,7 +356,7 @@ public class BizMeetingController extends BaseController {
try {
BizMeeting m = bizMeetingService.getById(meetingId);
if (m == null) throw new ServiceException("会议不存在");
doComplianceAudit(m, approved, body.getOpinion());
doComplianceAuditAllC0(m, approved, body.getOpinion());
successIds.add(meetingId);
} catch (ServiceException e) {
Map<String, Object> f = new HashMap<>();
@@ -343,35 +374,53 @@ public class BizMeetingController extends BaseController {
return success(data);
}
/** 某轨是否处于合规审中 (C0): SUBMITTED 且 compliance_approved != 1. */
private static boolean isCompliancePending(String stage, Integer compliance) {
return "SUBMITTED".equals(stage) && (compliance == null || compliance != 1);
}
/**
* 内部: 合规审核核心流转 (阶段校验 + 状态写入 + audit_log). 单条/批量共用.
* 内部: 合规审核单轨核心流转 (阶段校验 + 状态写入 + audit_log). 单条/批量共用.
*/
private String doComplianceAudit(BizMeeting m, boolean approved, String opinion) {
Integer compliance = m.getMaterialComplianceApproved();
if (!"SUBMITTED".equals(m.getMaterialAuditStage()) || (compliance != null && compliance == 1)) {
throw new ServiceException("当前阶段不允许合规审核");
private void doComplianceAuditTrack(BizMeeting m, String type, boolean approved, String opinion) {
boolean labor = "LABOR".equals(type);
String stage = labor ? m.getLaborAuditStage() : m.getServiceAuditStage();
Integer compliance = labor ? m.getLaborComplianceApproved() : m.getServiceComplianceApproved();
if (!isCompliancePending(stage, compliance)) {
throw new ServiceException((labor ? "劳务材料" : "会务材料") + "当前阶段不允许合规审核");
}
String result = approved ? "APPROVED" : "REJECTED";
if (approved) {
m.setMaterialComplianceApproved(1);
if (labor) m.setLaborComplianceApproved(1); else m.setServiceComplianceApproved(1);
} else {
m.setMaterialAuditStage("REJECTED");
if (labor) m.setLaborAuditStage("REJECTED"); else m.setServiceAuditStage("REJECTED");
// 退回 → 提交截止时间重新计算 (now + 项目天数)
m.setSubmitDeadline(computeSubmitDeadline(m.getProjectId(), new Date()));
}
m.setMaterialAuditTime(new Date());
m.setCurrentStage(stageDeriver.derivePhysicalStage(m));
bizMeetingService.updateByPrimaryKey(m);
appendAuditLog(m, "MATERIAL", result, opinion);
return result;
appendAuditLog(m, "MATERIAL", approved ? "APPROVED" : "REJECTED", opinion, type);
}
/**
* 支持方(监察员) 审核, 材料两级审核中的第二级.
* 内部: 批量合规审核 — 一个结果应用到该会议所有处于合规审中 (C0) 的轨 (简化, 不做逐轨不同结果).
*/
private void doComplianceAuditAllC0(BizMeeting m, boolean approved, String opinion) {
List<String> targets = new ArrayList<>();
if (isCompliancePending(m.getLaborAuditStage(), m.getLaborComplianceApproved())) targets.add("LABOR");
if (isCompliancePending(m.getServiceAuditStage(), m.getServiceComplianceApproved())) targets.add("SERVICE");
if (targets.isEmpty()) throw new ServiceException("当前阶段不允许合规审核");
for (String type : targets) {
doComplianceAuditTrack(m, type, approved, opinion);
}
}
/**
* 支持方(监察员) 审核, 材料两级审核中的第二级 (分轨).
* <p>
* body: { "approved": true|false, "opinion": "..." }
* <p>支持方审中判据: material_audit_stage=SUBMITTED 且 compliance_approved=1.
* 通过 → APPROVED (一并写监管意见), 拒绝 → REJECTED (退回执行方).
* body: { "items": [{ "type": "LABOR|SERVICE", "approved": true|false }], "opinion": "..." }
* <p>逐轨审核: 该轨 C1 (SUBMITTED 且 compliance_approved=1) 才可审;
* 通过 → APPROVED (任一轨通过即写监管意见), 拒绝 → REJECTED (退回执行方 + 通知).
*/
@Log(title = "会议审核", businessType = BusinessType.UPDATE)
@PostMapping("/{meetingId}/audit-supervision")
@@ -382,27 +431,57 @@ public class BizMeetingController extends BaseController {
BizMeeting m = bizMeetingService.getById(meetingId);
if (m == null) throw new ServiceException("会议不存在");
// 授权: 监察员 (biz_meeting_supervisor.sponsor_org_id) 或 支持方企业 (biz_project.sponsor_org_id) 均可审
// 授权 (与列表可见性同源, 原则: 能看到就能监察):
// 1) 项目级监察员 (biz_project_sponsor_assign.monitor_user_id = 当前 user_id) — SUB 监察员列表同源
// 2) 会议级监察员机构 (biz_meeting_supervisor.sponsor_org_id = 我的 org_id)
// 3) 支持方主账号 (biz_project.sponsor_org_id = 我的 org_id)
BizProject project = m.getProjectId() == null ? null : bizProjectService.getById(m.getProjectId());
boolean isProjectMonitor = m.getProjectId() != null && userId != null
&& bizProjectSponsorAssignService.listByProjectId(String.valueOf(m.getProjectId())).stream()
.anyMatch(a -> userId.equals(a.getMonitorUserId()));
boolean isSupervisor = bizMeetingSupervisorService.selectByMeetingId(meetingId).stream()
.anyMatch(s -> myOrgId != null && myOrgId.equals(s.getSponsorOrgId()));
BizProject project = m.getProjectId() == null ? null : bizProjectService.getById(m.getProjectId());
boolean isSponsorMain = project != null && myOrgId != null && myOrgId.equals(project.getSponsorOrgId());
if (!isSupervisor && !isSponsorMain) {
if (!isProjectMonitor && !isSupervisor && !isSponsorMain) {
throw new ServiceException("您不是该会议监察员, 无权监察");
}
boolean approved = Boolean.TRUE.equals(body.getApproved());
if (!approved && (body.getOpinion() == null || body.getOpinion().isEmpty())) {
throw new ServiceException("拒绝时意见不能为空");
List<AuditItem> items = body == null ? null : body.getItems();
if (items == null || items.isEmpty()) throw new ServiceException("请选择要审核的材料类型");
boolean anyRejected = false;
for (AuditItem it : items) {
if (!"LABOR".equals(it.getType()) && !"SERVICE".equals(it.getType())) throw new ServiceException("未知材料类型: " + it.getType());
if (!Boolean.TRUE.equals(it.getApproved()) && (body.getOpinion() == null || body.getOpinion().isEmpty())) {
throw new ServiceException("拒绝时意见不能为空");
}
}
for (AuditItem it : items) {
boolean approved = Boolean.TRUE.equals(it.getApproved());
doSupervisionAuditTrack(m, it.getType(), approved, body.getOpinion());
if (!approved) anyRejected = true;
}
Integer compliance = m.getMaterialComplianceApproved();
if (!"SUBMITTED".equals(m.getMaterialAuditStage()) || compliance == null || compliance != 1) {
throw new ServiceException("当前阶段不允许监察审核");
// 退回 → 通知执行方 (待整改 + 说明, 任一轨被拒即通知一次)
if (anyRejected) {
for (BizMeetingExecutor e : bizMeetingExecutorService.selectByMeetingId(meetingId)) {
bizNotifyService.meetingSupervisionRejected(resolveMainUserIdByOrgId(e.getExecutorOrgId()), meetingId, m.getMeetingName(), body.getOpinion());
}
}
return success("OK");
}
String result = approved ? "APPROVED" : "REJECTED";
m.setMaterialAuditStage(approved ? "APPROVED" : "REJECTED");
/**
* 内部: 支持方审核单轨核心流转 (阶段校验 + 状态写入 + audit_log).
*/
private void doSupervisionAuditTrack(BizMeeting m, String type, boolean approved, String opinion) {
boolean labor = "LABOR".equals(type);
String stage = labor ? m.getLaborAuditStage() : m.getServiceAuditStage();
Integer compliance = labor ? m.getLaborComplianceApproved() : m.getServiceComplianceApproved();
if (!"SUBMITTED".equals(stage) || compliance == null || compliance != 1) {
throw new ServiceException((labor ? "劳务材料" : "会务材料") + "当前阶段不允许监察审核");
}
if (labor) m.setLaborAuditStage(approved ? "APPROVED" : "REJECTED");
else m.setServiceAuditStage(approved ? "APPROVED" : "REJECTED");
// 退回 → 提交截止时间重新计算 (now + 项目天数)
if (!approved) {
m.setSubmitDeadline(computeSubmitDeadline(m.getProjectId(), new Date()));
@@ -410,21 +489,13 @@ public class BizMeetingController extends BaseController {
m.setMaterialAuditTime(new Date());
// 材料通过 → 写监管意见 (支持方的书面意见)
if (approved) {
m.setSupervisionOpinion(body.getOpinion());
m.setSupervisionOpinion(opinion);
m.setSupervisionBy(SecurityUtils.getUsername());
m.setSupervisionTime(new Date());
}
m.setCurrentStage(stageDeriver.derivePhysicalStage(m));
bizMeetingService.updateByPrimaryKey(m);
appendAuditLog(m, "MATERIAL", result, body.getOpinion());
// 退回 → 通知执行方 (待整改 + 说明)
if (!approved) {
for (BizMeetingExecutor e : bizMeetingExecutorService.selectByMeetingId(meetingId)) {
bizNotifyService.meetingSupervisionRejected(resolveMainUserIdByOrgId(e.getExecutorOrgId()), meetingId, m.getMeetingName(), body.getOpinion());
}
}
return success(result);
appendAuditLog(m, "MATERIAL", approved ? "APPROVED" : "REJECTED", opinion, type);
}
/**
@@ -443,8 +514,8 @@ public class BizMeetingController extends BaseController {
}
BizMeeting m = bizMeetingService.getById(meetingId);
if (m == null) throw new ServiceException("会议不存在");
if (!"APPROVED".equals(m.getMaterialAuditStage())) {
throw new ServiceException("材料审核通过后才能结算");
if (!"APPROVED".equals(m.getLaborAuditStage()) || !"APPROVED".equals(m.getServiceAuditStage())) {
throw new ServiceException("劳务与会务材料审核通过后才能结算");
}
if (isSettled(m)) throw new ServiceException("会议已结算");
// 费用未汇总完 (fee_calc_status=0) 禁止结算: 此时 labor_fee/meeting_fee 可能为旧值/0, 直接回写会污染项目金额.
@@ -465,7 +536,7 @@ public class BizMeetingController extends BaseController {
m.setSettleTime(new Date());
m.setCurrentStage(stageDeriver.derivePhysicalStage(m));
bizMeetingService.updateByPrimaryKey(m);
appendAuditLog(m, "SETTLE", "APPROVED", "会议结算");
appendAuditLog(m, "SETTLE", "APPROVED", "会议结算", null);
return success("SETTLED");
}
@@ -487,7 +558,7 @@ public class BizMeetingController extends BaseController {
m.setFinishTime(new Date());
m.setCurrentStage(stageDeriver.derivePhysicalStage(m));
bizMeetingService.updateByPrimaryKey(m);
appendAuditLog(m, "FINISH", "APPROVED", "会议完结");
appendAuditLog(m, "FINISH", "APPROVED", "会议完结", null);
return success("FINISHED");
}
@@ -559,13 +630,14 @@ public class BizMeetingController extends BaseController {
/**
* 内部: 写一条 audit_log (4 列角色展示状态由 post-transition 事实推导).
*/
private void appendAuditLog(BizMeeting m, String auditType, String result, String opinion) {
private void appendAuditLog(BizMeeting m, String auditType, String result, String opinion, String materialType) {
BizMeetingAuditLog log = new BizMeetingAuditLog();
log.setMeetingId(m.getMeetingId());
log.setAuditor(SecurityUtils.getUsername());
log.setAuditType(auditType);
log.setAuditResult(result);
log.setOpinion(opinion);
log.setMaterialType(materialType);
log.setCreateTime(new Date());
log.setAuditTime(new Date());
log.setExecutorStage(stageDeriver.deriveDisplay("executor", m));
@@ -575,16 +647,33 @@ public class BizMeetingController extends BaseController {
bizMeetingAuditLogService.insert(log);
}
/** request body for audit endpoints */
/** request body for audit endpoints (逐轨: 每项一个材料类型 + 通过/拒绝) */
public static class AuditBody {
private Boolean approved; // true=通过 false=拒绝
private String opinion; // 意见
public Boolean getApproved() { return approved; }
public void setApproved(Boolean approved) { this.approved = approved; }
private List<AuditItem> items; // 逐轨审核项
private String opinion; // 意见 (拒绝时必填)
public List<AuditItem> getItems() { return items; }
public void setItems(List<AuditItem> items) { this.items = items; }
public String getOpinion() { return opinion; }
public void setOpinion(String opinion) { this.opinion = opinion; }
}
/** 单轨审核项 */
public static class AuditItem {
private String type; // LABOR / SERVICE
private Boolean approved; // true=通过 false=拒绝
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public Boolean getApproved() { return approved; }
public void setApproved(Boolean approved) { this.approved = approved; }
}
/** request body for submit-material (分轨多选) */
public static class SubmitBody {
private List<String> types; // ["LABOR","SERVICE"]
public List<String> getTypes() { return types; }
public void setTypes(List<String> types) { this.types = types; }
}
/** request body for batch audit endpoint */
public static class BatchAuditBody {
private List<Long> meetingIds;
@@ -72,8 +72,10 @@ public class BizMeeting extends BaseEntity {
/** 监察时间 (与 DB datetime 对齐) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date supervisionTime;
/** 材料审核阶段 (NOT_SUBMITTED/SUBMITTED/APPROVED/REJECTED, 见 MeetingAuditStageEnum) */
private String materialAuditStage;
/** 劳务材料审核阶段 (NOT_SUBMITTED/SUBMITTED/APPROVED/REJECTED, 见 MeetingAuditStageEnum) */
private String laborAuditStage;
/** 会务材料审核阶段 (NOT_SUBMITTED/SUBMITTED/APPROVED/REJECTED, 见 MeetingAuditStageEnum) */
private String serviceAuditStage;
/** 是否执行 0否1是 (会议开始时间到, scheduler 置1) */
private Integer isExecuted;
/** 执行时间 */
@@ -97,8 +99,10 @@ public class BizMeeting extends BaseEntity {
/** 材料最近一次审核动作时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date materialAuditTime;
/** 材料合规是否已通过 0否1是 (区分 SUBMITTED 内合规审/支持方审) */
private Integer materialComplianceApproved;
/** 劳务材料合规是否已通过 0否1是 (区分 SUBMITTED 内合规审/支持方审) */
private Integer laborComplianceApproved;
/** 会务材料合规是否已通过 0否1是 (区分 SUBMITTED 内合规审/支持方审) */
private Integer serviceComplianceApproved;
/** 提交截止时间 (建会 = end_time + 项目 submit_deadline_days 天; 退回/解冻 = now + 天数; null = 项目未设天数, 永不冻结) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date submitDeadline;
@@ -189,8 +193,10 @@ public class BizMeeting extends BaseEntity {
public void setPosterUrl(String posterUrl) { this.posterUrl = posterUrl; }
public String getLaborSigned() { return laborSigned; }
public void setLaborSigned(String laborSigned) { this.laborSigned = laborSigned; }
public String getMaterialAuditStage() { return materialAuditStage; }
public void setMaterialAuditStage(String materialAuditStage) { this.materialAuditStage = materialAuditStage; }
public String getLaborAuditStage() { return laborAuditStage; }
public void setLaborAuditStage(String laborAuditStage) { this.laborAuditStage = laborAuditStage; }
public String getServiceAuditStage() { return serviceAuditStage; }
public void setServiceAuditStage(String serviceAuditStage) { this.serviceAuditStage = serviceAuditStage; }
public Integer getIsExecuted() { return isExecuted; }
public void setIsExecuted(Integer isExecuted) { this.isExecuted = isExecuted; }
public Date getExecuteTime() { return executeTime; }
@@ -209,8 +215,10 @@ public class BizMeeting extends BaseEntity {
public void setFreezeTime(Date freezeTime) { this.freezeTime = freezeTime; }
public Date getMaterialAuditTime() { return materialAuditTime; }
public void setMaterialAuditTime(Date materialAuditTime) { this.materialAuditTime = materialAuditTime; }
public Integer getMaterialComplianceApproved() { return materialComplianceApproved; }
public void setMaterialComplianceApproved(Integer materialComplianceApproved) { this.materialComplianceApproved = materialComplianceApproved; }
public Integer getLaborComplianceApproved() { return laborComplianceApproved; }
public void setLaborComplianceApproved(Integer laborComplianceApproved) { this.laborComplianceApproved = laborComplianceApproved; }
public Integer getServiceComplianceApproved() { return serviceComplianceApproved; }
public void setServiceComplianceApproved(Integer serviceComplianceApproved) { this.serviceComplianceApproved = serviceComplianceApproved; }
public Date getSubmitDeadline() { return submitDeadline; }
public void setSubmitDeadline(Date submitDeadline) { this.submitDeadline = submitDeadline; }
public Long getUserId() { return userId; }
@@ -7,7 +7,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
* 会议审核流程日志对象 biz_meeting_audit_log
* <p>
* 记录会议审核的每一次流转: 谁、什么时间、什么意见、当前阶段.
* 与 biz_meeting.material_audit_stage 配合, 还原审核轨迹.
* 与 biz_meeting 劳务/会务两轨 audit_stage 配合, 还原审核轨迹.
*/
public class BizMeetingAuditLog {
@@ -51,6 +51,9 @@ public class BizMeetingAuditLog {
/** 审核结果 (APPROVED / REJECTED) */
private String auditResult;
/** 材料类型 (LABOR / SERVICE; NULL = 会议级操作如结算/完结, 或历史数据) */
private String materialType;
/** 软删除标记 0否1是 (admin 删除会议时级联置1, 查询需 WHERE is_deleted=0) */
private Integer isDeleted;
@@ -90,6 +93,9 @@ public class BizMeetingAuditLog {
public String getAuditResult() { return auditResult; }
public void setAuditResult(String auditResult) { this.auditResult = auditResult; }
public String getMaterialType() { return materialType; }
public void setMaterialType(String materialType) { this.materialType = materialType; }
public Integer getIsDeleted() { return isDeleted; }
public void setIsDeleted(Integer isDeleted) { this.isDeleted = isDeleted; }
}
@@ -10,12 +10,11 @@ import lombok.extern.slf4j.Slf4j;
* 会议事实/阶段 自动流转调度器 (每分钟一次).
* <p>
* 状态机已改为「事实 + 推导」模型 (见 {@code StageDeriver}): biz_meeting 存事实
* (is_executed / is_frozen / material_audit_stage / 审核时间 …),
* 各角色看到的阶段名称由推导得出. 本调度器只负责「时间驱动」的类事实落地:
* (is_executed / is_frozen / 劳务·会务两轨 audit_stage / 审核时间 …),
* 各角色看到的阶段名称由推导得出. 本调度器只负责「时间驱动」的类事实落地:
* <pre>
* 1) start_time 到 → is_executed=1 (执行中)
* 2) end_time + submit_deadline_days 到 且 material 未提交 → is_frozen=1 (冻结)
* 3) material 通过 且 审核时间过 24h → 待结算 (current_stage 缓存翻 AWAITING_SETTLEMENT)
* 2) submit_deadline 到 且 任一轨未提交/已退回 → is_frozen=1 (冻结)
* </pre>
* 其余阶段流转由执行方提交 / 审核动作触发 (BizMeetingController), 不在此调度器范围.
* <p>
@@ -29,7 +28,7 @@ public class MeetingStageScheduler
private BizMeetingMapper meetingMapper;
/**
* 每分钟: start_time 已过 且 material 未提交 且未执行 → 置执行中.
* 每分钟: start_time 已过 且 劳务·会务两轨均未提交 且未执行 → 置执行中.
*/
@Scheduled(fixedRate = 60_000, initialDelay = 30_000)
public void markExecuted()
@@ -49,7 +48,7 @@ public class MeetingStageScheduler
}
/**
* 每分钟: material 未提交 且 提交截止时间 (end_time + submit_deadline_days) 已过 → 冻结.
* 每分钟: 任一轨未提交/已退回 且 提交截止时间已过 → 冻结 (会议级).
*/
@Scheduled(fixedRate = 60_000, initialDelay = 30_000)
public void markFrozen()
@@ -7,11 +7,14 @@ import com.ruoyi.business.domain.BizMeeting;
* 会议阶段推导器 (单一可信源).
* <p>
* 事实与展示分离: {@code biz_meeting} 只存事实 (is_executed/is_settled/is_finished/is_frozen
* + material_audit_stage + 审核时间 + compliance_approved), 各角色看到的
* + 劳务/会务两轨各自 audit_stage + compliance_approved + 审核时间), 各角色看到的
* 「阶段名称」由本类实时计算.
* <p>
* 劳务/会务材料分轨后, 每条轨道独立走 N/C0/C1/A/R 状态机, 会议级阶段由两轨汇合:
* <ul>
* <li>{@link #derivePhysicalStage(BizMeeting)}: 10 值物理阶段 (current_stage 缓存 + 列表筛选).</li>
* <li>{@link #deriveDisplay(String, BizMeeting)}: 各角色展示名 (audit_log 4 列 + 前端镜像).</li>
* <li>{@link #derivePhysicalStage(BizMeeting)}: 10 值物理阶段 = 两轨最小进度 (current_stage 缓存 + 列表筛选).</li>
* <li>{@link #deriveDisplay(String, BizMeeting)}: 各角色展示名 (audit_log 4 列 + 前端镜像),
* 按角色优先级选代表轨, 再用单轨措辞渲染.</li>
* </ul>
*
* @author guoju
@@ -24,10 +27,29 @@ public class StageDeriver
return v != null && v == 1;
}
/** 单轨状态码: R=0, N=1, C0=2, C1=3, A=4 (数值越小 = 流程越靠前). */
private static int stateOf(String stage, Integer complianceApproved)
{
if ("REJECTED".equals(stage)) return 0;
if ("SUBMITTED".equals(stage)) return t(complianceApproved) ? 3 : 2;
if ("APPROVED".equals(stage)) return 4;
return 1; // NOT_SUBMITTED (或 null 兜底)
}
private static int laborState(BizMeeting m)
{
return stateOf(m.getLaborAuditStage(), m.getLaborComplianceApproved());
}
private static int serviceState(BizMeeting m)
{
return stateOf(m.getServiceAuditStage(), m.getServiceComplianceApproved());
}
/**
* 10 值物理阶段 (NOT_STARTED/RUNNING/AWAITING_COMPLIANCE/AWAITING_SUPERVISION/
* SUPERVISION_APPROVED/RECTIFYING/AWAITING_SETTLEMENT/SETTLED/FINISHED/FROZEN), 由事实推导.
* 用于 current_stage 缓存 (列表筛选按物理态精确匹配).
* 两轨取最小进度 (流程最靠前的一轨决定会议物理态), 用于 current_stage 缓存 (列表筛选按物理态精确匹配).
*/
public String derivePhysicalStage(BizMeeting m)
{
@@ -35,26 +57,25 @@ public class StageDeriver
if (t(m.getIsFinished())) return "FINISHED";
if (t(m.getIsSettled())) return "SETTLED";
String material = m.getMaterialAuditStage();
if ("REJECTED".equals(material)) return "RECTIFYING";
if ("APPROVED".equals(material))
int s = Math.min(laborState(m), serviceState(m));
switch (s)
{
return "AWAITING_SETTLEMENT";
case 0: return "RECTIFYING";
case 2: return "AWAITING_COMPLIANCE";
case 3: return "AWAITING_SUPERVISION";
case 4: return "AWAITING_SETTLEMENT";
default: return t(m.getIsExecuted()) ? "RUNNING" : "NOT_STARTED"; // s = 1 (N)
}
if ("SUBMITTED".equals(material))
{
return t(m.getMaterialComplianceApproved()) ? "AWAITING_SUPERVISION" : "AWAITING_COMPLIANCE";
}
// NOT_SUBMITTED (或 null 兜底)
return t(m.getIsExecuted()) ? "RUNNING" : "NOT_STARTED";
}
/**
* 各角色展示阶段名. role ∈ {executor, sponsor, manager, admin}; doctor/expert 等非流程角色按 admin 中性.
* <p>
* 优先级自上而下命中即返回:
* 固定顶格: 冻结中 → 已完结 → 已结算. 否则按角色优先级选「代表轨」再渲染单轨措辞:
* <pre>
* 冻结 → 完结 → 已结算 → (材料) 审核驳回 → 审核通过 → (材料) APPROVED → SUBMITTED → NOT_SUBMITTED
* 默认 (executor/admin/doctor): 最小进度 (R &gt; N &gt; C0 &gt; C1 &gt; A)
* manager: 优先 C0(待合规审核) &gt; R &gt; N &gt; C1 &gt; A → 有活「待审核」, 无活按最早环节优先
* sponsor: 优先 C1(待支持方审核) &gt; R &gt; N &gt; C0 &gt; A → 有活「待审核」, 无活按最早环节优先
* </pre>
*/
public String deriveDisplay(String role, BizMeeting m)
@@ -63,44 +84,69 @@ public class StageDeriver
if (t(m.getIsFinished())) return "已完结";
if (t(m.getIsSettled())) return "已结算";
String material = m.getMaterialAuditStage();
int chosen = chooseState(role, laborState(m), serviceState(m));
return render(role, chosen, t(m.getIsExecuted()));
}
// 退回: 执行方看「已退回」, 其他方看「待整改」
if ("REJECTED".equals(material))
/** 按角色优先级选代表轨 (两轨中优先级更高的那轨). */
private static int chooseState(String role, int labor, int service)
{
if ("manager".equals(role))
{
return "executor".equals(role) ? "已退回" : "待整改";
return managerPriority(labor) <= managerPriority(service) ? labor : service;
}
// 材料已支持方通过 → 待结算 (支持方审通过即待结算, 不再有 24h 慢路径)
if ("APPROVED".equals(material))
if ("sponsor".equals(role))
{
return "待结算";
return sponsorPriority(labor) <= sponsorPriority(service) ? labor : service;
}
// 默认: 最小进度 (状态码本身即 R<N<C0<C1<A 顺序)
return Math.min(labor, service);
}
// 材料在审 (SUBMITTED)
if ("SUBMITTED".equals(material))
/** manager 优先级: C0=0, R=1, N=2, C1=3, A=4 (越小越优先). C0=有活最优先; 无活时按最早环节优先(R<N<C1<A). */
private static int managerPriority(int s)
{
switch (s)
{
if (t(m.getMaterialComplianceApproved()))
{
// 支持方审中
if ("manager".equals(role)) return "审核通过";
if ("sponsor".equals(role)) return "待审核";
return "待审核"; // executor / admin
}
else
{
// 合规审中
case 2: return 0; // C0 待合规审核 (有活)
case 0: return 1; // R 待整改 (无活时最早)
case 1: return 2; // N 未提交
case 3: return 3; // C1 待支持方审核
default: return 4; // A 已通过
}
}
/** sponsor 优先级: C1=0, R=1, N=2, C0=3, A=4 (越小越优先). C1=有活最优先; 无活时按最早环节优先(R<N<C0<A). */
private static int sponsorPriority(int s)
{
switch (s)
{
case 3: return 0; // C1 待支持方审核 (有活)
case 0: return 1; // R 待整改 (无活时最早)
case 1: return 2; // N 未提交
case 2: return 3; // C0 待合规审核
default: return 4; // A 已通过
}
}
/** 单轨措辞 (代表轨状态 + 角色 → 展示名). */
private static String render(String role, int s, boolean executed)
{
switch (s)
{
case 0: // R 退回
return "executor".equals(role) ? "已退回" : "待整改";
case 1: // N 未提交
if (!executed) return "未执行";
return "executor".equals(role) ? "执行中" : "已执行未传材料";
case 2: // C0 合规审中
if ("sponsor".equals(role)) return "已执行未传材料"; // 只读
if ("manager".equals(role)) return "待审核";
return "待审核"; // executor / admin
}
return "待审核"; // executor / manager / admin
case 3: // C1 支持方审中
if ("manager".equals(role)) return "审核通过";
return "待审核"; // executor / sponsor / admin
default: // 4 = A 已通过
return "待结算";
}
// 未提交
if (t(m.getIsExecuted()))
{
return "executor".equals(role) ? "执行中" : "已执行未传材料";
}
return "未执行";
}
}
@@ -61,8 +61,11 @@ public class BizMeetingServiceImpl implements IBizMeetingService
if (entity.getCurrentStage() == null || entity.getCurrentStage().isEmpty()) {
entity.setCurrentStage(BizMeetingStageEnum.NOT_STARTED.getCode());
}
if (entity.getMaterialAuditStage() == null || entity.getMaterialAuditStage().isEmpty()) {
entity.setMaterialAuditStage("NOT_SUBMITTED");
if (entity.getLaborAuditStage() == null || entity.getLaborAuditStage().isEmpty()) {
entity.setLaborAuditStage("NOT_SUBMITTED");
}
if (entity.getServiceAuditStage() == null || entity.getServiceAuditStage().isEmpty()) {
entity.setServiceAuditStage("NOT_SUBMITTED");
}
return bizMeetingMapper.insert(entity);
}
@@ -15,11 +15,12 @@
<result property="auditTime" column="audit_time" />
<result property="auditType" column="audit_type" />
<result property="auditResult" column="audit_result" />
<result property="materialType" column="material_type" />
<result property="isDeleted" column="is_deleted" />
</resultMap>
<sql id="selectFields">
select id, meeting_id, auditor, opinion, executor_stage, sponsor_stage, manager_stage, admin_stage, create_time, audit_time, audit_type, audit_result, is_deleted
select id, meeting_id, auditor, opinion, executor_stage, sponsor_stage, manager_stage, admin_stage, create_time, audit_time, audit_type, audit_result, material_type, is_deleted
from biz_meeting_audit_log
</sql>
@@ -52,6 +53,7 @@
<if test="auditTime != null">audit_time,</if>
<if test="auditType != null and auditType != ''">audit_type,</if>
<if test="auditResult != null and auditResult != ''">audit_result,</if>
<if test="materialType != null and materialType != ''">material_type,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="meetingId != null">#{meetingId},</if>
@@ -65,6 +67,7 @@
<if test="auditTime != null">#{auditTime},</if>
<if test="auditType != null and auditType != ''">#{auditType},</if>
<if test="auditResult != null and auditResult != ''">#{auditResult},</if>
<if test="materialType != null and materialType != ''">#{materialType},</if>
</trim>
</insert>
@@ -21,7 +21,8 @@
<result property="supervisionOpinion" column="supervision_opinion" />
<result property="supervisionBy" column="supervision_by" />
<result property="supervisionTime" column="supervision_time" />
<result property="materialAuditStage" column="material_audit_stage" />
<result property="laborAuditStage" column="labor_audit_stage" />
<result property="serviceAuditStage" column="service_audit_stage" />
<result property="isExecuted" column="is_executed" />
<result property="executeTime" column="execute_time" />
<result property="isSettled" column="is_settled" />
@@ -31,7 +32,8 @@
<result property="isFrozen" column="is_frozen" />
<result property="freezeTime" column="freeze_time" />
<result property="materialAuditTime" column="material_audit_time" />
<result property="materialComplianceApproved" column="material_compliance_approved" />
<result property="laborComplianceApproved" column="labor_compliance_approved" />
<result property="serviceComplianceApproved" column="service_compliance_approved" />
<result property="submitDeadline" column="submit_deadline" />
<result property="invitationUrl" column="invitation_url" />
<result property="projectInvitationUrl" column="project_invitation_url" />
@@ -51,7 +53,7 @@
<result property="isDeleted" column="is_deleted" />
</resultMap>
<sql id="selectFields">
meeting_id, business_id, project_id, execution_unit_id, project_no, project_name, meeting_name, period_no, total_periods, project_form, start_time, end_time, address, remark, current_stage, supervision_opinion, supervision_by, supervision_time, material_audit_stage, is_executed, execute_time, is_settled, settle_time, is_finished, finish_time, is_frozen, freeze_time, material_audit_time, material_compliance_approved, submit_deadline, invitation_url, schedule_url, poster_url, labor_signed, labor_fee, meeting_fee, total_fee, fee_calc_status, create_by, create_time, update_by, update_time, is_deleted
meeting_id, business_id, project_id, execution_unit_id, project_no, project_name, meeting_name, period_no, total_periods, project_form, start_time, end_time, address, remark, current_stage, supervision_opinion, supervision_by, supervision_time, labor_audit_stage, service_audit_stage, is_executed, execute_time, is_settled, settle_time, is_finished, finish_time, is_frozen, freeze_time, material_audit_time, labor_compliance_approved, service_compliance_approved, submit_deadline, invitation_url, schedule_url, poster_url, labor_signed, labor_fee, meeting_fee, total_fee, fee_calc_status, create_by, create_time, update_by, update_time, is_deleted
</sql>
<select id="selectByPrimaryKey" resultMap="BizMeetingResult" parameterType="Long">
select
@@ -93,16 +95,13 @@
and p2.is_finished = '1' and p2.open_status = 'N'
)
</if>
<!-- executor 数据权限: 只看"我的项目"下的会议. MAIN 走 biz_project_assign (execution_unit_id), SUB(执行人) 走 biz_project_executor_assign.staff_user_id -->
<!-- executor 数据权限: MAIN 看本执行单位项目下的会议 (biz_project_assign.execution_unit_id), SUB(执行人) 只看本人创建的会议 (create_by) -->
<if test="params.executorUserId != null">and project_id in (
select distinct a.project_id from biz_project_assign a
where a.is_deleted = 0
and a.execution_unit_id = (select org_id from biz_org where user_id = #{params.executorUserId} and org_type = 'executor')
)</if>
<if test="params.executorStaffUserId != null">and project_id in (
select distinct a.project_id from biz_project_executor_assign a
where a.is_deleted = 0 and a.staff_user_id = #{params.executorStaffUserId}
)</if>
<if test="params.executorCreatorUsername != null and params.executorCreatorUsername != ''">and create_by = #{params.executorCreatorUsername}</if>
<!-- 合规人员(manager) 数据权限: 只看本人创建项目的会议 -->
<if test="params.managerCreateUserId != null">and project_id in (
select project_id from biz_project where create_user_id = #{params.managerCreateUserId} and is_deleted = 0
@@ -131,7 +130,8 @@
<if test="supervisionOpinion != null and supervisionOpinion != ''">supervision_opinion,</if>
<if test="supervisionBy != null and supervisionBy != ''">supervision_by,</if>
<if test="supervisionTime != null">supervision_time,</if>
<if test="materialAuditStage != null and materialAuditStage != ''">material_audit_stage,</if>
<if test="laborAuditStage != null and laborAuditStage != ''">labor_audit_stage,</if>
<if test="serviceAuditStage != null and serviceAuditStage != ''">service_audit_stage,</if>
<if test="submitDeadline != null">submit_deadline,</if>
<if test="invitationUrl != null and invitationUrl != ''">invitation_url,</if>
<if test="scheduleUrl != null and scheduleUrl != ''">schedule_url,</if>
@@ -161,7 +161,8 @@
<if test="supervisionOpinion != null and supervisionOpinion != ''">#{supervisionOpinion},</if>
<if test="supervisionBy != null and supervisionBy != ''">#{supervisionBy},</if>
<if test="supervisionTime != null">#{supervisionTime},</if>
<if test="materialAuditStage != null and materialAuditStage != ''">#{materialAuditStage},</if>
<if test="laborAuditStage != null and laborAuditStage != ''">#{laborAuditStage},</if>
<if test="serviceAuditStage != null and serviceAuditStage != ''">#{serviceAuditStage},</if>
<if test="submitDeadline != null">#{submitDeadline},</if>
<if test="invitationUrl != null and invitationUrl != ''">#{invitationUrl},</if>
<if test="scheduleUrl != null and scheduleUrl != ''">#{scheduleUrl},</if>
@@ -194,7 +195,8 @@
<if test="supervisionOpinion != null and supervisionOpinion != ''">supervision_opinion = #{supervisionOpinion},</if>
<if test="supervisionBy != null and supervisionBy != ''">supervision_by = #{supervisionBy},</if>
<if test="supervisionTime != null">supervision_time = #{supervisionTime},</if>
<if test="materialAuditStage != null and materialAuditStage != ''">material_audit_stage = #{materialAuditStage},</if>
<if test="laborAuditStage != null and laborAuditStage != ''">labor_audit_stage = #{laborAuditStage},</if>
<if test="serviceAuditStage != null and serviceAuditStage != ''">service_audit_stage = #{serviceAuditStage},</if>
<if test="isExecuted != null">is_executed = #{isExecuted},</if>
<if test="executeTime != null">execute_time = #{executeTime},</if>
<if test="isSettled != null">is_settled = #{isSettled},</if>
@@ -204,7 +206,8 @@
<if test="isFrozen != null">is_frozen = #{isFrozen},</if>
<if test="freezeTime != null">freeze_time = #{freezeTime},</if>
<if test="materialAuditTime != null">material_audit_time = #{materialAuditTime},</if>
<if test="materialComplianceApproved != null">material_compliance_approved = #{materialComplianceApproved},</if>
<if test="laborComplianceApproved != null">labor_compliance_approved = #{laborComplianceApproved},</if>
<if test="serviceComplianceApproved != null">service_compliance_approved = #{serviceComplianceApproved},</if>
<if test="submitDeadline != null">submit_deadline = #{submitDeadline},</if>
<if test="invitationUrl != null and invitationUrl != ''">invitation_url = #{invitationUrl},</if>
<if test="scheduleUrl != null and scheduleUrl != ''">schedule_url = #{scheduleUrl},</if>
@@ -258,7 +261,8 @@
and is_frozen = 0
and start_time is not null
and start_time &lt;= NOW()
and material_audit_stage = 'NOT_SUBMITTED'
and labor_audit_stage = 'NOT_SUBMITTED'
and service_audit_stage = 'NOT_SUBMITTED'
</update>
<!-- 自动流转 (MeetingStageScheduler 每分钟调): material 未提交/已退回 且 submit_deadline 已过 → 冻结.
submit_deadline 由建会/退回/解冻 时写入 (end_time 或 now + 项目 submit_deadline_days 天), null = 项目未设天数 → 永不冻结. -->
@@ -269,7 +273,8 @@
current_stage = 'FROZEN'
where is_deleted = 0
and is_frozen = 0
and material_audit_stage in ('NOT_SUBMITTED', 'REJECTED')
and (labor_audit_stage in ('NOT_SUBMITTED', 'REJECTED')
or service_audit_stage in ('NOT_SUBMITTED', 'REJECTED'))
and submit_deadline is not null
and submit_deadline &lt;= NOW()
</update>
@@ -111,6 +111,7 @@
<select id="selectSponsorOrgOptions" parameterType="BizOrg" resultType="java.util.LinkedHashMap">
select o.org_id as orgId,
o.org_name as orgName,
o.status as status,
u.user_name as userName
from biz_org o
join sys_user u on u.user_id = o.user_id
@@ -135,6 +136,7 @@
<select id="selectExecutorOrgOptions" parameterType="BizOrg" resultType="java.util.LinkedHashMap">
select o.org_id as orgId,
o.org_name as orgName,
o.status as status,
u.user_name as userName
from biz_org o
join sys_user u on u.user_id = o.user_id
@@ -1,10 +1,11 @@
package com.ruoyi.common.enums;
/**
* 材料 审核阶段 (biz_meeting.material_audit_stage)
* 材料 审核阶段 (单轨 4 值, 用于 biz_meeting.labor_audit_stage / service_audit_stage)
* <p>
* 4 值 (用户拍板「已提交/待审核」合并): 提交后进入 SUBMITTED, 两级审核(合规先-支持方后)
* 用 material_compliance_approved 布尔区分「合规审中 vs 支持方审中」.
* 劳务/会务两轨各自独立走这条状态机. 4 值 (用户拍板「已提交/待审核」合并):
* 提交后进入 SUBMITTED, 两级审核(合规先-支持方后)用各自 compliance_approved 布尔区分
* 「合规审中 vs 支持方审中」.
* <pre>
* NOT_SUBMITTED 未提交 执行方还没交
* ↓ (执行方提交)