[audit] P6.1 F11-4 台账路由挂起可观测:pageAdmin 增 hung 过滤 + 行级 routeHung 红标(口径=max attempt 路由行 outlet 仍 NULL,防误报)

This commit is contained in:
chenxing
2026-10-18 08:05:32 +08:00
parent f4121b2c1e
commit 448c8d36f4
6 changed files with 159 additions and 5 deletions
@@ -54,6 +54,7 @@ public class FlowAdminController {
private final com.fasterxml.jackson.databind.ObjectMapper objectMapper;
private final com.yunhai.flowcenter.service.FlowAppAdminService appAdminService;
private final com.yunhai.flowcenter.service.FlowDeptAdminService deptAdminService;
private final com.yunhai.flowcenter.repository.FlwRouteLogRepository routeLogRepository;
// ==================== 流程定义 ====================
@@ -462,18 +463,34 @@ public class FlowAdminController {
// ==================== 办件查询 ====================
/**
* 管理端办件分页(F11-4 增 hung 筛选与行级红标):hung=true 只看路由挂起办件
* (口径见 {@link com.yunhai.flowcenter.repository.FlwInstanceRepository#pageAdmin});
* 每行回填 {@code routeHung}(按页一次 {@code findHungInstanceIds} 查询,非逐行 N+1)。
*/
@GetMapping("/instance/page")
public BasePageResult<FlwInstance> instancePage(
BoaUser user,
@RequestParam(value = "status", required = false) Integer status,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "hung", required = false) Boolean hung,
@RequestParam(value = "page", required = false) Integer page,
@RequestParam(value = "size", required = false) Integer size) {
Long schId = adminGuard.requireAdmin(user, null);
Page<FlwInstance> p = instanceRepository.pageAdmin(schId, status, keyword,
Page<FlwInstance> p = instanceRepository.pageAdmin(schId, status, keyword, hung,
PageRequest.of(page == null || page < 1 ? 0 : page - 1,
size == null || size < 1 || size > 100 ? 10 : size,
Sort.by(Sort.Order.desc("id"))));
List<Long> pageIds = new java.util.ArrayList<>();
for (FlwInstance i : p.getContent()) {
pageIds.add(i.getId());
}
// 行级红标:不在挂起集合内的行显式置 false(前端只判 truthy,null 也行,但显式更可读)
java.util.Set<Long> hungIds = pageIds.isEmpty() ? java.util.Collections.emptySet()
: new java.util.HashSet<>(routeLogRepository.findHungInstanceIds(pageIds));
for (FlwInstance i : p.getContent()) {
i.setRouteHung(hungIds.contains(i.getId()));
}
int pi = page == null || page < 1 ? 1 : page;
int ps = size == null || size < 1 || size > 100 ? 10 : size;
return new BasePageResult<>(p.getContent(), (int) p.getTotalElements(), pi, ps,
@@ -128,4 +128,12 @@ public class FlwInstance extends BaseEntity {
@Version
@Column(name = "version", nullable = false)
private Integer version;
/**
* 台账展示位(F11-4):该办件是否「路由挂起」(审批中且最新 attempt 的路由判断行无出口)。
* 非持久列——由管理端办件分页(FlowAdminController.instancePage)按页查询后填充,
* 随实体序列化给前端画红标;业务代码不得读写该字段。
*/
@javax.persistence.Transient
private Boolean routeHung;
}
@@ -27,12 +27,23 @@ public interface FlwInstanceRepository extends JpaRepository<FlwInstance, Long>
@Param("status") Integer status, @Param("appKey") String appKey,
Pageable pageable);
/** 管理端办件分页(状态/关键字可空) */
/**
* 管理端办件分页(状态/关键字可空)。
* hung=true 只看「路由挂起」:审批中(status=10)且存在某 (instance,node) 的<b>最大 attempt</b>
* flw_route_log 行 outlet 仍为 NULL(与 {@link com.yunhai.flowcenter.repository.FlwRouteLogRepository#findHungInstanceIds}
* 同口径——历史低 attempt 失败行不参与判定,防「重跑已成功仍报挂起」误报);hung=null/false 不过滤。
*/
@Query("select i from FlwInstance i where i.schId = :schId "
+ "and (:status is null or i.status = :status) "
+ "and (:keyword is null or i.bizTitle like concat('%', :keyword, '%')) order by i.id desc")
+ "and (:keyword is null or i.bizTitle like concat('%', :keyword, '%')) "
+ "and (:hung is null or :hung = false or (i.status = 10 and exists ("
+ "select r.id from FlwRouteLog r where r.instanceId = i.id and r.outlet is null "
+ "and r.attempt = (select max(r2.attempt) from FlwRouteLog r2 "
+ "where r2.instanceId = r.instanceId and r2.nodeId = r.nodeId)))) "
+ "order by i.id desc")
Page<FlwInstance> pageAdmin(@Param("schId") Long schId, @Param("status") Integer status,
@Param("keyword") String keyword, Pageable pageable);
@Param("keyword") String keyword, @Param("hung") Boolean hung,
Pageable pageable);
/** 按业务类型/状态分页(oapi instances 查询;bizType/status 均可空=不过滤) */
@Query("select i from FlwInstance i where i.schId = :schId "
@@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Collection;
import java.util.List;
public interface FlwRouteLogRepository extends JpaRepository<FlwRouteLog, Long> {
@@ -16,4 +17,16 @@ public interface FlwRouteLogRepository extends JpaRepository<FlwRouteLog, Long>
@Query("select coalesce(max(l.attempt), 0) from FlwRouteLog l "
+ "where l.instanceId = :instanceId and l.nodeId = :nodeId")
int maxAttempt(@Param("instanceId") Long instanceId, @Param("nodeId") String nodeId);
/**
* 台账「路由挂起」红标数据源(F11-4):页内办件中仍挂起的 instanceId 集合。
* 判定口径:某 (instance, node) 的<b>最大 attempt</b> 行 outlet 仍为 NULL 才算挂着——
* 退回重走/管理端重跑会写更高 attempt 的新行,历史低 attempt 的失败行(outlet NULL)不参与判定,
* 防「已重跑成功仍报挂起」的误报。
*/
@Query("select distinct r.instanceId from FlwRouteLog r where r.instanceId in :instanceIds "
+ "and r.outlet is null "
+ "and r.attempt = (select max(r2.attempt) from FlwRouteLog r2 "
+ "where r2.instanceId = r.instanceId and r2.nodeId = r.nodeId)")
List<Long> findHungInstanceIds(@Param("instanceIds") Collection<Long> instanceIds);
}