feat: 会议阶段劳务/会务前缀投影 + 结算前未保存拦截
- 阶段投影: 待审核/审核通过/待整改/待结算 按劳务/会务前缀区分 (前后端镜像) - 劳务材料提交门槛改为参会人员列表≥1人 (非上传文件) - sponsor/manager 操作列「审核」「结算」提为直链 - 会议结算时间轴节点: 待结算态显示「补充凭证并结算」 - 结算前检测未保存改动, 弹「保存并结算」拦截 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+14
-6
@@ -285,7 +285,7 @@ public class BizMeetingController extends BaseController {
|
||||
* <li>校验 1: 当前用户是该会议执行方 (项目级归属, 强校验)</li>
|
||||
* <li>校验 2: 已执行 (is_executed=1) 且未冻结</li>
|
||||
* <li>校验 3: 所选轨 audit_stage ∈ {NOT_SUBMITTED, REJECTED}</li>
|
||||
* <li>校验 4: 所选轨在 biz_meeting_material 各至少 1 条 (劳务 L_* / 会务 M_*)</li>
|
||||
* <li>校验 4: 会务轨在 biz_meeting_material 至少 1 条 (M_*); 劳务轨 = 参会人员列表至少 1 人 (非上传文件)</li>
|
||||
* </ul>
|
||||
* body: { "types": ["LABOR","SERVICE"] }
|
||||
* 通过后该轨 → SUBMITTED (compliance_approved=0), 每轨记一条 audit_log.
|
||||
@@ -310,8 +310,9 @@ public class BizMeetingController extends BaseController {
|
||||
}
|
||||
|
||||
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_"));
|
||||
// 劳务材料 = 参会人员列表 (不是上传的文件), 提交门槛 = 至少 1 名参会人
|
||||
boolean hasLabor = !attendeeService.selectByMeetingId(meetingId).isEmpty();
|
||||
|
||||
for (String type : types) {
|
||||
String label = "LABOR".equals(type) ? "劳务材料" : "会务材料";
|
||||
@@ -319,8 +320,11 @@ public class BizMeetingController extends BaseController {
|
||||
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 + "至少上传一条");
|
||||
if ("LABOR".equals(type)) {
|
||||
if (!hasLabor) throw new ServiceException("请先添加参会人员 (劳务材料)");
|
||||
} else if (!hasService) {
|
||||
throw new ServiceException("会务材料至少上传一条");
|
||||
}
|
||||
}
|
||||
|
||||
for (String type : types) {
|
||||
@@ -685,8 +689,12 @@ public class BizMeetingController extends BaseController {
|
||||
log.setAuditResult(result);
|
||||
log.setOpinion(opinion);
|
||||
log.setMaterialType(materialType);
|
||||
log.setCreateTime(new Date());
|
||||
log.setAuditTime(new Date());
|
||||
Date now = new Date();
|
||||
log.setCreateTime(now);
|
||||
log.setAuditTime(now);
|
||||
// 本次动作所在轨「最近时间」记为 now, 供 deriveDisplay 按最近时间合并两轨 (4 列阶段快照)
|
||||
if ("LABOR".equals(materialType)) m.setLaborAuditTime(now);
|
||||
else if ("SERVICE".equals(materialType)) m.setServiceAuditTime(now);
|
||||
log.setExecutorStage(stageDeriver.deriveDisplay("executor", m));
|
||||
log.setSponsorStage(stageDeriver.deriveDisplay("sponsor", m));
|
||||
log.setManagerStage(stageDeriver.deriveDisplay("manager", m));
|
||||
|
||||
@@ -99,6 +99,12 @@ public class BizMeeting extends BaseEntity {
|
||||
/** 材料最近一次审核动作时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date materialAuditTime;
|
||||
/** 劳务材料最后一次动作时间 (派生字段, 标量子查询 max(audit_time) where material_type='LABOR', 不落库) */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date laborAuditTime;
|
||||
/** 会务材料最后一次动作时间 (派生字段, 标量子查询 max(audit_time) where material_type='SERVICE', 不落库) */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date serviceAuditTime;
|
||||
/** 劳务材料合规是否已通过 0否1是 (区分 SUBMITTED 内合规审/支持方审) */
|
||||
private Integer laborComplianceApproved;
|
||||
/** 会务材料合规是否已通过 0否1是 (区分 SUBMITTED 内合规审/支持方审) */
|
||||
@@ -219,6 +225,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 Date getLaborAuditTime() { return laborAuditTime; }
|
||||
public void setLaborAuditTime(Date laborAuditTime) { this.laborAuditTime = laborAuditTime; }
|
||||
public Date getServiceAuditTime() { return serviceAuditTime; }
|
||||
public void setServiceAuditTime(Date serviceAuditTime) { this.serviceAuditTime = serviceAuditTime; }
|
||||
public Integer getLaborComplianceApproved() { return laborComplianceApproved; }
|
||||
public void setLaborComplianceApproved(Integer laborComplianceApproved) { this.laborComplianceApproved = laborComplianceApproved; }
|
||||
public Integer getServiceComplianceApproved() { return serviceComplianceApproved; }
|
||||
|
||||
@@ -88,8 +88,42 @@ public class StageDeriver
|
||||
if (t(m.getIsFinished())) return "已完结";
|
||||
if (t(m.getIsSettled())) return "已结算";
|
||||
|
||||
int chosen = chooseState(role, laborState(m), serviceState(m));
|
||||
return render(role, chosen, executionPhase(m));
|
||||
int phase = executionPhase(m);
|
||||
int labor = laborState(m);
|
||||
int service = serviceState(m);
|
||||
|
||||
// 中性角色 (admin/doctor/expert): 维持原逻辑 (最小进度 + 单轨措辞), 不加前缀
|
||||
if (!"executor".equals(role) && !"sponsor".equals(role) && !"manager".equals(role))
|
||||
{
|
||||
return render(role, Math.min(labor, service), phase);
|
||||
}
|
||||
|
||||
// 三流程角色: 两轨合并 + 劳务/会务前缀
|
||||
String laborLabel = render(role, labor, phase);
|
||||
String serviceLabel = render(role, service, phase);
|
||||
if (laborLabel.equals(serviceLabel)) return laborLabel;
|
||||
|
||||
// 待办态最高优先: manager=C0(2), sponsor=C1(3), executor=R(0)
|
||||
int urgent = "manager".equals(role) ? 2 : "sponsor".equals(role) ? 3 : 0;
|
||||
String track;
|
||||
String label;
|
||||
if (labor == urgent) { track = "labor"; label = laborLabel; }
|
||||
else if (service == urgent) { track = "service"; label = serviceLabel; }
|
||||
else
|
||||
{
|
||||
Date lt = m.getLaborAuditTime();
|
||||
Date st = m.getServiceAuditTime();
|
||||
boolean laborNewer = lt != null && (st == null || !lt.before(st));
|
||||
if (laborNewer) { track = "labor"; label = laborLabel; }
|
||||
else { track = "service"; label = serviceLabel; }
|
||||
}
|
||||
|
||||
// 仅 待审核/审核通过/待整改/待结算 四类加前缀
|
||||
if ("待审核".equals(label) || "审核通过".equals(label) || "待整改".equals(label) || "待结算".equals(label))
|
||||
{
|
||||
return ("labor".equals(track) ? "劳务" : "会务") + label;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
/** 按角色优先级选代表轨 (两轨中优先级更高的那轨). */
|
||||
@@ -139,7 +173,7 @@ public class StageDeriver
|
||||
switch (s)
|
||||
{
|
||||
case 0: // R 退回
|
||||
return "executor".equals(role) ? "已退回" : "待整改";
|
||||
return "待整改";
|
||||
case 1: // N 未提交 (时间驱动三态: 未执行 → 执行中 → 已执行, 所有角色统一)
|
||||
if (phase == 0) return "未执行";
|
||||
if (phase == 1) return "执行中";
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
<result property="isFrozen" column="is_frozen" />
|
||||
<result property="freezeTime" column="freeze_time" />
|
||||
<result property="materialAuditTime" column="material_audit_time" />
|
||||
<result property="laborAuditTime" column="labor_audit_time" />
|
||||
<result property="serviceAuditTime" column="service_audit_time" />
|
||||
<result property="laborComplianceApproved" column="labor_compliance_approved" />
|
||||
<result property="serviceComplianceApproved" column="service_compliance_approved" />
|
||||
<result property="submitDeadline" column="submit_deadline" />
|
||||
@@ -59,6 +61,8 @@
|
||||
<select id="selectByPrimaryKey" resultMap="BizMeetingResult" parameterType="Long">
|
||||
select
|
||||
(select o.org_name from biz_project p join biz_org o on o.org_id = p.sponsor_org_id where p.project_id = biz_meeting.project_id limit 1) as org_name,
|
||||
(select max(a2.audit_time) from biz_meeting_audit_log a2 where a2.meeting_id = biz_meeting.meeting_id and a2.material_type = 'LABOR' and a2.is_deleted = 0) as labor_audit_time,
|
||||
(select max(a2.audit_time) from biz_meeting_audit_log a2 where a2.meeting_id = biz_meeting.meeting_id and a2.material_type = 'SERVICE' and a2.is_deleted = 0) as service_audit_time,
|
||||
<include refid="selectFields"/>
|
||||
from biz_meeting
|
||||
where meeting_id = #{meetingId} and is_deleted = 0
|
||||
@@ -71,6 +75,8 @@
|
||||
(select o.org_name from biz_project p join biz_org o on o.org_id = p.sponsor_org_id where p.project_id = biz_meeting.project_id limit 1) as org_name,
|
||||
(select p.invitation_url from biz_project p where p.project_id = biz_meeting.project_id limit 1) as project_invitation_url,
|
||||
<if test="params.assignedExecutorUserId != null">(select coalesce(sum(a.sessions), 0) from biz_project_assign a where a.project_id = biz_meeting.project_id and a.is_deleted = 0 and a.execution_unit_id = (select org_id from biz_org where user_id = #{params.assignedExecutorUserId} and org_type = 'executor')) as assigned_sessions,</if>
|
||||
(select max(a2.audit_time) from biz_meeting_audit_log a2 where a2.meeting_id = biz_meeting.meeting_id and a2.material_type = 'LABOR' and a2.is_deleted = 0) as labor_audit_time,
|
||||
(select max(a2.audit_time) from biz_meeting_audit_log a2 where a2.meeting_id = biz_meeting.meeting_id and a2.material_type = 'SERVICE' and a2.is_deleted = 0) as service_audit_time,
|
||||
<include refid="selectFields"/>
|
||||
from biz_meeting
|
||||
<where>
|
||||
|
||||
Reference in New Issue
Block a user