- flw_def 唯一键 (sch,code,ver)→(sch,flow_guid,ver)(uk_flw_def_guid_ver,生产/本地已手工切索引):升版本 maxByGuid+1 与唯一键同口径,同编码多条流程线(work-leave 三条线实锤追尾)不再死锁 - 新建永远新开流程线 v1,删除同编码草稿复用;撞唯一键转"保存冲突"友好提示 - 新增 flw_def.app_key 归属应用列:save 全链路透传(升版本缺省继承源线、传非空覆盖);/admin/def/list 支持 appKey 过滤;新增 /admin/def/appkeys 应用清单(flw_def 已标注 ∪ 已注册回调) - FlowDefAdminServiceSaveTest 10 用例锁 save 分派语义
This commit is contained in:
@@ -54,8 +54,15 @@ public class FlowAdminController {
|
|||||||
|
|
||||||
@GetMapping("/def/list")
|
@GetMapping("/def/list")
|
||||||
public List<FlwDef> defList(BoaUser user,
|
public List<FlwDef> defList(BoaUser user,
|
||||||
@RequestParam(value = "status", required = false) Integer status) {
|
@RequestParam(value = "status", required = false) Integer status,
|
||||||
return defAdminService.list(adminGuard.requireAdmin(user, null), status);
|
@RequestParam(value = "appKey", required = false) String appKey) {
|
||||||
|
return defAdminService.list(adminGuard.requireAdmin(user, null), status, appKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 应用清单("按应用筛选"下拉):本校 flw_def 已标注 ∪ 已注册回调的应用 */
|
||||||
|
@GetMapping("/def/appkeys")
|
||||||
|
public List<String> defAppKeys(BoaUser user) {
|
||||||
|
return defAdminService.distinctAppKeys(adminGuard.requireAdmin(user, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/def/get")
|
@GetMapping("/def/get")
|
||||||
@@ -70,7 +77,8 @@ public class FlowAdminController {
|
|||||||
Long id = body.get("id") == null ? null : Long.valueOf(String.valueOf(body.get("id")));
|
Long id = body.get("id") == null ? null : Long.valueOf(String.valueOf(body.get("id")));
|
||||||
return defAdminService.save(schId, id,
|
return defAdminService.save(schId, id,
|
||||||
(String) body.get("code"), (String) body.get("name"),
|
(String) body.get("code"), (String) body.get("name"),
|
||||||
(String) body.get("contentJson"), (String) body.get("baseGuid"));
|
(String) body.get("contentJson"), (String) body.get("baseGuid"),
|
||||||
|
(String) body.get("appKey"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/def/publish/{id}")
|
@PostMapping("/def/publish/{id}")
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import java.time.LocalDateTime;
|
|||||||
@Setter
|
@Setter
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "flw_def",
|
@Table(name = "flw_def",
|
||||||
uniqueConstraints = @UniqueConstraint(name = "uk_flw_def_sch_code_ver",
|
uniqueConstraints = @UniqueConstraint(name = "uk_flw_def_guid_ver",
|
||||||
columnNames = {"sch_id", "code", "version"}),
|
columnNames = {"sch_id", "flow_guid", "version"}),
|
||||||
indexes = @Index(name = "idx_flw_def_guid", columnList = "sch_id, flow_guid, status, version"))
|
indexes = @Index(name = "idx_flw_def_guid", columnList = "sch_id, flow_guid, status, version"))
|
||||||
public class FlwDef extends BaseEntity {
|
public class FlwDef extends BaseEntity {
|
||||||
|
|
||||||
@@ -33,7 +33,10 @@ public class FlwDef extends BaseEntity {
|
|||||||
@Column(name = "flow_guid", nullable = false, length = 32)
|
@Column(name = "flow_guid", nullable = false, length = 32)
|
||||||
private String flowGuid;
|
private String flowGuid;
|
||||||
|
|
||||||
/** 流程编码(本应用内部管理键) */
|
/**
|
||||||
|
* 流程编码(管理端分类/检索标签,可重名)。2026-10-10 起唯一键改为 (sch_id, flow_guid, version),
|
||||||
|
* 编码不再承担唯一性——多条流程线(如同为请假的产假/丧假)可共用一个编码。
|
||||||
|
*/
|
||||||
@Column(name = "code", nullable = false, length = 64)
|
@Column(name = "code", nullable = false, length = 64)
|
||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
@@ -41,6 +44,10 @@ public class FlwDef extends BaseEntity {
|
|||||||
@Column(name = "name", nullable = false, length = 128)
|
@Column(name = "name", nullable = false, length = 128)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
/** 归属应用(子应用 appKey,可空=未分配;管理端"按应用筛选"维度,2026-10-10) */
|
||||||
|
@Column(name = "app_key", length = 64)
|
||||||
|
private String appKey;
|
||||||
|
|
||||||
/** 版本,发布后不可改,改定义=升版本 */
|
/** 版本,发布后不可改,改定义=升版本 */
|
||||||
@Column(name = "version", nullable = false)
|
@Column(name = "version", nullable = false)
|
||||||
private Integer version;
|
private Integer version;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.yunhai.flowcenter.repository;
|
|||||||
|
|
||||||
import com.yunhai.flowcenter.entity.FlwCallbackReg;
|
import com.yunhai.flowcenter.entity.FlwCallbackReg;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -14,4 +16,8 @@ public interface FlwCallbackRegRepository extends JpaRepository<FlwCallbackReg,
|
|||||||
List<FlwCallbackReg> findBySchIdAndFlowGuidAndStatus(Long schId, String flowGuid, Integer status);
|
List<FlwCallbackReg> findBySchIdAndFlowGuidAndStatus(Long schId, String flowGuid, Integer status);
|
||||||
|
|
||||||
List<FlwCallbackReg> findBySchIdOrderByUpdateTimeDesc(Long schId);
|
List<FlwCallbackReg> findBySchIdOrderByUpdateTimeDesc(Long schId);
|
||||||
|
|
||||||
|
/** "按应用筛选"下拉数据源之一:本校注册过回调的应用 */
|
||||||
|
@Query("select distinct r.appKey from FlwCallbackReg r where r.schId = :schId")
|
||||||
|
List<String> findDistinctAppKeysBySchId(@Param("schId") Long schId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,12 @@ public interface FlwDefRepository extends JpaRepository<FlwDef, Long> {
|
|||||||
/** 任意状态(回调注册只要求流程线存在) */
|
/** 任意状态(回调注册只要求流程线存在) */
|
||||||
Optional<FlwDef> findFirstBySchIdAndFlowGuidOrderByVersionDesc(Long schId, String flowGuid);
|
Optional<FlwDef> findFirstBySchIdAndFlowGuidOrderByVersionDesc(Long schId, String flowGuid);
|
||||||
|
|
||||||
/** 同名草稿(保存新建时复用,避免 uk 冲突) */
|
|
||||||
Optional<FlwDef> findFirstBySchIdAndCodeAndStatus(Long schId, String code, Integer status);
|
|
||||||
|
|
||||||
@Query("select coalesce(max(d.version), 0) from FlwDef d where d.schId = :schId and d.code = :code")
|
|
||||||
int maxVersionByCode(@Param("schId") Long schId, @Param("code") String code);
|
|
||||||
|
|
||||||
@Query("select coalesce(max(d.version), 0) from FlwDef d where d.schId = :schId and d.flowGuid = :flowGuid")
|
@Query("select coalesce(max(d.version), 0) from FlwDef d where d.schId = :schId and d.flowGuid = :flowGuid")
|
||||||
int maxVersionByGuid(@Param("schId") Long schId, @Param("flowGuid") String flowGuid);
|
int maxVersionByGuid(@Param("schId") Long schId, @Param("flowGuid") String flowGuid);
|
||||||
|
|
||||||
Optional<FlwDef> findFirstBySchIdAndCodeOrderByVersionDesc(Long schId, String code);
|
/** "按应用筛选"下拉数据源之一:本校 flw_def 已标注的应用 */
|
||||||
|
@Query("select distinct d.appKey from FlwDef d where d.schId = :schId and d.appKey is not null")
|
||||||
|
List<String> findDistinctAppKeys(@Param("schId") Long schId);
|
||||||
|
|
||||||
List<FlwDef> findBySchIdAndFlowGuidOrderByVersionDesc(Long schId, String flowGuid);
|
List<FlwDef> findBySchIdAndFlowGuidOrderByVersionDesc(Long schId, String flowGuid);
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,15 @@ import com.yunhai.flowcenter.repository.FlwDefRepository;
|
|||||||
import com.yunhai.platform.apistd.BizException;
|
import com.yunhai.platform.apistd.BizException;
|
||||||
import com.yunhai.platform.utils.exception.ErrorCode;
|
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -32,15 +35,28 @@ public class FlowDefAdminService {
|
|||||||
private final FlwDefRepository defRepository;
|
private final FlwDefRepository defRepository;
|
||||||
private final FlwAssigneeMapRepository mapRepository;
|
private final FlwAssigneeMapRepository mapRepository;
|
||||||
private final com.yunhai.flowcenter.repository.FlwRouterRepository routerRepository;
|
private final com.yunhai.flowcenter.repository.FlwRouterRepository routerRepository;
|
||||||
|
private final com.yunhai.flowcenter.repository.FlwCallbackRegRepository regRepository;
|
||||||
private final FlowEngine flowEngine;
|
private final FlowEngine flowEngine;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
/** 定义列表:status 空=全量(各流程线各版本都展示,管理端"版本历史"内聚在行操作里) */
|
/** 定义列表:status/appKey 可选过滤(各流程线各版本都展示;appKey 精确匹配,2026-10-10 按应用筛选) */
|
||||||
public List<FlwDef> list(Long schId, Integer status) {
|
public List<FlwDef> list(Long schId, Integer status, String appKey) {
|
||||||
if (status != null) {
|
List<FlwDef> rows = status != null
|
||||||
return defRepository.findBySchIdAndStatusOrderByUpdateTimeDesc(schId, status);
|
? defRepository.findBySchIdAndStatusOrderByUpdateTimeDesc(schId, status)
|
||||||
|
: defRepository.findBySchIdOrderByUpdateTimeDesc(schId);
|
||||||
|
String key = appKey == null ? null : appKey.trim();
|
||||||
|
if (key != null && !key.isEmpty()) {
|
||||||
|
rows = rows.stream().filter(d -> key.equals(d.getAppKey())).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
return defRepository.findBySchIdOrderByUpdateTimeDesc(schId);
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 应用清单("按应用筛选"下拉数据源):本校 flw_def 已标注 ∪ 已注册回调的应用,去重排序 */
|
||||||
|
public List<String> distinctAppKeys(Long schId) {
|
||||||
|
TreeSet<String> keys = new TreeSet<>();
|
||||||
|
keys.addAll(defRepository.findDistinctAppKeys(schId));
|
||||||
|
keys.addAll(regRepository.findDistinctAppKeysBySchId(schId));
|
||||||
|
return new ArrayList<>(keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 全量列表(管理端首页:各流程线最新版本+状态聚合展示简化为全量返回) */
|
/** 全量列表(管理端首页:各流程线最新版本+状态聚合展示简化为全量返回) */
|
||||||
@@ -54,14 +70,15 @@ public class FlowDefAdminService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存(新建语义按 baseGuid/同名草稿分派,避免 uk(sch,code,version) 冲突):
|
* 保存(2026-10-10 起唯一键为 uk(sch,flow_guid,version),编码降级为可重名的分类标签):
|
||||||
* - id 非空:草稿直改;已发布/停用=复制升版本(同 flowGuid,version=max+1)
|
* - id 非空:草稿直改;已发布/停用=复制升版本(同 flowGuid,version=max+1)
|
||||||
* - id 空 + baseGuid 非空:在该流程线上开新版本草稿(version=max+1,flowGuid 不变)——前端"升版本"
|
* - id 空 + baseGuid 非空:在该流程线上开新版本草稿(version=max+1,flowGuid 不变)——前端"升版本"
|
||||||
* - id 空 + 同名草稿存在:复用该草稿直改
|
* - id 空:永远新开一条流程线(flowGuid 生成,version=1);同编码多条线合法,前端保存前有软提示
|
||||||
* - id 空 + 全新 code:新流程线(flowGuid 生成,version=1)
|
* appKey=归属应用(筛选维度):新建/草稿直改直接落库;升版本(baseGuid/已发布行)缺省继承源线,传非空则覆盖
|
||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public FlwDef save(Long schId, Long id, String code, String name, String contentJson, String baseGuid) {
|
public FlwDef save(Long schId, Long id, String code, String name, String contentJson, String baseGuid, String appKey) {
|
||||||
|
String cleanAppKey = (appKey == null || appKey.trim().isEmpty()) ? null : appKey.trim();
|
||||||
// JSON 先解析再落库(语法错误不进库)
|
// JSON 先解析再落库(语法错误不进库)
|
||||||
FlowDefModel parsed = FlowDefModel.parse(objectMapper, contentJson);
|
FlowDefModel parsed = FlowDefModel.parse(objectMapper, contentJson);
|
||||||
if (code == null || code.trim().isEmpty() || name == null || name.trim().isEmpty()) {
|
if (code == null || code.trim().isEmpty() || name == null || name.trim().isEmpty()) {
|
||||||
@@ -69,8 +86,8 @@ public class FlowDefAdminService {
|
|||||||
}
|
}
|
||||||
FlwDef def;
|
FlwDef def;
|
||||||
if (id == null && baseGuid != null && !baseGuid.trim().isEmpty()) {
|
if (id == null && baseGuid != null && !baseGuid.trim().isEmpty()) {
|
||||||
// 在既有流程线上开新版本草稿
|
// 在既有流程线上开新版本草稿(appKey 缺省继承源线)
|
||||||
defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(schId, baseGuid.trim())
|
FlwDef base = defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(schId, baseGuid.trim())
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "baseGuid 流程线不存在"));
|
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "baseGuid 流程线不存在"));
|
||||||
def = new FlwDef();
|
def = new FlwDef();
|
||||||
def.setSchId(schId);
|
def.setSchId(schId);
|
||||||
@@ -78,25 +95,24 @@ public class FlowDefAdminService {
|
|||||||
def.setCode(code.trim());
|
def.setCode(code.trim());
|
||||||
def.setVersion(defRepository.maxVersionByGuid(schId, baseGuid.trim()) + 1);
|
def.setVersion(defRepository.maxVersionByGuid(schId, baseGuid.trim()) + 1);
|
||||||
def.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
|
def.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
|
||||||
|
def.setAppKey(cleanAppKey != null ? cleanAppKey : base.getAppKey());
|
||||||
} else if (id == null) {
|
} else if (id == null) {
|
||||||
FlwDef draft = defRepository.findFirstBySchIdAndCodeAndStatus(
|
// 新建:永远新开一条流程线,版本从 1 起(不再做同编码草稿复用)
|
||||||
schId, code.trim(), FlwEnums.DefStatus.DRAFT.getCode()).orElse(null);
|
|
||||||
if (draft != null) {
|
|
||||||
// 同名草稿复用直改
|
|
||||||
def = draft;
|
|
||||||
} else {
|
|
||||||
def = new FlwDef();
|
def = new FlwDef();
|
||||||
def.setSchId(schId);
|
def.setSchId(schId);
|
||||||
def.setFlowGuid(UUID.randomUUID().toString().replace("-", "").toLowerCase());
|
def.setFlowGuid(UUID.randomUUID().toString().replace("-", "").toLowerCase());
|
||||||
def.setCode(code.trim());
|
def.setCode(code.trim());
|
||||||
def.setVersion(defRepository.maxVersionByCode(schId, code.trim()) + 1);
|
def.setVersion(1);
|
||||||
def.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
|
def.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
|
||||||
}
|
def.setAppKey(cleanAppKey);
|
||||||
} else {
|
} else {
|
||||||
def = requireDef(schId, id);
|
def = requireDef(schId, id);
|
||||||
if (!def.getCode().equals(code.trim())) {
|
if (!def.getCode().equals(code.trim())) {
|
||||||
throw new BizException(ErrorCode.PARAM_ERROR, "流程编码不可修改");
|
throw new BizException(ErrorCode.PARAM_ERROR, "流程编码不可修改");
|
||||||
}
|
}
|
||||||
|
if (cleanAppKey != null) {
|
||||||
|
def.setAppKey(cleanAppKey);
|
||||||
|
}
|
||||||
if (def.getStatus() != FlwEnums.DefStatus.DRAFT.getCode()) {
|
if (def.getStatus() != FlwEnums.DefStatus.DRAFT.getCode()) {
|
||||||
// 升版本:复制行,保持 flowGuid
|
// 升版本:复制行,保持 flowGuid
|
||||||
FlwDef next = new FlwDef();
|
FlwDef next = new FlwDef();
|
||||||
@@ -105,6 +121,7 @@ public class FlowDefAdminService {
|
|||||||
next.setCode(def.getCode());
|
next.setCode(def.getCode());
|
||||||
next.setVersion(defRepository.maxVersionByGuid(schId, def.getFlowGuid()) + 1);
|
next.setVersion(defRepository.maxVersionByGuid(schId, def.getFlowGuid()) + 1);
|
||||||
next.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
|
next.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
|
||||||
|
next.setAppKey(cleanAppKey != null ? cleanAppKey : def.getAppKey());
|
||||||
def = next;
|
def = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +135,12 @@ public class FlowDefAdminService {
|
|||||||
if (parsed.name != null && !parsed.name.trim().isEmpty()) {
|
if (parsed.name != null && !parsed.name.trim().isEmpty()) {
|
||||||
def.setName(parsed.name.trim());
|
def.setName(parsed.name.trim());
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
return defRepository.save(def);
|
return defRepository.save(def);
|
||||||
|
} catch (DataIntegrityViolationException e) {
|
||||||
|
// 撞唯一键 (sch,flow_guid,version):并发升版本同一线时可能撞号,转可理解提示(原样抛出=笼统"内部服务错误")
|
||||||
|
throw new BizException(ErrorCode.PARAM_ERROR, "保存冲突:该流程可能正被他人同时升版本,请返回列表刷新后重试");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发布:静态校验(START/END 唯一、无孤儿无环、DEPT_KEY 有启用映射)后置为已发布 */
|
/** 发布:静态校验(START/END 唯一、无孤儿无环、DEPT_KEY 有启用映射)后置为已发布 */
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package com.yunhai.flowcenter.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.yunhai.flowcenter.engine.FlowEngine;
|
||||||
|
import com.yunhai.flowcenter.entity.FlwDef;
|
||||||
|
import com.yunhai.flowcenter.enums.FlwEnums;
|
||||||
|
import com.yunhai.flowcenter.repository.FlwAssigneeMapRepository;
|
||||||
|
import com.yunhai.flowcenter.repository.FlwDefRepository;
|
||||||
|
import com.yunhai.flowcenter.repository.FlwRouterRepository;
|
||||||
|
import com.yunhai.platform.apistd.BizException;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save() 分派语义(2026-10-10 唯一键改 (sch,flow_guid,version) 后):
|
||||||
|
* 新建=永远新线 v1(编码只是标签);升版本=复制行 maxByGuid+1;唯一键冲突转友好 BizException。
|
||||||
|
* 注:@Nested 在 surefire 2.22 的 -Dtest 过滤下发现不了,保持拍平写法。
|
||||||
|
*/
|
||||||
|
class FlowDefAdminServiceSaveTest {
|
||||||
|
|
||||||
|
private static final Long SCH = 3L;
|
||||||
|
private static final String JSON = "{\"nodes\":[],\"edges\":[]}";
|
||||||
|
|
||||||
|
private FlwDefRepository defRepository;
|
||||||
|
private FlowDefAdminService service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
defRepository = mock(FlwDefRepository.class);
|
||||||
|
service = new FlowDefAdminService(defRepository,
|
||||||
|
mock(FlwAssigneeMapRepository.class),
|
||||||
|
mock(FlwRouterRepository.class),
|
||||||
|
mock(com.yunhai.flowcenter.repository.FlwCallbackRegRepository.class),
|
||||||
|
mock(FlowEngine.class),
|
||||||
|
new ObjectMapper());
|
||||||
|
when(defRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FlwDef def(long id, String flowGuid, String code, int version, int status) {
|
||||||
|
FlwDef d = new FlwDef();
|
||||||
|
d.setId(id);
|
||||||
|
d.setSchId(SCH);
|
||||||
|
d.setFlowGuid(flowGuid);
|
||||||
|
d.setCode(code);
|
||||||
|
d.setVersion(version);
|
||||||
|
d.setStatus(status);
|
||||||
|
d.setContentJson(JSON);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 新建_永远新开流程线_版本从1起() {
|
||||||
|
ArgumentCaptor<FlwDef> cap = ArgumentCaptor.forClass(FlwDef.class);
|
||||||
|
FlwDef saved = service.save(SCH, null, "work-leave", "请假", JSON, null, null);
|
||||||
|
verify(defRepository).save(cap.capture());
|
||||||
|
assertEquals(cap.getValue(), saved);
|
||||||
|
assertEquals(1, saved.getVersion());
|
||||||
|
assertEquals(FlwEnums.DefStatus.DRAFT.getCode(), saved.getStatus());
|
||||||
|
assertEquals("work-leave", saved.getCode());
|
||||||
|
assertEquals(32, saved.getFlowGuid().length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 连续新建同编码_各得独立流程线() {
|
||||||
|
FlwDef a = service.save(SCH, null, "work-leave", "产假", JSON, null, null);
|
||||||
|
FlwDef b = service.save(SCH, null, "work-leave", "丧假", JSON, null, null);
|
||||||
|
assertEquals(1, a.getVersion());
|
||||||
|
assertEquals(1, b.getVersion());
|
||||||
|
assertNotEquals(a.getFlowGuid(), b.getFlowGuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void baseGuid_在线上开新版本草稿_max加1且保持flowGuid() {
|
||||||
|
when(defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(SCH, "G"))
|
||||||
|
.thenReturn(Optional.of(def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode())));
|
||||||
|
when(defRepository.maxVersionByGuid(SCH, "G")).thenReturn(6);
|
||||||
|
|
||||||
|
FlwDef saved = service.save(SCH, null, "work-leave", "请假", JSON, "G", null);
|
||||||
|
|
||||||
|
assertEquals("G", saved.getFlowGuid());
|
||||||
|
assertEquals(7, saved.getVersion());
|
||||||
|
assertEquals(FlwEnums.DefStatus.DRAFT.getCode(), saved.getStatus());
|
||||||
|
assertNull(saved.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 已发布行带id保存_复制升版本() {
|
||||||
|
when(defRepository.findById(9L)).thenReturn(Optional.of(def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode())));
|
||||||
|
when(defRepository.maxVersionByGuid(SCH, "G")).thenReturn(4);
|
||||||
|
|
||||||
|
FlwDef saved = service.save(SCH, 9L, "work-leave", "请假", JSON, null, null);
|
||||||
|
|
||||||
|
assertEquals("G", saved.getFlowGuid());
|
||||||
|
assertEquals(5, saved.getVersion());
|
||||||
|
assertEquals(FlwEnums.DefStatus.DRAFT.getCode(), saved.getStatus());
|
||||||
|
assertNull(saved.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 草稿行带id保存_直改本行() {
|
||||||
|
FlwDef draft = def(9L, "G", "work-leave", 5, FlwEnums.DefStatus.DRAFT.getCode());
|
||||||
|
when(defRepository.findById(9L)).thenReturn(Optional.of(draft));
|
||||||
|
|
||||||
|
FlwDef saved = service.save(SCH, 9L, "work-leave", "新名字", JSON, null, null);
|
||||||
|
|
||||||
|
assertSame(draft, saved);
|
||||||
|
assertEquals("新名字", saved.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void baseGuid流程线不存在_点名报错() {
|
||||||
|
when(defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(SCH, "GX"))
|
||||||
|
.thenReturn(Optional.empty());
|
||||||
|
BizException e = assertThrows(BizException.class,
|
||||||
|
() -> service.save(SCH, null, "work-leave", "请假", JSON, "GX", null));
|
||||||
|
assertTrue(e.getMessage().contains("baseGuid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 新建_归属应用直接落库_空白规整为null() {
|
||||||
|
assertEquals("notice", service.save(SCH, null, "work-leave", "请假", JSON, null, "notice").getAppKey());
|
||||||
|
assertNull(service.save(SCH, null, "work-leave", "请假", JSON, null, " ").getAppKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 升版本_appKey缺省继承源线_传非空则覆盖() {
|
||||||
|
FlwDef base = def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode());
|
||||||
|
base.setAppKey("notice");
|
||||||
|
when(defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(SCH, "G")).thenReturn(Optional.of(base));
|
||||||
|
when(defRepository.maxVersionByGuid(SCH, "G")).thenReturn(4);
|
||||||
|
|
||||||
|
assertEquals("notice", service.save(SCH, null, "work-leave", "请假", JSON, "G", null).getAppKey());
|
||||||
|
assertEquals("crm", service.save(SCH, null, "work-leave", "请假", JSON, "G", "crm").getAppKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 草稿直改_传appKey覆盖_不传保留() {
|
||||||
|
FlwDef draft = def(9L, "G", "work-leave", 5, FlwEnums.DefStatus.DRAFT.getCode());
|
||||||
|
draft.setAppKey("notice");
|
||||||
|
when(defRepository.findById(9L)).thenReturn(Optional.of(draft));
|
||||||
|
|
||||||
|
assertEquals("notice", service.save(SCH, 9L, "work-leave", "请假", JSON, null, null).getAppKey());
|
||||||
|
assertEquals("crm", service.save(SCH, 9L, "work-leave", "请假", JSON, null, "crm").getAppKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 并发升版本撞唯一键_转友好提示而非内部服务错误() {
|
||||||
|
when(defRepository.findById(9L)).thenReturn(Optional.of(def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode())));
|
||||||
|
when(defRepository.maxVersionByGuid(SCH, "G")).thenReturn(4);
|
||||||
|
when(defRepository.save(any())).thenThrow(new DataIntegrityViolationException("dup"));
|
||||||
|
|
||||||
|
BizException e = assertThrows(BizException.class,
|
||||||
|
() -> service.save(SCH, 9L, "work-leave", "请假", JSON, null, null));
|
||||||
|
assertTrue(e.getMessage().contains("保存冲突"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user