Files
flow-center-service/src/main/java/com/yunhai/flowcenter/service/FlowQueryService.java
T
chenxing 52c27d88bb
build / build-push (push) Successful in 1m43s
[fix] 办理页业务信息走 summaryFields + biz_url 绝对化(BizUrlResolver)
- 详情出参新增 bizUrlAbs:biz_url 裸相对路径按 applyAppCode(by-code)→applyAppKey(by-key)
  查应用注册表 pc_url 拼 base#path(口径同 UserHome 消息中心 jumpUrl),查不到为 null
- 缓存 5min/未命中 60s,不阻断详情接口;biz_url 原值保留给 bare 态父页面接管
2026-10-19 20:09:29 +08:00

572 lines
28 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.flowcenter.service;
import com.yunhai.flowcenter.engine.BranchScope;
import com.yunhai.flowcenter.engine.FlowDefModel;
import com.yunhai.flowcenter.engine.FlowEngine;
import com.yunhai.flowcenter.entity.FlwBranch;
import com.yunhai.flowcenter.entity.FlwCc;
import com.yunhai.flowcenter.entity.FlwDef;
import com.yunhai.flowcenter.entity.FlwInstance;
import com.yunhai.flowcenter.entity.FlwRouteLog;
import com.yunhai.flowcenter.entity.FlwTask;
import com.yunhai.flowcenter.enums.FlwEnums;
import com.yunhai.flowcenter.model.FlowModels;
import com.yunhai.flowcenter.repository.FlwBranchRepository;
import com.yunhai.flowcenter.repository.FlwCcRepository;
import com.yunhai.flowcenter.repository.FlwDefRepository;
import com.yunhai.flowcenter.repository.FlwInstanceRepository;
import com.yunhai.flowcenter.repository.FlwRouteLogRepository;
import com.yunhai.flowcenter.repository.FlwTaskRepository;
import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.platform.utils.model.BasePageResult;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 用户面查询(我的办件/待办/已办/办件详情)。
*/
@Service
@RequiredArgsConstructor
public class FlowQueryService {
private final FlwInstanceRepository instanceRepository;
private final FlwTaskRepository taskRepository;
private final FlwBranchRepository branchRepository;
private final FlwCcRepository ccRepository;
private final FlwRouteLogRepository routeLogRepository;
private final FlwDefRepository defRepository;
private final FlowDeptAdminService deptAdminService;
private final FlowEngine flowEngine;
private final BizUrlResolver bizUrlResolver;
public BasePageResult<FlowModels.InstanceItem> pageMy(Long schId, Integer submitterId,
Integer status, String appKey, Integer page, Integer size) {
Page<FlwInstance> p = instanceRepository.pageMy(schId, submitterId, status, emptyToNull(appKey),
PageRequest.of(normPage(page), normSize(size)));
return toResult(p.getTotalElements(), p.getContent().stream().map(this::toInstanceItem)
.collect(Collectors.toList()), page, size);
}
/** 抄送我的(NOTIFY 落库行,sent_time 倒序;知会不占待办,独立于 todo/done 口径) */
public BasePageResult<FlowModels.CcItem> pageCc(Long schId, Integer userId,
String appKey, Integer page, Integer size) {
Page<FlwCc> p = ccRepository.pageCc(schId, userId, emptyToNull(appKey),
PageRequest.of(normPage(page), normSize(size)));
List<FlowModels.CcItem> items = p.getContent().stream()
.map(this::toCcItem).collect(Collectors.toList());
fillCcInstanceInfo(items);
return toResult(p.getTotalElements(), items, page, size);
}
private FlowModels.CcItem toCcItem(FlwCc cc) {
FlowModels.CcItem item = new FlowModels.CcItem();
item.setCcId(cc.getId());
item.setInstanceId(cc.getInstanceId());
item.setNodeId(cc.getNodeId());
item.setNodeName(cc.getNodeName());
item.setUserId(cc.getUserId());
item.setUserName(cc.getUserName());
item.setSentTime(str(cc.getSentTime()));
return item;
}
private void fillCcInstanceInfo(List<FlowModels.CcItem> items) {
for (FlowModels.CcItem item : items) {
FlwInstance instance = instanceRepository.findById(item.getInstanceId()).orElse(null);
if (instance != null) {
item.setBizTitle(instance.getBizTitle());
item.setBizType(instance.getBizType());
item.setBizId(instance.getBizId());
item.setInstanceStatus(instance.getStatus());
}
}
}
public BasePageResult<FlowModels.TaskItem> pageTodo(Long schId, Integer assigneeId, String appKey,
Integer page, Integer size) {
Page<FlwTask> p = taskRepository.pageTodo(schId, assigneeId, emptyToNull(appKey),
PageRequest.of(normPage(page), normSize(size)));
List<FlowModels.TaskItem> items = p.getContent().stream()
.map(this::toTaskItem).collect(Collectors.toList());
fillInstanceInfo(items);
return toResult(p.getTotalElements(), items, page, size);
}
/** 已办:一个办件一行(保留本人最后办理的那条),并回填累计办理次数 */
public BasePageResult<FlowModels.TaskItem> pageDone(Long schId, Integer assigneeId, String appKey,
Integer page, Integer size) {
List<Integer> handled = FlwEnums.TaskStatus.handledCodes();
Page<FlwTask> p = taskRepository.pageDone(schId, assigneeId, handled, emptyToNull(appKey),
PageRequest.of(normPage(page), normSize(size)));
List<FlowModels.TaskItem> items = p.getContent().stream()
.map(this::toTaskItem).collect(Collectors.toList());
fillInstanceInfo(items);
fillDoneCount(items, assigneeId, handled);
return toResult(p.getTotalElements(), items, page, size);
}
/** 已办行的累计办理次数(同一办件多节点办理/退回重办时的"共办理 N 次") */
private void fillDoneCount(List<FlowModels.TaskItem> items, Integer assigneeId,
List<Integer> handled) {
if (items.isEmpty()) {
return;
}
List<Long> ids = items.stream().map(FlowModels.TaskItem::getInstanceId)
.distinct().collect(Collectors.toList());
Map<Long, Integer> counts = new HashMap<>();
for (Object[] row : taskRepository.countDoneByInstance(ids, assigneeId, handled)) {
counts.put((Long) row[0], ((Number) row[1]).intValue());
}
for (FlowModels.TaskItem item : items) {
item.setDoneCount(counts.getOrDefault(item.getInstanceId(), 1));
}
}
/**
* 办件详情:快照 + 任务轨迹 + 分支进度 + 本人待办标记(办理页/详情页共用数据源)。
* viewerId 必须由控制器从 BoaUser.getId() 传入(CAS 会话 LoginUser.userId 是登录名字符串,
* 不是数字主键,不能直接转 Integer——2026-09-12 全链测试修复)。
*/
public FlowModels.InstanceDetail detail(Long schId, Long instanceId, Integer viewerId) {
FlwInstance instance = instanceRepository.findById(instanceId)
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在"));
if (!instance.getSchId().equals(schId)) {
throw new BizException(ErrorCode.FORBIDDEN, "办件不属于该学校");
}
FlowModels.InstanceDetail detail = new FlowModels.InstanceDetail();
detail.setInstanceId(instance.getId());
detail.setSchId(instance.getSchId());
detail.setDefCode(instance.getDefCode());
detail.setDefVersion(instance.getDefVersion());
detail.setBizType(instance.getBizType());
detail.setBizId(instance.getBizId());
detail.setBizTitle(instance.getBizTitle());
detail.setBizUrl(instance.getBizUrl());
detail.setBizUrlAbs(bizUrlResolver.resolve(
instance.getApplyAppCode(), instance.getApplyAppKey(), instance.getBizUrl()));
detail.setBizSnapshot(instance.getBizSnapshot());
detail.setFormDataJson(instance.getFormDataJson());
detail.setStatus(instance.getStatus());
detail.setCurrentNodes(instance.getCurrentNodes());
detail.setSubmitterId(instance.getSubmitterId());
detail.setSubmitterName(instance.getSubmitterName());
detail.setSubmitTime(str(instance.getSubmitTime()));
detail.setFinishTime(str(instance.getFinishTime()));
detail.setVersion(instance.getVersion());
List<FlwTask> tasks = taskRepository.findByInstanceIdOrderByIdAsc(instanceId);
List<FlwCc> ccs = ccRepository.findByInstanceIdOrderByIdAsc(instanceId);
List<FlwRouteLog> routeLogs = routeLogRepository.findByInstanceIdOrderByIdAsc(instanceId);
FlwDef def = defRepository.findById(instance.getDefId()).orElse(null);
FlowDefModel model = def == null ? null : flowEngine.parseModel(def);
detail.setTrace(buildTrace(instance, tasks, ccs, routeLogs, model));
List<Map<String, Object>> branches = new ArrayList<>();
for (FlwBranch b : branchRepository.findByInstanceId(instanceId)) {
Map<String, Object> row = new HashMap<>();
row.put("branchKey", b.getBranchKey());
row.put("status", b.getStatus());
branches.add(row);
}
detail.setBranches(branches);
List<Long> myPending = new ArrayList<>();
if (viewerId != null) {
for (FlwTask t : tasks) {
if (viewerId.equals(t.getAssigneeId())
&& t.getStatus() == FlwEnums.TaskStatus.PENDING.getCode()) {
myPending.add(t.getId());
}
}
detail.setMyPendingTask(!myPending.isEmpty());
detail.setMyPendingTaskIds(myPending);
}
detail.setNodeSummary(def == null ? "" : FlowEngine.nodeSummary(model));
// 办理面板数据(出口/退回目标/移交标记):仅对持本人待办的查看者填充
FlwTask myPend = viewerId == null ? null : tasks.stream()
.filter(t -> viewerId.equals(t.getAssigneeId())
&& t.getStatus() == FlwEnums.TaskStatus.PENDING.getCode())
.findFirst().orElse(null);
if (myPend != null && def != null) {
String curNodeId = myPend.getNodeId();
List<FlowDefModel.Edge> outs = model.outEdges(curNodeId);
if (outs.size() > 1) {
List<Map<String, Object>> outlets = new ArrayList<>();
for (FlowDefModel.Edge e : outs) {
Map<String, Object> o = new HashMap<>();
o.put("name", e.name);
o.put("toNodeId", e.to);
FlowDefModel.Node to = model.node(e.to);
o.put("toNodeName", to == null ? e.to
: (to.name == null || to.name.isEmpty() ? e.to : to.name));
outlets.add(o);
}
detail.setOutlets(outlets);
} else {
detail.setOutlets(new ArrayList<>());
}
// 可退回目标:办件已办过的主线审批节点(去重,排除当前节点)
Map<String, Map<String, Object>> targets = new java.util.LinkedHashMap<>();
for (FlwTask t : tasks) {
if (t.getStatus() != FlwEnums.TaskStatus.APPROVED.getCode()
|| t.getNodeId().equals(myPend.getNodeId())
|| BranchScope.keyOf(model, t.getNodeId()) != null) {
continue;
}
FlowDefModel.Node node = model.node(t.getNodeId());
if (node != null && FlowDefModel.TYPE_APPROVAL.equals(node.type)
&& !targets.containsKey(t.getNodeId())) {
Map<String, Object> r = new HashMap<>();
r.put("nodeId", node.id);
r.put("nodeName", node.name == null || node.name.isEmpty() ? node.id : node.name);
targets.put(node.id, r);
}
}
detail.setReturnTargets(new ArrayList<>(targets.values()));
detail.setCanTransfer(true);
} else {
detail.setOutlets(new ArrayList<>());
detail.setReturnTargets(new ArrayList<>());
}
return detail;
}
/** 任务定位办件详情(办理页 /task/{taskId} 数据源:UserMsg jumpPath 只带 taskId) */
public FlowModels.InstanceDetail taskDetail(Long schId, Long taskId, Integer viewerId) {
FlwTask task = taskRepository.findById(taskId)
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "任务不存在:" + taskId));
if (!task.getSchId().equals(schId)) {
throw new BizException(ErrorCode.FORBIDDEN, "任务不属于该学校");
}
return detail(schId, task.getInstanceId(), viewerId);
}
private FlowModels.InstanceItem toInstanceItem(FlwInstance instance) {
FlowModels.InstanceItem item = new FlowModels.InstanceItem();
item.setInstanceId(instance.getId());
item.setBizType(instance.getBizType());
item.setBizId(instance.getBizId());
item.setBizTitle(instance.getBizTitle());
item.setStatus(instance.getStatus());
item.setCurrentNodes(instance.getCurrentNodes());
item.setSubmitterName(instance.getSubmitterName());
item.setSubmitTime(str(instance.getSubmitTime()));
item.setFinishTime(str(instance.getFinishTime()));
item.setDefVersion(instance.getDefVersion());
return item;
}
/**
* 流程轨迹(2026-09-18 重排为"时间流"):审批任务行 + 留痕事件行(6 发起/重新提交、7 撤回)
* 合成一条按时间升序的轨迹,替掉原先"按 id 升序"的排法(id 序在撤回重走场景下与
* 真实发生顺序不一致)。
*
* <p>2026-09-23 并入抄送行(flw_cc,kind=CC):NOTIFY 节点不建任务,若不并入则抄送在
* 轨迹上不可见;按 sentTime 与任务行同场归位、参与轮次进位(退回重走的第 N 轮抄送)。
*
* <p>2026-09-24 并入路由判断行(flw_route_log,kind=ROUTE):CONDITION 判断留痕
* "系统判断 → 出口"(outlet=NULL 渲染为挂起);按 decideTime 同场归位。
*
* <p>每条行补四个前端直接用、不再自行推断的字段:
* <ul>
* <li>{@code time} 该行实际发生的时间(已办/退回/移交/撤回=handleTime,在办/失效=arriveTime,抄送=sentTime,判断=decideTime);</li>
* <li>{@code kind} SUBMIT|RESUBMIT|WITHDRAW|APPROVE|REJECT|RETURN|TRANSFER|INVALID|PENDING|CC|ROUTE
* (前端配色与文案的唯一依据);</li>
* <li>{@code round} 第几轮审批(遇"重新提交"进位,撤回属于它结束的那一轮);</li>
* <li>{@code rowKey} 去重 key(任务行 t{id}、抄送行 cc{id}、合成行 submit —— taskId 可能为 null 不能当 key)。</li>
* </ul>
* 科室口径(视图两档:dept/user)另需 assigneeType/deptKey/deptName —— 只有 DEPT_KEY 节点有
* 科室概念,故仅对该类节点回填;节点在定义里找不到(重提后老节点、留痕事件行)不回填,
* 前端回落人名显示。
*
* <p>老办件(改造前发起)没有"发起"留痕行,按办件快照的 submitTime/submitterName 合成一条
* 兜底插入到时间流对应位置,保证轨迹首条仍是"发起申请"。
*/
private List<Map<String, Object>> buildTrace(FlwInstance instance, List<FlwTask> tasks,
List<FlwCc> ccs, List<FlwRouteLog> routeLogs,
FlowDefModel model) {
// 任务行、判断行与抄送行同场按发生时间排序:时间只到秒(DATETIME),同秒平局任务行恒在前、
// 判断行次之(办理→判断→抄送的因果序)、抄送行最后(秩隔开跨表 id 空间;稳排保证同源行相对次序不变)。
// 无判断/抄送的办件所有行 source 恒 0,排序与旧版 (time,id) 严格等价——
// 不能给任务行再分因果秩:驳回行与重提行同秒是常态,旧序按 id(真实先后),秩会颠倒。
List<TimedRow> rows = new ArrayList<>();
Map<String, String> deptNames = new HashMap<>();
for (FlwTask t : tasks) {
rows.add(new TimedRow(happenedAt(t), 0, t.getId(),
taskTraceRow(t, model, instance, deptNames)));
}
for (FlwRouteLog r : routeLogs) {
rows.add(new TimedRow(r.getDecideTime(), 1, r.getId(), routeTraceRow(r)));
}
for (FlwCc c : ccs) {
rows.add(new TimedRow(c.getSentTime(), 2, c.getId(),
ccTraceRow(c, model, instance, deptNames)));
}
rows.sort(Comparator.comparing((TimedRow r) -> r.time,
Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(r -> r.source)
.thenComparing(r -> r.seq));
List<Map<String, Object>> trace = new ArrayList<>();
int headIndex = 0;
LocalDateTime submitTime = instance.getSubmitTime();
for (TimedRow r : rows) {
trace.add(r.row);
// 老办件兜底行的插入位:时间早于提交时间的行数(老办件里恒为 0;重提过的老办件按时间放在中段)
if (submitTime != null && r.time != null && r.time.isBefore(submitTime)) {
headIndex = trace.size();
}
}
if (!hasSubmitRow(tasks)) {
trace.add(headIndex, syntheticSubmitRow(instance));
}
int round = 1;
for (Map<String, Object> row : trace) {
if ("RESUBMIT".equals(row.get("kind"))) {
round++;
}
row.put("round", round);
}
return trace;
}
private Map<String, Object> taskTraceRow(FlwTask t, FlowDefModel model, FlwInstance instance,
Map<String, String> deptNames) {
Map<String, Object> row = new HashMap<>();
row.put("rowKey", "t" + t.getId());
row.put("taskId", t.getId());
row.put("kind", kindOf(t));
row.put("nodeId", t.getNodeId());
row.put("nodeName", t.getNodeName());
row.put("assigneeId", t.getAssigneeId());
row.put("assigneeName", t.getAssigneeName());
row.put("status", t.getStatus());
row.put("opinion", t.getOpinion());
row.put("arriveTime", str(t.getArriveTime()));
row.put("handleTime", str(t.getHandleTime()));
row.put("time", str(happenedAt(t)));
row.put("deadline", str(t.getDeadline()));
row.put("urgeCount", t.getUrgeCount());
row.put("edgeName", t.getEdgeName());
fillAssigneeScope(row, model, t.getNodeId(), instance.getSchId(), deptNames);
return row;
}
/** 抄送轨迹行(kind=CC):no taskId/opinion,接收人记在 assignee* 字段(前端复用人列)。
* 2026-10-12 补科室口径:抄送节点用 receivers,走 {@link #fillCcScope} 富化 assigneeType,
* 前端 dept 档据此显科室名 / "抄送对象",不再回落接收人真实姓名。 */
private Map<String, Object> ccTraceRow(FlwCc c, FlowDefModel model, FlwInstance instance,
Map<String, String> deptNames) {
Map<String, Object> row = new HashMap<>();
row.put("rowKey", "cc" + c.getId());
row.put("ccId", c.getId());
row.put("kind", "CC");
row.put("nodeId", c.getNodeId());
row.put("nodeName", c.getNodeName() == null || c.getNodeName().isEmpty() ? "抄送" : c.getNodeName());
row.put("assigneeId", c.getUserId());
row.put("assigneeName", c.getUserName());
row.put("status", null);
row.put("time", str(c.getSentTime()));
fillCcScope(row, model, c.getNodeId(), instance.getSchId(), deptNames);
return row;
}
/** 路由判断轨迹行(kind=ROUTE,2026-09-24):"系统判断 → 出口",outlet=NULL 渲染为挂起 */
private static Map<String, Object> routeTraceRow(FlwRouteLog r) {
Map<String, Object> row = new HashMap<>();
row.put("rowKey", "r" + r.getId());
row.put("routeLogId", r.getId());
row.put("kind", "ROUTE");
row.put("nodeId", r.getNodeId());
row.put("nodeName", r.getNodeName() == null || r.getNodeName().isEmpty() ? "条件判断" : r.getNodeName());
row.put("routerKey", r.getRouterKey());
row.put("outlet", r.getOutlet());
row.put("attempt", r.getAttempt());
row.put("status", null);
row.put("time", str(r.getDecideTime()));
// 议题三②:调试弹窗「从路由日志导入」需要看失败摘要(>500 截断)
row.put("rawResponse", r.getRawResponse() == null ? "" :
(r.getRawResponse().length() > 500 ? r.getRawResponse().substring(0, 500) + "…" : r.getRawResponse()));
return row;
}
/** 排序载体:发生时间 + 来源秩(任务 0/判断 1/抄送 2)+ 同表内序(id),跨表 id 不比大小 */
private static final class TimedRow {
final LocalDateTime time;
final int source;
final long seq;
final Map<String, Object> row;
TimedRow(LocalDateTime time, int source, long seq, Map<String, Object> row) {
this.time = time;
this.source = source;
this.seq = seq;
this.row = row;
}
}
private static boolean hasSubmitRow(List<FlwTask> tasks) {
for (FlwTask t : tasks) {
if (FlwEnums.TaskStatus.NODE_SUBMIT.equals(t.getNodeId())) {
return true;
}
}
return false;
}
/** 老办件兜底:改造前发起不留痕,用快照补一条"发起申请"(taskId 为空,前端按 rowKey 去重) */
private static Map<String, Object> syntheticSubmitRow(FlwInstance instance) {
Map<String, Object> row = new HashMap<>();
row.put("rowKey", "submit");
row.put("taskId", null);
row.put("kind", "SUBMIT");
row.put("nodeId", FlwEnums.TaskStatus.NODE_SUBMIT);
row.put("nodeName", "发起申请");
row.put("assigneeId", instance.getSubmitterId());
row.put("assigneeName", instance.getSubmitterName());
row.put("status", FlwEnums.TaskStatus.SUBMITTED.getCode());
row.put("time", str(instance.getSubmitTime()));
row.put("synthetic", true);
return row;
}
/** 该行实际发生的时间:办结过看 handleTime,否则看 arriveTime */
private static LocalDateTime happenedAt(FlwTask t) {
return t.getHandleTime() != null ? t.getHandleTime() : t.getArriveTime();
}
private static String kindOf(FlwTask t) {
int s = t.getStatus() == null ? FlwEnums.TaskStatus.PENDING.getCode() : t.getStatus();
if (s == FlwEnums.TaskStatus.SUBMITTED.getCode()) {
return FlwEnums.TaskStatus.NODE_RESUBMIT.equals(t.getNodeId()) ? "RESUBMIT" : "SUBMIT";
}
if (s == FlwEnums.TaskStatus.WITHDRAWN.getCode()) {
return "WITHDRAW";
}
if (s == FlwEnums.TaskStatus.APPROVED.getCode()) {
return "APPROVE";
}
if (s == FlwEnums.TaskStatus.REJECTED.getCode()) {
return "REJECT";
}
if (s == FlwEnums.TaskStatus.INVALIDATED.getCode()) {
return "INVALID";
}
if (s == FlwEnums.TaskStatus.RETURNED.getCode()) {
return "RETURN";
}
if (s == FlwEnums.TaskStatus.TRANSFERRED.getCode()) {
return "TRANSFER";
}
return "PENDING";
}
/**
* 科室口径富化:节点是 DEPT_KEY(科室办理)时带上 deptKey + 映射里的科室名,
* 前端"按科室显示"据此把人名换成科室名;USERS(指定人员)节点只带 assigneeType,
* 由前端显示"指定办理人"(口径同示意图的科室视图)。
*/
private void fillAssigneeScope(Map<String, Object> row, FlowDefModel model, String nodeId,
Long schId, Map<String, String> deptNames) {
FlowDefModel.Node node = model == null ? null : model.node(nodeId);
FlowDefModel.Assignee assignee = node == null ? null : node.assignee;
applyAssigneeScope(row, assignee, schId, deptNames);
}
/**
* 抄送行(kind=CC)科室口径富化(2026-10-12):NOTIFY 节点的接收人存在 {@code receivers}
* 字段而非 {@code assignee},故不能直接复用 {@link #fillAssigneeScope}——否则取到的 assignee
* 恒为 null,dept 档下抄送行只能回落接收人真实姓名(原先的泄露点)。
* 节点在定义里找不到(重提后的老节点、已删节点)不填,前端统一显示"抄送对象"。
*/
private void fillCcScope(Map<String, Object> row, FlowDefModel model, String nodeId,
Long schId, Map<String, String> deptNames) {
FlowDefModel.Node node = model == null ? null : model.node(nodeId);
FlowDefModel.Assignee receivers = node == null ? null : node.receivers;
applyAssigneeScope(row, receivers, schId, deptNames);
}
/** 两种口径的公共落值:assigneeType 必填;DEPT_KEY 另填 deptKey + 映射里的科室名 */
private void applyAssigneeScope(Map<String, Object> row, FlowDefModel.Assignee assignee,
Long schId, Map<String, String> deptNames) {
if (assignee == null || assignee.type == null) {
return;
}
row.put("assigneeType", assignee.type);
if (!FlowDefModel.ASSIGNEE_DEPT_KEY.equals(assignee.type) || assignee.deptKey == null) {
return;
}
row.put("deptKey", assignee.deptKey);
String deptName = deptNames.computeIfAbsent(assignee.deptKey,
k -> DeptLabelSupport.deptName(deptAdminService, schId, k));
if (deptName != null) {
row.put("deptName", deptName);
}
}
private FlowModels.TaskItem toTaskItem(FlwTask t) {
FlowModels.TaskItem item = new FlowModels.TaskItem();
item.setTaskId(t.getId());
item.setInstanceId(t.getInstanceId());
item.setNodeId(t.getNodeId());
item.setNodeName(t.getNodeName());
item.setAssigneeName(t.getAssigneeName());
item.setStatus(t.getStatus());
item.setOpinion(t.getOpinion());
item.setArriveTime(str(t.getArriveTime()));
item.setHandleTime(str(t.getHandleTime()));
item.setDeadline(str(t.getDeadline()));
item.setUrgeCount(t.getUrgeCount());
return item;
}
private void fillInstanceInfo(List<FlowModels.TaskItem> items) {
for (FlowModels.TaskItem item : items) {
FlwInstance instance = instanceRepository.findById(item.getInstanceId()).orElse(null);
if (instance != null) {
item.setBizTitle(instance.getBizTitle());
item.setBizType(instance.getBizType());
item.setBizId(instance.getBizId());
item.setInstanceStatus(instance.getStatus());
item.setSubmitterName(instance.getSubmitterName());
}
}
}
/**
* 展示时间统一截断到秒:时间列已升毫秒精度(DATETIME(3),轨迹交织排序用),
* 对外展示口径保持秒级不变(存量前端零适配);排序用未截断的原值。
*/
private static String str(LocalDateTime t) {
return t == null ? null : t.truncatedTo(java.time.temporal.ChronoUnit.SECONDS).toString();
}
private static int normPage(Integer page) {
return page == null || page < 1 ? 0 : page - 1;
}
/** 空串按「不过滤」处理(前端清筛选可能传空值,避免误筛成空列表) */
private static String emptyToNull(String s) {
return s == null || s.trim().isEmpty() ? null : s.trim();
}
private static int normSize(Integer size) {
return size == null || size < 1 || size > 100 ? 10 : size;
}
private static <T> BasePageResult<T> toResult(long total, List<T> content, Integer page, Integer size) {
int pageIndex = page == null || page < 1 ? 1 : page;
int pageSize = size == null || size < 1 || size > 100 ? 10 : size;
int totalPages = (int) Math.ceil(total / (double) pageSize);
return new BasePageResult<>(content, (int) total, pageIndex, pageSize, totalPages,
pageIndex < totalPages);
}
}