!135 test(improvementproposal): 新增评分/奖级/流水号纯函数与流程单测
* test(improvementproposal): 新增评分/奖级/流水号纯函数与流程单测 * refactor(improvementproposal): 提炼评分/奖级/流水号纯函数 ImprovementProposalSuppo… * test(tasklist): 新增看板 Service 单测并消除第 11 处重复(normalizePid 复用 support) * test(tasklist): 新增任务详情 Service/纯函数单测(权限与父子计数规则) * refactor(tasklist): 提炼任务详情纯函数 TaskListDetailSupport,消除内联重复 * test(supervision): 补齐中小头 Service/枚举/监听器单测 * refactor(xispeak): AfterSDW 监听器复用 FlowVariableSupport,消除第 10 处重复 * test(bgpartymatter): 新增党委会 Service 单测 * test(supervision): 新增反馈流程/反馈Service/问题清单三层校验单测 * refactor(xispeak): 反馈流程经办人分割复用 FlowVariableSupport,消除第 9 处重复 * test(xispeak): 补齐 P1 Service 与监听器单测并消除第 8 处部门ID清洗重复 * test(supervision): 删除已上提的旧 helper 测试文件 * test(supervision): 新增 xispeak 流程/监听器测试并将 helper 测试上提到 common.flow * refactor(supervision): 流程通用逻辑上提到 common.flow 并消除 fixcontact/xispeak 重复 * test(supervision): 补齐 dqinspecttask Service/监听器单测(validateProblemId 规则锁定)
This commit is contained in:
+7
-6
@@ -1,20 +1,21 @@
|
|||||||
package org.jeecg.modules.supervision.fixcontact.listener;
|
package org.jeecg.modules.supervision.common.flow;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定点联系流程审批辅助逻辑(纯函数,无外部依赖,可独立测试)。
|
* Flowable 流程通用支撑:执行实例层级解析(无外部依赖,可独立测试)。
|
||||||
*
|
*
|
||||||
* 规则:部门领导审批后,领导变量写入"上两级"执行实例;
|
* 规则(提炼自 fixcontact/xispeak 多处重复内联逻辑):
|
||||||
|
* 部门领导/经办人审批后,将审批人写入"上两级"执行实例;
|
||||||
* 存在第二层父级存第二层(大会签容器/外层作用域),否则存第一层。
|
* 存在第二层父级存第二层(大会签容器/外层作用域),否则存第一层。
|
||||||
*/
|
*/
|
||||||
public final class FixContactApprovalHelper {
|
public final class FlowExecutionSupport {
|
||||||
|
|
||||||
private FixContactApprovalHelper() {
|
private FlowExecutionSupport() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析领导变量应写入的目标执行实例 ID。
|
* 解析审批人应写入的目标执行实例 ID。
|
||||||
*
|
*
|
||||||
* @param parentId 第一层父级执行实例 ID
|
* @param parentId 第一层父级执行实例 ID
|
||||||
* @param grandParentId 第二层父级执行实例 ID(可能为空)
|
* @param grandParentId 第二层父级执行实例 ID(可能为空)
|
||||||
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
package org.jeecg.modules.supervision.fixcontact.flow;
|
package org.jeecg.modules.supervision.common.flow;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
@@ -8,16 +8,16 @@ import java.util.List;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定点联系流程表达式纯逻辑支撑(无外部依赖,可独立测试)。
|
* Flowable 流程通用支撑:流程变量字符串处理(无外部依赖,可独立测试)。
|
||||||
*
|
*
|
||||||
* 规则(提炼自 FixContactFlow 内联重复逻辑):
|
* 规则(提炼自 fixcontact/xispeak 多处重复内联逻辑):
|
||||||
* - splitToTrimmedList:逗号分割 → trim → 过滤空白元素
|
* - splitToTrimmedList:逗号分割 → trim → 过滤空白元素
|
||||||
* - countTokens:分割后有效元素个数
|
* - countTokens:分割后有效元素个数
|
||||||
* - cleanDeptId:流程变量部门 ID 清洗(去 [] 与首尾空白)
|
* - cleanDeptId:流程变量部门 ID 清洗(去 [] 与首尾空白)
|
||||||
*/
|
*/
|
||||||
public final class FixContactFlowSupport {
|
public final class FlowVariableSupport {
|
||||||
|
|
||||||
private FixContactFlowSupport() {
|
private FlowVariableSupport() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> splitToTrimmedList(String raw) {
|
public static List<String> splitToTrimmedList(String raw) {
|
||||||
+5
-4
@@ -8,6 +8,7 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.flowable.engine.RuntimeService;
|
import org.flowable.engine.RuntimeService;
|
||||||
import org.flowable.engine.delegate.DelegateExecution;
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
|
import org.jeecg.modules.supervision.common.flow.FlowVariableSupport;
|
||||||
import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
|
import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
|
||||||
import org.jeecg.modules.supervision.fixcontact.entity.FixedContact20260730;
|
import org.jeecg.modules.supervision.fixcontact.entity.FixedContact20260730;
|
||||||
import org.jeecg.modules.supervision.fixcontact.entity.FixedContactFeedback20260730;
|
import org.jeecg.modules.supervision.fixcontact.entity.FixedContactFeedback20260730;
|
||||||
@@ -258,7 +259,7 @@ public class FixContactFlow {
|
|||||||
execution.getProcessInstanceId());
|
execution.getProcessInstanceId());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return FixContactFlowSupport.cleanDeptId(deptVar);
|
return FlowVariableSupport.cleanDeptId(deptVar);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ${fixContactFlow.getCurrentDeptLeaderList(execution, deptId)}
|
// ${fixContactFlow.getCurrentDeptLeaderList(execution, deptId)}
|
||||||
@@ -357,7 +358,7 @@ public class FixContactFlow {
|
|||||||
if (value == null) {
|
if (value == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return FixContactFlowSupport.splitToTrimmedList(value.toString());
|
return FlowVariableSupport.splitToTrimmedList(value.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ${fixContactFlow.getVarAsListCount(execution, varName)}
|
// ${fixContactFlow.getVarAsListCount(execution, varName)}
|
||||||
@@ -367,7 +368,7 @@ public class FixContactFlow {
|
|||||||
|
|
||||||
// ${fixContactFlow.getListSize(data)}
|
// ${fixContactFlow.getListSize(data)}
|
||||||
public int getListSize(String data) {
|
public int getListSize(String data) {
|
||||||
return FixContactFlowSupport.countTokens(data);
|
return FlowVariableSupport.countTokens(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ${fixContactFlow.getIsNeedLeaderApprove(execution)}
|
// ${fixContactFlow.getIsNeedLeaderApprove(execution)}
|
||||||
@@ -606,7 +607,7 @@ public class FixContactFlow {
|
|||||||
if (StringUtils.isBlank(rawValue)) {
|
if (StringUtils.isBlank(rawValue)) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
List<String> result = FixContactFlowSupport.splitToTrimmedList(rawValue);
|
List<String> result = FlowVariableSupport.splitToTrimmedList(rawValue);
|
||||||
log.info("【定点联系-流程表达式】{} 解析结果: {}", logLabel, result);
|
log.info("【定点联系-流程表达式】{} 解析结果: {}", logLabel, result);
|
||||||
return result;
|
return result;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
+3
-2
@@ -7,6 +7,7 @@ import org.flowable.engine.RuntimeService;
|
|||||||
import org.flowable.engine.delegate.TaskListener;
|
import org.flowable.engine.delegate.TaskListener;
|
||||||
import org.flowable.engine.runtime.Execution;
|
import org.flowable.engine.runtime.Execution;
|
||||||
import org.flowable.task.service.delegate.DelegateTask;
|
import org.flowable.task.service.delegate.DelegateTask;
|
||||||
|
import org.jeecg.modules.supervision.common.flow.FlowExecutionSupport;
|
||||||
import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
|
import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -62,8 +63,8 @@ public class FixContactDeptLeaderApproveListener implements TaskListener {
|
|||||||
if (parentExecution == null) {
|
if (parentExecution == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 有第二层父级则存入第二层(大会签容器/外层作用域),否则存入第一层(规则见 FixContactApprovalHelper)
|
// 有第二层父级则存入第二层(大会签容器/外层作用域),否则存入第一层(规则见 FlowExecutionSupport)
|
||||||
String targetId = FixContactApprovalHelper.resolveTargetExecutionId(parentId, parentExecution.getParentId());
|
String targetId = FlowExecutionSupport.resolveTargetExecutionId(parentId, parentExecution.getParentId());
|
||||||
try {
|
try {
|
||||||
runtimeService.setVariableLocal(targetId, VARIABLE_NAME, assignee);
|
runtimeService.setVariableLocal(targetId, VARIABLE_NAME, assignee);
|
||||||
log.info("【定点联系-部门领导审批监听器】已存入上两级局部变量, targetExecutionId: {}, variableName: {}, assignee: {}",
|
log.info("【定点联系-部门领导审批监听器】已存入上两级局部变量, targetExecutionId: {}, variableName: {}, assignee: {}",
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@ import org.flowable.engine.delegate.TaskListener;
|
|||||||
import org.flowable.task.service.delegate.DelegateTask;
|
import org.flowable.task.service.delegate.DelegateTask;
|
||||||
import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
|
import org.jeecg.modules.supervision.fixcontact.constant.FixContactConstant;
|
||||||
import org.jeecg.modules.supervision.fixcontact.entity.FixedContact20260730;
|
import org.jeecg.modules.supervision.fixcontact.entity.FixedContact20260730;
|
||||||
import org.jeecg.modules.supervision.fixcontact.flow.FixContactFlowSupport;
|
import org.jeecg.modules.supervision.common.flow.FlowVariableSupport;
|
||||||
import org.jeecg.modules.supervision.fixcontact.service.IFixedContact20260730Service;
|
import org.jeecg.modules.supervision.fixcontact.service.IFixedContact20260730Service;
|
||||||
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
|
||||||
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
|
||||||
@@ -75,7 +75,7 @@ public class FixContactWorkerApproveListener implements TaskListener {
|
|||||||
log.warn("【定点联系-经办人会签监听器】未获取到子流程当前部门ID,跳过记录经办人, taskId: {}", delegateTask.getId());
|
log.warn("【定点联系-经办人会签监听器】未获取到子流程当前部门ID,跳过记录经办人, taskId: {}", delegateTask.getId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String deptId = FixContactFlowSupport.cleanDeptId(rawDeptVar);
|
String deptId = FlowVariableSupport.cleanDeptId(rawDeptVar);
|
||||||
|
|
||||||
FixedContact20260730 fixedContact = fixedContact20260730Service.getByIdForUpdate(rawBusinessKey.toString());
|
FixedContact20260730 fixedContact = fixedContact20260730Service.getByIdForUpdate(rawBusinessKey.toString());
|
||||||
if (fixedContact == null) {
|
if (fixedContact == null) {
|
||||||
|
|||||||
+9
-46
@@ -161,7 +161,7 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
|||||||
throw new IllegalStateException("生成提案编号失败,请稍后重试");
|
throw new IllegalStateException("生成提案编号失败,请稍后重试");
|
||||||
}
|
}
|
||||||
|
|
||||||
improvementProposal.setProposalNo(formatProposalNo(improvementProposal.getProposalDeptName(), proposalYear, sequence));
|
improvementProposal.setProposalNo(ImprovementProposalSupport.formatProposalNo(improvementProposal.getProposalDeptName(), proposalYear, sequence));
|
||||||
saveMain(improvementProposal, null, null);
|
saveMain(improvementProposal, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,9 +287,6 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
|||||||
return calendar.get(Calendar.YEAR);
|
return calendar.get(Calendar.YEAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatProposalNo(String proposalDeptName, int proposalYear, int sequence) {
|
|
||||||
return String.format("%s-%d年-%0" + PROPOSAL_NO_SEQUENCE_LENGTH + "d", proposalDeptName.trim(), proposalYear, sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@@ -382,7 +379,7 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
|||||||
public void saveInitialReview(ImprovementProposalBpmActionDTO action, LoginUser loginUser) {
|
public void saveInitialReview(ImprovementProposalBpmActionDTO action, LoginUser loginUser) {
|
||||||
validateAward(action.getInitialAward());
|
validateAward(action.getInitialAward());
|
||||||
ImprovementProposal proposal = validateRunningTask(action, loginUser, TASK_INITIAL_REVIEW);
|
ImprovementProposal proposal = validateRunningTask(action, loginUser, TASK_INITIAL_REVIEW);
|
||||||
String needDeptScore = needsDeptScore(action.getInitialAward()) ? "1" : "0";
|
String needDeptScore = ImprovementProposalSupport.needsDeptScore(action.getInitialAward()) ? "1" : "0";
|
||||||
ImprovementProposal update = new ImprovementProposal();
|
ImprovementProposal update = new ImprovementProposal();
|
||||||
update.setId(proposal.getId());
|
update.setId(proposal.getId());
|
||||||
update.setInitialAward(action.getInitialAward());
|
update.setInitialAward(action.getInitialAward());
|
||||||
@@ -416,8 +413,8 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 改善效果由四个子项在服务端计算,避免前端篡改汇总得分或加权总分。
|
// 改善效果由四个子项在服务端计算,避免前端篡改汇总得分或加权总分。
|
||||||
BigDecimal effectScore = calculateEffectScore(scoreAction);
|
BigDecimal effectScore = ImprovementProposalSupport.calculateEffectScore(scoreAction);
|
||||||
BigDecimal totalScore = calculateTotalScore(scoreAction, effectScore);
|
BigDecimal totalScore = ImprovementProposalSupport.calculateTotalScore(scoreAction, effectScore);
|
||||||
if (existingScore == null) {
|
if (existingScore == null) {
|
||||||
ImprovementDeptScore newScore = new ImprovementDeptScore();
|
ImprovementDeptScore newScore = new ImprovementDeptScore();
|
||||||
newScore.setMainId(lockedProposal.getId());
|
newScore.setMainId(lockedProposal.getId());
|
||||||
@@ -487,7 +484,7 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
|||||||
validateAward(action.getLeaderFinalAward());
|
validateAward(action.getLeaderFinalAward());
|
||||||
ImprovementProposal proposal = validateRunningTask(action, loginUser, TASK_LEADER_AWARD);
|
ImprovementProposal proposal = validateRunningTask(action, loginUser, TASK_LEADER_AWARD);
|
||||||
String awardBase = resolveLeaderAwardBase(proposal);
|
String awardBase = resolveLeaderAwardBase(proposal);
|
||||||
if (awardRank(action.getLeaderFinalAward()) < awardRank(awardBase)) {
|
if (ImprovementProposalSupport.awardRank(action.getLeaderFinalAward()) < ImprovementProposalSupport.awardRank(awardBase)) {
|
||||||
throw new IllegalStateException("负责人确认奖项只能选择当前档位或降档奖项");
|
throw new IllegalStateException("负责人确认奖项只能选择当前档位或降档奖项");
|
||||||
}
|
}
|
||||||
String needInstituteVote = AWARD_GOLD.equals(action.getLeaderFinalAward()) ? "1" : "0";
|
String needInstituteVote = AWARD_GOLD.equals(action.getLeaderFinalAward()) ? "1" : "0";
|
||||||
@@ -756,21 +753,8 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal calculateEffectScore(ImprovementDeptScoreBpmDTO scoreAction) {
|
|
||||||
return calculateEffectScore(scoreAction.getSafetyScore(), scoreAction.getQualityScore(), scoreAction.getEfficiencyScore(), scoreAction.getCostScore());
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal calculateEffectScore(BigDecimal safetyScore, BigDecimal qualityScore, BigDecimal efficiencyScore, BigDecimal costScore) {
|
|
||||||
return safetyScore.add(qualityScore).add(efficiencyScore).add(costScore).divide(BigDecimal.valueOf(4), 2, RoundingMode.HALF_UP);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal calculateTotalScore(ImprovementDeptScoreBpmDTO scoreAction, BigDecimal effectScore) {
|
|
||||||
return effectScore.multiply(new BigDecimal("0.50"))
|
|
||||||
.add(scoreAction.getPromotionScore().multiply(new BigDecimal("0.20")))
|
|
||||||
.add(scoreAction.getOriginalityScore().multiply(new BigDecimal("0.20")))
|
|
||||||
.add(scoreAction.getEffortScore().multiply(new BigDecimal("0.10")))
|
|
||||||
.setScale(2, RoundingMode.HALF_UP);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fillDeptScoreDraft(ImprovementDeptScore target, ImprovementDeptScoreBpmDTO source, BigDecimal effectScore, BigDecimal totalScore) {
|
private void fillDeptScoreDraft(ImprovementDeptScore target, ImprovementDeptScoreBpmDTO source, BigDecimal effectScore, BigDecimal totalScore) {
|
||||||
target.setSafetyScore(source.getSafetyScore());
|
target.setSafetyScore(source.getSafetyScore());
|
||||||
@@ -795,7 +779,7 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
|||||||
validateScoreValue(score.getPromotionScore(), "可推广度");
|
validateScoreValue(score.getPromotionScore(), "可推广度");
|
||||||
validateScoreValue(score.getOriginalityScore(), "独创性");
|
validateScoreValue(score.getOriginalityScore(), "独创性");
|
||||||
validateScoreValue(score.getEffortScore(), "努力度");
|
validateScoreValue(score.getEffortScore(), "努力度");
|
||||||
BigDecimal calculatedEffectScore = calculateEffectScore(score.getSafetyScore(), score.getQualityScore(), score.getEfficiencyScore(), score.getCostScore());
|
BigDecimal calculatedEffectScore = ImprovementProposalSupport.calculateEffectScore(score.getSafetyScore(), score.getQualityScore(), score.getEfficiencyScore(), score.getCostScore());
|
||||||
if (score.getEffectScore() == null || score.getEffectScore().compareTo(calculatedEffectScore) != 0 || score.getTotalScore() == null) {
|
if (score.getEffectScore() == null || score.getEffectScore().compareTo(calculatedEffectScore) != 0 || score.getTotalScore() == null) {
|
||||||
throw new IllegalStateException("请先保存完整个人评分");
|
throw new IllegalStateException("请先保存完整个人评分");
|
||||||
}
|
}
|
||||||
@@ -820,22 +804,10 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
|||||||
ImprovementProposal summary = new ImprovementProposal();
|
ImprovementProposal summary = new ImprovementProposal();
|
||||||
summary.setId(proposal.getId());
|
summary.setId(proposal.getId());
|
||||||
summary.setDeptAvgScore(averageScore);
|
summary.setDeptAvgScore(averageScore);
|
||||||
summary.setSuggestedAward(resolveSuggestedAward(averageScore));
|
summary.setSuggestedAward(ImprovementProposalSupport.resolveSuggestedAward(averageScore));
|
||||||
improvementProposalMapper.updateById(summary);
|
improvementProposalMapper.updateById(summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveSuggestedAward(BigDecimal averageScore) {
|
|
||||||
if (averageScore.compareTo(BigDecimal.valueOf(80)) >= 0) {
|
|
||||||
return AWARD_GOLD;
|
|
||||||
}
|
|
||||||
if (averageScore.compareTo(BigDecimal.valueOf(60)) >= 0) {
|
|
||||||
return AWARD_SILVER;
|
|
||||||
}
|
|
||||||
if (averageScore.compareTo(BigDecimal.valueOf(40)) >= 0) {
|
|
||||||
return AWARD_BRONZE;
|
|
||||||
}
|
|
||||||
return AWARD_PARTICIPATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ImprovementProposal validateRunningTask(ImprovementProposalBpmActionDTO action, LoginUser loginUser, String expectedTaskDefKey) {
|
private ImprovementProposal validateRunningTask(ImprovementProposalBpmActionDTO action, LoginUser loginUser, String expectedTaskDefKey) {
|
||||||
if (action == null || oConvertUtils.isEmpty(action.getMainId()) || oConvertUtils.isEmpty(action.getProcessInstanceId())
|
if (action == null || oConvertUtils.isEmpty(action.getMainId()) || oConvertUtils.isEmpty(action.getProcessInstanceId())
|
||||||
@@ -858,33 +830,24 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validateAward(String award) {
|
private void validateAward(String award) {
|
||||||
if (!isValidAward(award)) {
|
if (!ImprovementProposalSupport.isValidAward(award)) {
|
||||||
throw new IllegalStateException("请选择有效的奖项");
|
throw new IllegalStateException("请选择有效的奖项");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidAward(String award) {
|
|
||||||
return AWARD_GOLD.equals(award) || AWARD_SILVER.equals(award) || AWARD_BRONZE.equals(award) || AWARD_PARTICIPATION.equals(award);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 负责人只能在当前评定档位内确认或降档:经过评分时以系统建议奖项为准,未评分时以初评奖项为准。
|
* 负责人只能在当前评定档位内确认或降档:经过评分时以系统建议奖项为准,未评分时以初评奖项为准。
|
||||||
*/
|
*/
|
||||||
private String resolveLeaderAwardBase(ImprovementProposal proposal) {
|
private String resolveLeaderAwardBase(ImprovementProposal proposal) {
|
||||||
String awardBase = "1".equals(proposal.getNeedDeptScore()) ? proposal.getSuggestedAward() : proposal.getInitialAward();
|
String awardBase = "1".equals(proposal.getNeedDeptScore()) ? proposal.getSuggestedAward() : proposal.getInitialAward();
|
||||||
if (!isValidAward(awardBase)) {
|
if (!ImprovementProposalSupport.isValidAward(awardBase)) {
|
||||||
throw new IllegalStateException("部门评议建议奖项尚未生成,不能确认负责人奖项");
|
throw new IllegalStateException("部门评议建议奖项尚未生成,不能确认负责人奖项");
|
||||||
}
|
}
|
||||||
return awardBase;
|
return awardBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int awardRank(String award) {
|
|
||||||
return Integer.parseInt(award);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean needsDeptScore(String award) {
|
|
||||||
return AWARD_GOLD.equals(award) || AWARD_SILVER.equals(award);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 任何会改变主表生命周期的操作都必须先锁定并确认仍是未发起草稿,
|
* 任何会改变主表生命周期的操作都必须先锁定并确认仍是未发起草稿,
|
||||||
|
|||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
package org.jeecg.modules.supervision.improvementproposal.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.dto.ImprovementDeptScoreBpmDTO;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 改善提案纯逻辑支撑(无外部依赖,可独立测试)。
|
||||||
|
*
|
||||||
|
* 规则(提炼自 ImprovementProposalServiceImpl 内联逻辑):
|
||||||
|
* - formatProposalNo:流水号格式化(部门-年份-4 位序号)
|
||||||
|
* - calculateEffectScore:四维效果分平均(HALF_UP 2 位)
|
||||||
|
* - calculateTotalScore:加权总分(效果 0.5 / 推广 0.2 / 独创 0.2 / 努力 0.1)
|
||||||
|
* - resolveSuggestedAward:按平均分建议奖级(≥80 金 / ≥60 银 / ≥40 铜 / 参与)
|
||||||
|
* - isValidAward / awardRank / needsDeptScore:奖级合法性/排名/是否需要部门评分
|
||||||
|
*/
|
||||||
|
public final class ImprovementProposalSupport {
|
||||||
|
|
||||||
|
public static final String AWARD_GOLD = "0";
|
||||||
|
public static final String AWARD_SILVER = "1";
|
||||||
|
public static final String AWARD_BRONZE = "2";
|
||||||
|
public static final String AWARD_PARTICIPATION = "3";
|
||||||
|
|
||||||
|
private static final int PROPOSAL_NO_SEQUENCE_LENGTH = 4;
|
||||||
|
|
||||||
|
private ImprovementProposalSupport() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatProposalNo(String proposalDeptName, int proposalYear, int sequence) {
|
||||||
|
return String.format("%s-%d年-%0" + PROPOSAL_NO_SEQUENCE_LENGTH + "d", proposalDeptName.trim(), proposalYear, sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigDecimal calculateEffectScore(BigDecimal safetyScore, BigDecimal qualityScore,
|
||||||
|
BigDecimal efficiencyScore, BigDecimal costScore) {
|
||||||
|
return safetyScore.add(qualityScore).add(efficiencyScore).add(costScore)
|
||||||
|
.divide(BigDecimal.valueOf(4), 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DTO 重载(兼容原调用点) */
|
||||||
|
public static BigDecimal calculateEffectScore(ImprovementDeptScoreBpmDTO scoreAction) {
|
||||||
|
return calculateEffectScore(scoreAction.getSafetyScore(), scoreAction.getQualityScore(),
|
||||||
|
scoreAction.getEfficiencyScore(), scoreAction.getCostScore());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigDecimal calculateTotalScore(BigDecimal effectScore, BigDecimal promotionScore,
|
||||||
|
BigDecimal originalityScore, BigDecimal effortScore) {
|
||||||
|
return effectScore.multiply(new BigDecimal("0.50"))
|
||||||
|
.add(promotionScore.multiply(new BigDecimal("0.20")))
|
||||||
|
.add(originalityScore.multiply(new BigDecimal("0.20")))
|
||||||
|
.add(effortScore.multiply(new BigDecimal("0.10")))
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DTO 重载(兼容原调用点) */
|
||||||
|
public static BigDecimal calculateTotalScore(ImprovementDeptScoreBpmDTO scoreAction, BigDecimal effectScore) {
|
||||||
|
return calculateTotalScore(effectScore, scoreAction.getPromotionScore(),
|
||||||
|
scoreAction.getOriginalityScore(), scoreAction.getEffortScore());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String resolveSuggestedAward(BigDecimal averageScore) {
|
||||||
|
if (averageScore.compareTo(BigDecimal.valueOf(80)) >= 0) {
|
||||||
|
return AWARD_GOLD;
|
||||||
|
}
|
||||||
|
if (averageScore.compareTo(BigDecimal.valueOf(60)) >= 0) {
|
||||||
|
return AWARD_SILVER;
|
||||||
|
}
|
||||||
|
if (averageScore.compareTo(BigDecimal.valueOf(40)) >= 0) {
|
||||||
|
return AWARD_BRONZE;
|
||||||
|
}
|
||||||
|
return AWARD_PARTICIPATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidAward(String award) {
|
||||||
|
return AWARD_GOLD.equals(award) || AWARD_SILVER.equals(award)
|
||||||
|
|| AWARD_BRONZE.equals(award) || AWARD_PARTICIPATION.equals(award);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int awardRank(String award) {
|
||||||
|
return Integer.parseInt(award);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean needsDeptScore(String award) {
|
||||||
|
return AWARD_GOLD.equals(award) || AWARD_SILVER.equals(award);
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
package org.jeecg.modules.supervision.tasklist.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务清单详情纯逻辑支撑(无外部依赖,可独立测试)。
|
||||||
|
*
|
||||||
|
* 规则(提炼自 TaskListDetialServiceImpl 内联逻辑):
|
||||||
|
* - normalizePid:空 pid 归一为 null
|
||||||
|
* - collectUserIds:逗号分隔用户ID收集到集合(trim + 去空)
|
||||||
|
* - buildNamesFromMap:ID 列表 → 姓名列表(按映射,保序拼接)
|
||||||
|
* - mergeIds:两组用户ID合并去重(LinkedHashSet 保序)
|
||||||
|
*/
|
||||||
|
public final class TaskListDetailSupport {
|
||||||
|
|
||||||
|
private TaskListDetailSupport() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalizePid(String pid) {
|
||||||
|
return oConvertUtils.isNotEmpty(pid) ? pid : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void collectUserIds(String idsStr, Set<String> idSet) {
|
||||||
|
if (oConvertUtils.isEmpty(idsStr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String id : idsStr.split(",")) {
|
||||||
|
String trimmed = id.trim();
|
||||||
|
if (oConvertUtils.isNotEmpty(trimmed)) {
|
||||||
|
idSet.add(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String buildNamesFromMap(String idsStr, Map<String, String> idToRealname) {
|
||||||
|
if (oConvertUtils.isEmpty(idsStr) || idToRealname.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String id : idsStr.split(",")) {
|
||||||
|
String trimmed = id.trim();
|
||||||
|
if (oConvertUtils.isNotEmpty(trimmed)) {
|
||||||
|
String name = idToRealname.get(trimmed);
|
||||||
|
if (name != null) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
sb.append(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.length() > 0 ? sb.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String mergeIds(String existingIds, String newIds) {
|
||||||
|
Set<String> idSet = new LinkedHashSet<>();
|
||||||
|
collectUserIds(existingIds, idSet);
|
||||||
|
collectUserIds(newIds, idSet);
|
||||||
|
return idSet.isEmpty() ? null : String.join(",", idSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-64
@@ -75,7 +75,7 @@ public class TaskListDetialServiceImpl extends ServiceImpl<TaskListDetialMapper,
|
|||||||
entity.setTaskDesc(req.getTaskDesc());
|
entity.setTaskDesc(req.getTaskDesc());
|
||||||
entity.setPriority(req.getPriority());
|
entity.setPriority(req.getPriority());
|
||||||
entity.setType(req.getType() != null ? req.getType() : "1");
|
entity.setType(req.getType() != null ? req.getType() : "1");
|
||||||
entity.setPid(normalizePid(req.getPid()));
|
entity.setPid(TaskListDetailSupport.normalizePid(req.getPid()));
|
||||||
entity.setTaskStatus(0);
|
entity.setTaskStatus(0);
|
||||||
entity.setSubTaskCount(0);
|
entity.setSubTaskCount(0);
|
||||||
entity.setCompletedSubTaskCount(0);
|
entity.setCompletedSubTaskCount(0);
|
||||||
@@ -98,10 +98,10 @@ public class TaskListDetialServiceImpl extends ServiceImpl<TaskListDetialMapper,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.getSortOrder() != null) {
|
if (req.getSortOrder() != null) {
|
||||||
taskListDetialMapper.shiftSortOrderUp(req.getMainId(), normalizePid(req.getPid()), req.getSortOrder());
|
taskListDetialMapper.shiftSortOrderUp(req.getMainId(), TaskListDetailSupport.normalizePid(req.getPid()), req.getSortOrder());
|
||||||
entity.setSortOrder(req.getSortOrder());
|
entity.setSortOrder(req.getSortOrder());
|
||||||
} else {
|
} else {
|
||||||
Integer maxSort = taskListDetialMapper.getMaxSortOrder(req.getMainId(), normalizePid(req.getPid()));
|
Integer maxSort = taskListDetialMapper.getMaxSortOrder(req.getMainId(), TaskListDetailSupport.normalizePid(req.getPid()));
|
||||||
entity.setSortOrder(maxSort + 1);
|
entity.setSortOrder(maxSort + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,8 +366,8 @@ public class TaskListDetialServiceImpl extends ServiceImpl<TaskListDetialMapper,
|
|||||||
throw new RuntimeException("可阅读者无法移动任务");
|
throw new RuntimeException("可阅读者无法移动任务");
|
||||||
}
|
}
|
||||||
|
|
||||||
String oldPid = normalizePid(task.getPid());
|
String oldPid = TaskListDetailSupport.normalizePid(task.getPid());
|
||||||
String newPid = normalizePid(req.getTargetPid());
|
String newPid = TaskListDetailSupport.normalizePid(req.getTargetPid());
|
||||||
|
|
||||||
boolean pidChanged = (oldPid == null && newPid != null) || (oldPid != null && !oldPid.equals(newPid));
|
boolean pidChanged = (oldPid == null && newPid != null) || (oldPid != null && !oldPid.equals(newPid));
|
||||||
|
|
||||||
@@ -552,9 +552,6 @@ public class TaskListDetialServiceImpl extends ServiceImpl<TaskListDetialMapper,
|
|||||||
return perm != null ? perm.getPermission() : null;
|
return perm != null ? perm.getPermission() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String normalizePid(String pid) {
|
|
||||||
return oConvertUtils.isNotEmpty(pid) ? pid : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fillCreateByName(List<TaskListDetial> list) {
|
private void fillCreateByName(List<TaskListDetial> list) {
|
||||||
if (list == null || list.isEmpty()) {
|
if (list == null || list.isEmpty()) {
|
||||||
@@ -568,9 +565,9 @@ public class TaskListDetialServiceImpl extends ServiceImpl<TaskListDetialMapper,
|
|||||||
if (oConvertUtils.isNotEmpty(task.getCreateBy())) {
|
if (oConvertUtils.isNotEmpty(task.getCreateBy())) {
|
||||||
allUsernames.add(task.getCreateBy());
|
allUsernames.add(task.getCreateBy());
|
||||||
}
|
}
|
||||||
collectUserIds(task.getAssigneeId(), allUserIds);
|
TaskListDetailSupport.collectUserIds(task.getAssigneeId(), allUserIds);
|
||||||
collectUserIds(task.getParticipantId(), allUserIds);
|
TaskListDetailSupport.collectUserIds(task.getParticipantId(), allUserIds);
|
||||||
collectUserIds(task.getFollowersId(), allUserIds);
|
TaskListDetailSupport.collectUserIds(task.getFollowersId(), allUserIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 批量查询,构建映射表
|
// 2. 批量查询,构建映射表
|
||||||
@@ -603,48 +600,18 @@ public class TaskListDetialServiceImpl extends ServiceImpl<TaskListDetialMapper,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oConvertUtils.isNotEmpty(task.getAssigneeId()) && oConvertUtils.isEmpty(task.getAssigneeName())) {
|
if (oConvertUtils.isNotEmpty(task.getAssigneeId()) && oConvertUtils.isEmpty(task.getAssigneeName())) {
|
||||||
task.setAssigneeName(buildNamesFromMap(task.getAssigneeId(), idToRealname));
|
task.setAssigneeName(TaskListDetailSupport.buildNamesFromMap(task.getAssigneeId(), idToRealname));
|
||||||
}
|
}
|
||||||
if (oConvertUtils.isNotEmpty(task.getParticipantId()) && oConvertUtils.isEmpty(task.getParticipantName())) {
|
if (oConvertUtils.isNotEmpty(task.getParticipantId()) && oConvertUtils.isEmpty(task.getParticipantName())) {
|
||||||
task.setParticipantName(buildNamesFromMap(task.getParticipantId(), idToRealname));
|
task.setParticipantName(TaskListDetailSupport.buildNamesFromMap(task.getParticipantId(), idToRealname));
|
||||||
}
|
}
|
||||||
if (oConvertUtils.isNotEmpty(task.getFollowersId()) && oConvertUtils.isEmpty(task.getFollowersName())) {
|
if (oConvertUtils.isNotEmpty(task.getFollowersId()) && oConvertUtils.isEmpty(task.getFollowersName())) {
|
||||||
task.setFollowersName(buildNamesFromMap(task.getFollowersId(), idToRealname));
|
task.setFollowersName(TaskListDetailSupport.buildNamesFromMap(task.getFollowersId(), idToRealname));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void collectUserIds(String idsStr, Set<String> idSet) {
|
|
||||||
if (oConvertUtils.isEmpty(idsStr)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (String id : idsStr.split(",")) {
|
|
||||||
String trimmed = id.trim();
|
|
||||||
if (oConvertUtils.isNotEmpty(trimmed)) {
|
|
||||||
idSet.add(trimmed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildNamesFromMap(String idsStr, Map<String, String> idToRealname) {
|
|
||||||
if (oConvertUtils.isEmpty(idsStr) || idToRealname.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (String id : idsStr.split(",")) {
|
|
||||||
String trimmed = id.trim();
|
|
||||||
if (oConvertUtils.isNotEmpty(trimmed)) {
|
|
||||||
String name = idToRealname.get(trimmed);
|
|
||||||
if (name != null) {
|
|
||||||
if (sb.length() > 0) {
|
|
||||||
sb.append(",");
|
|
||||||
}
|
|
||||||
sb.append(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.length() > 0 ? sb.toString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String translateUserIdsToNames(String ids) {
|
private String translateUserIdsToNames(String ids) {
|
||||||
if (oConvertUtils.isEmpty(ids)) {
|
if (oConvertUtils.isEmpty(ids)) {
|
||||||
@@ -664,26 +631,6 @@ public class TaskListDetialServiceImpl extends ServiceImpl<TaskListDetialMapper,
|
|||||||
return names.toString();
|
return names.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String mergeIds(String existingIds, String newIds) {
|
|
||||||
Set<String> idSet = new LinkedHashSet<>();
|
|
||||||
if (oConvertUtils.isNotEmpty(existingIds)) {
|
|
||||||
for (String id : existingIds.split(",")) {
|
|
||||||
String trimmed = id.trim();
|
|
||||||
if (oConvertUtils.isNotEmpty(trimmed)) {
|
|
||||||
idSet.add(trimmed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oConvertUtils.isNotEmpty(newIds)) {
|
|
||||||
for (String id : newIds.split(",")) {
|
|
||||||
String trimmed = id.trim();
|
|
||||||
if (oConvertUtils.isNotEmpty(trimmed)) {
|
|
||||||
idSet.add(trimmed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return idSet.isEmpty() ? null : String.join(",", idSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String filterUsersBySecLevel(String userIds, Integer listSecLevel) {
|
private String filterUsersBySecLevel(String userIds, Integer listSecLevel) {
|
||||||
if (oConvertUtils.isEmpty(userIds)) return userIds;
|
if (oConvertUtils.isEmpty(userIds)) return userIds;
|
||||||
|
|||||||
+2
-5
@@ -233,7 +233,7 @@ public class TaskListServiceImpl extends ServiceImpl<TaskListMapper, TaskList> i
|
|||||||
throw new RuntimeException("分组不支持移动操作");
|
throw new RuntimeException("分组不支持移动操作");
|
||||||
}
|
}
|
||||||
|
|
||||||
String oldPid = normalizePid(favorite.getPid());
|
String oldPid = TaskListDetailSupport.normalizePid(favorite.getPid());
|
||||||
|
|
||||||
String newPid = null;
|
String newPid = null;
|
||||||
if (req.getTargetGroupId() != null) {
|
if (req.getTargetGroupId() != null) {
|
||||||
@@ -242,7 +242,7 @@ public class TaskListServiceImpl extends ServiceImpl<TaskListMapper, TaskList> i
|
|||||||
if (targetGroup == null || !"0".equals(targetGroup.getType()) || !userId.equals(targetGroup.getUserId())) {
|
if (targetGroup == null || !"0".equals(targetGroup.getType()) || !userId.equals(targetGroup.getUserId())) {
|
||||||
throw new RuntimeException("目标分组不存在或无权限");
|
throw new RuntimeException("目标分组不存在或无权限");
|
||||||
}
|
}
|
||||||
newPid = normalizePid(req.getTargetGroupId());
|
newPid = TaskListDetailSupport.normalizePid(req.getTargetGroupId());
|
||||||
} else {
|
} else {
|
||||||
newPid = "";
|
newPid = "";
|
||||||
}
|
}
|
||||||
@@ -700,9 +700,6 @@ public class TaskListServiceImpl extends ServiceImpl<TaskListMapper, TaskList> i
|
|||||||
return taskListMapper.selectCount(query).intValue();
|
return taskListMapper.selectCount(query).intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String normalizePid(String pid) {
|
|
||||||
return oConvertUtils.isNotEmpty(pid) ? pid : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TaskList> enrichListSummaries(List<TaskList> lists, String currentUserId) {
|
private List<TaskList> enrichListSummaries(List<TaskList> lists, String currentUserId) {
|
||||||
if (lists == null || lists.isEmpty()) {
|
if (lists == null || lists.isEmpty()) {
|
||||||
|
|||||||
+2
-4
@@ -10,6 +10,7 @@ import org.flowable.engine.RuntimeService;
|
|||||||
import org.flowable.engine.delegate.DelegateExecution;
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.jeecg.modules.supervision.common.OrgConfig;
|
import org.jeecg.modules.supervision.common.OrgConfig;
|
||||||
import org.jeecg.modules.supervision.xispeak.config.XiSpeakFeedbackConfig;
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakFeedbackConfig;
|
||||||
|
import org.jeecg.modules.supervision.common.flow.FlowVariableSupport;
|
||||||
import org.jeecg.common.constant.SymbolConstant;
|
import org.jeecg.common.constant.SymbolConstant;
|
||||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
||||||
@@ -198,10 +199,7 @@ public class XiSpeakFeedbackFlow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 将 "张三, 李四 " 转换为 ["张三", "李四"]
|
// 将 "张三, 李四 " 转换为 ["张三", "李四"]
|
||||||
return Arrays.stream(implDeptWorker.split(","))
|
return FlowVariableSupport.splitToTrimmedList(implDeptWorker);
|
||||||
.map(String::trim) // 去掉前后空格
|
|
||||||
.filter(StringUtils::isNotBlank) // 过滤掉空字符串(防止出现 "张三,,李四")
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+5
-22
@@ -15,6 +15,7 @@ import org.jeecg.common.system.api.ISysBaseAPI;
|
|||||||
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
||||||
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
|
||||||
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
|
||||||
|
import org.jeecg.modules.supervision.common.flow.FlowVariableSupport;
|
||||||
import org.jeecg.modules.extbpm.process.common.expression.FlowNodeExpression;
|
import org.jeecg.modules.extbpm.process.common.expression.FlowNodeExpression;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -125,16 +126,7 @@ public class XiSpeakFlow {
|
|||||||
String rawValue = data.getString(codeName);
|
String rawValue = data.getString(codeName);
|
||||||
|
|
||||||
// 4. 转换逗号分隔的字符串为 List
|
// 4. 转换逗号分隔的字符串为 List
|
||||||
List<String> resultList;
|
List<String> resultList = FlowVariableSupport.splitToTrimmedList(rawValue);
|
||||||
if (org.apache.commons.lang.StringUtils.isBlank(rawValue)) {
|
|
||||||
resultList = Collections.emptyList();
|
|
||||||
} else {
|
|
||||||
// 使用逗号分割,并过滤掉每个元素前后的空格
|
|
||||||
resultList = Arrays.stream(rawValue.split(","))
|
|
||||||
.map(String::trim)
|
|
||||||
.filter(org.apache.commons.lang.StringUtils::isNotBlank) // 过滤掉因连续逗号产生的空元素
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("{} 业务解析结果: {}", logLabel, resultList);
|
log.info("{} 业务解析结果: {}", logLabel, resultList);
|
||||||
return resultList;
|
return resultList;
|
||||||
@@ -285,7 +277,7 @@ public class XiSpeakFlow {
|
|||||||
if (org.apache.commons.lang.StringUtils.isBlank(businessKey) || rawDeptVar == null) {
|
if (org.apache.commons.lang.StringUtils.isBlank(businessKey) || rawDeptVar == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String deptId = rawDeptVar.toString().replace("[", "").replace("]", "").trim();
|
String deptId = FlowVariableSupport.cleanDeptId(rawDeptVar);
|
||||||
BgXiSpeak xiSpeak = bgXiSpeakService.getById(businessKey);
|
BgXiSpeak xiSpeak = bgXiSpeakService.getById(businessKey);
|
||||||
if (xiSpeak == null || xiSpeak.getApproveInfo() == null) {
|
if (xiSpeak == null || xiSpeak.getApproveInfo() == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -340,13 +332,7 @@ public class XiSpeakFlow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getListSize(String jsonData){
|
public int getListSize(String jsonData){
|
||||||
if (org.apache.commons.lang3.StringUtils.isEmpty(jsonData)) {
|
return FlowVariableSupport.countTokens(jsonData);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return Math.toIntExact(Arrays.stream(jsonData.split(","))
|
|
||||||
.map(String::trim)
|
|
||||||
.filter(org.apache.commons.lang3.StringUtils::isNotEmpty)
|
|
||||||
.count());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List<String> getImplDeptWorker(String JG_LOCAL_PROCESS_ID,String deptId){
|
// List<String> getImplDeptWorker(String JG_LOCAL_PROCESS_ID,String deptId){
|
||||||
@@ -421,10 +407,7 @@ public class XiSpeakFlow {
|
|||||||
value = execution.getVariable(variableName);
|
value = execution.getVariable(variableName);
|
||||||
}
|
}
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
return Arrays.stream(String.valueOf(value).split(SymbolConstant.COMMA))
|
return FlowVariableSupport.splitToTrimmedList(String.valueOf(value));
|
||||||
.map(String::trim)
|
|
||||||
.filter(org.apache.commons.lang.StringUtils::isNotBlank)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("getDeptWorkerList from process variable failed", e);
|
log.warn("getDeptWorkerList from process variable failed", e);
|
||||||
|
|||||||
+3
-3
@@ -19,6 +19,7 @@ import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals;
|
|||||||
import org.jeecg.modules.tasktask.entity.TaskTask;
|
import org.jeecg.modules.tasktask.entity.TaskTask;
|
||||||
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
||||||
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.common.flow.FlowExecutionSupport;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -75,9 +76,8 @@ public class AfterBgLeaderApproveListener implements TaskListener {
|
|||||||
// 4. 获取第二层父级 ID (大会签容器/外层作用域)
|
// 4. 获取第二层父级 ID (大会签容器/外层作用域)
|
||||||
String grandParentId = parentExecution.getParentId();
|
String grandParentId = parentExecution.getParentId();
|
||||||
|
|
||||||
// 5. 确定最终存储目标
|
// 5. 确定最终存储目标(有第二层父级则存入第二层,否则存入第一层)
|
||||||
// 如果有第二层父级,则存入第二层;如果没有,则退而求其次存入第一层
|
String targetId = FlowExecutionSupport.resolveTargetExecutionId(parentId, grandParentId);
|
||||||
String targetId = StringUtils.isNotBlank(grandParentId) ? grandParentId : parentId;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 6. 执行存储
|
// 6. 执行存储
|
||||||
|
|||||||
+4
-8
@@ -20,6 +20,7 @@ import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals;
|
|||||||
import org.jeecg.modules.tasktask.entity.TaskTask;
|
import org.jeecg.modules.tasktask.entity.TaskTask;
|
||||||
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
||||||
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.common.flow.FlowExecutionSupport;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -35,11 +36,7 @@ public class AfterImplDeptLeaderApproveListener implements TaskListener {
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private static final String VARIABLE_NAME = "tem_impl_leader";
|
private static final String VARIABLE_NAME = "tem_impl_leader";
|
||||||
|
|
||||||
private static RuntimeService runtimeService;
|
private final RuntimeService runtimeService;
|
||||||
|
|
||||||
static {
|
|
||||||
runtimeService = SpringContextUtils.getBean(RuntimeService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void notify(DelegateTask delegateTask) {
|
public void notify(DelegateTask delegateTask) {
|
||||||
@@ -75,9 +72,8 @@ public class AfterImplDeptLeaderApproveListener implements TaskListener {
|
|||||||
// 4. 获取第二层父级 ID (大会签容器/外层作用域)
|
// 4. 获取第二层父级 ID (大会签容器/外层作用域)
|
||||||
String grandParentId = parentExecution.getParentId();
|
String grandParentId = parentExecution.getParentId();
|
||||||
|
|
||||||
// 5. 确定最终存储目标
|
// 5. 确定最终存储目标(有第二层父级则存入第二层,否则存入第一层)
|
||||||
// 如果有第二层父级,则存入第二层;如果没有,则退而求其次存入第一层
|
String targetId = FlowExecutionSupport.resolveTargetExecutionId(parentId, grandParentId);
|
||||||
String targetId = StringUtils.isNotBlank(grandParentId) ? grandParentId : parentId;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 6. 执行存储
|
// 6. 执行存储
|
||||||
|
|||||||
+2
-1
@@ -20,6 +20,7 @@ import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals;
|
|||||||
import org.jeecg.modules.tasktask.entity.TaskTask;
|
import org.jeecg.modules.tasktask.entity.TaskTask;
|
||||||
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
||||||
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.common.flow.FlowVariableSupport;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -74,7 +75,7 @@ public class AfterImplWorkerApproveListener implements TaskListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String cleanDeptId = rawDeptVar.toString().replace("[", "").replace("]", "").trim();
|
String cleanDeptId = FlowVariableSupport.cleanDeptId(rawDeptVar);
|
||||||
|
|
||||||
DeptApproveDetailMap approveMap = xiSpeak.getApproveInfo();
|
DeptApproveDetailMap approveMap = xiSpeak.getApproveInfo();
|
||||||
if (approveMap == null) {
|
if (approveMap == null) {
|
||||||
|
|||||||
+4
-8
@@ -20,6 +20,7 @@ import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals;
|
|||||||
import org.jeecg.modules.tasktask.entity.TaskTask;
|
import org.jeecg.modules.tasktask.entity.TaskTask;
|
||||||
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
||||||
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.common.flow.FlowExecutionSupport;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -35,11 +36,7 @@ public class AfterImplWorkerTemStoreListener implements TaskListener {
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private static final String VARIABLE_NAME = "tem_impl_worker";
|
private static final String VARIABLE_NAME = "tem_impl_worker";
|
||||||
|
|
||||||
private static RuntimeService runtimeService;
|
private final RuntimeService runtimeService;
|
||||||
|
|
||||||
static {
|
|
||||||
runtimeService = SpringContextUtils.getBean(RuntimeService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void notify(DelegateTask delegateTask) {
|
public void notify(DelegateTask delegateTask) {
|
||||||
@@ -75,9 +72,8 @@ public class AfterImplWorkerTemStoreListener implements TaskListener {
|
|||||||
// 4. 获取第二层父级 ID (大会签容器/外层作用域)
|
// 4. 获取第二层父级 ID (大会签容器/外层作用域)
|
||||||
String grandParentId = parentExecution.getParentId();
|
String grandParentId = parentExecution.getParentId();
|
||||||
|
|
||||||
// 5. 确定最终存储目标
|
// 5. 确定最终存储目标(有第二层父级则存入第二层,否则存入第一层)
|
||||||
// 如果有第二层父级,则存入第二层;如果没有,则退而求其次存入第一层
|
String targetId = FlowExecutionSupport.resolveTargetExecutionId(parentId, grandParentId);
|
||||||
String targetId = StringUtils.isNotBlank(grandParentId) ? grandParentId : parentId;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 6. 执行存储
|
// 6. 执行存储
|
||||||
|
|||||||
+2
-4
@@ -9,6 +9,7 @@ import org.flowable.engine.delegate.ExecutionListener;
|
|||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals;
|
import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals;
|
||||||
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.common.flow.FlowVariableSupport;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -65,10 +66,7 @@ public class AfterSDWApproveHqListener implements ExecutionListener {
|
|||||||
|
|
||||||
// 3. 只有当字段存在且内容不为空时,才进行解析
|
// 3. 只有当字段存在且内容不为空时,才进行解析
|
||||||
if (oConvertUtils.isNotEmpty(jsonStr)) {
|
if (oConvertUtils.isNotEmpty(jsonStr)) {
|
||||||
List<String> deptIds = Arrays.stream(jsonStr.split(","))
|
List<String> deptIds = FlowVariableSupport.splitToTrimmedList(jsonStr);
|
||||||
.map(String::trim) // 去掉可能存在的空格
|
|
||||||
.filter(s -> !s.isEmpty()) // 过滤掉空字符串
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (deptIds != null && !deptIds.isEmpty()) {
|
if (deptIds != null && !deptIds.isEmpty()) {
|
||||||
// 4. 将提取出的 List 存入子流程变量(供多实例 Collection 使用)
|
// 4. 将提取出的 List 存入子流程变量(供多实例 Collection 使用)
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package org.jeecg.modules.supervision.bgpartymatter.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.modules.supervision.bgpartymatter.entity.BgPartymatterFeedback;
|
||||||
|
import org.jeecg.modules.supervision.bgpartymatter.mapper.BgPartymatterFeedbackMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BgPartymatterFeedbackServiceImpl 测试(简单转发)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class BgPartymatterFeedbackServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private BgPartymatterFeedbackMapper bgPartymatterFeedbackMapper;
|
||||||
|
|
||||||
|
private BgPartymatterFeedbackServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new BgPartymatterFeedbackServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "bgPartymatterFeedbackMapper", bgPartymatterFeedbackMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_select_by_main_id() {
|
||||||
|
BgPartymatterFeedback feedback = new BgPartymatterFeedback();
|
||||||
|
when(bgPartymatterFeedbackMapper.selectByMainId("m-1")).thenReturn(List.of(feedback));
|
||||||
|
|
||||||
|
List<BgPartymatterFeedback> result = service.selectByMainId("m-1");
|
||||||
|
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
verify(bgPartymatterFeedbackMapper).selectByMainId("m-1");
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package org.jeecg.modules.supervision.bgpartymatter.service.impl;
|
||||||
|
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.jeecg.modules.supervision.bgpartymatter.entity.BgPartymatter;
|
||||||
|
import org.jeecg.modules.supervision.bgpartymatter.mapper.BgPartymatterFeedbackMapper;
|
||||||
|
import org.jeecg.modules.supervision.bgpartymatter.mapper.BgPartymatterMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BgPartymatterServiceImpl 测试(主从删除 + 流程变量同步)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class BgPartymatterServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private BgPartymatterMapper bgPartymatterMapper;
|
||||||
|
@Mock private BgPartymatterFeedbackMapper bgPartymatterFeedbackMapper;
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
|
||||||
|
private BgPartymatterServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new BgPartymatterServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "bgPartymatterMapper", bgPartymatterMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "baseMapper", bgPartymatterMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "bgPartymatterFeedbackMapper", bgPartymatterFeedbackMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "runtimeService", runtimeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_delete_main_with_feedback() {
|
||||||
|
service.delMain("m-1");
|
||||||
|
verify(bgPartymatterFeedbackMapper).deleteByMainId("m-1");
|
||||||
|
verify(bgPartymatterMapper).deleteById("m-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_delete_batch_with_feedback() {
|
||||||
|
service.delBatchMain(List.of("m-1", "m-2"));
|
||||||
|
verify(bgPartymatterFeedbackMapper).deleteByMainId("m-1");
|
||||||
|
verify(bgPartymatterMapper).deleteById("m-1");
|
||||||
|
verify(bgPartymatterFeedbackMapper).deleteByMainId("m-2");
|
||||||
|
verify(bgPartymatterMapper).deleteById("m-2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_update_form_and_sync_variable() {
|
||||||
|
BgPartymatter form = new BgPartymatter();
|
||||||
|
service.saveBpmFormAndSyncVariable(form, "proc-1", "json_data", "{\"a\":1}");
|
||||||
|
verify(bgPartymatterMapper).updateById(form);
|
||||||
|
verify(runtimeService).setVariable("proc-1", "json_data", "{\"a\":1}");
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package org.jeecg.modules.supervision.bqtakepulse.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.modules.supervision.bqtakepulse.entity.BgTakepulseFeedback;
|
||||||
|
import org.jeecg.modules.supervision.bqtakepulse.mapper.BgTakepulseFeedbackMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BgTakepulseFeedbackServiceImpl 测试(简单转发)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class BgTakepulseFeedbackServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private BgTakepulseFeedbackMapper bgTakepulseFeedbackMapper;
|
||||||
|
|
||||||
|
private BgTakepulseFeedbackServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new BgTakepulseFeedbackServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "bgTakepulseFeedbackMapper", bgTakepulseFeedbackMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_select_by_main_id() {
|
||||||
|
BgTakepulseFeedback feedback = new BgTakepulseFeedback();
|
||||||
|
when(bgTakepulseFeedbackMapper.selectByMainId("m-1")).thenReturn(List.of(feedback));
|
||||||
|
assertEquals(1, service.selectByMainId("m-1").size());
|
||||||
|
verify(bgTakepulseFeedbackMapper).selectByMainId("m-1");
|
||||||
|
}
|
||||||
|
}
|
||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
package org.jeecg.modules.supervision.bqtakepulse.service.impl;
|
||||||
|
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.jeecg.modules.supervision.bqtakepulse.entity.BgTakepulse;
|
||||||
|
import org.jeecg.modules.supervision.bqtakepulse.entity.BgTakepulseFeedback;
|
||||||
|
import org.jeecg.modules.supervision.bqtakepulse.mapper.BgTakepulseFeedbackMapper;
|
||||||
|
import org.jeecg.modules.supervision.bqtakepulse.mapper.BgTakepulseMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BgTakepulseServiceImpl 测试。
|
||||||
|
* 核心:saveMain 默认 del_flag 补齐(防 @TableLogic 过滤)+ 主从 CRUD。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class BgTakepulseServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private BgTakepulseMapper bgTakepulseMapper;
|
||||||
|
@Mock private BgTakepulseFeedbackMapper bgTakepulseFeedbackMapper;
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
|
||||||
|
private BgTakepulseServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new BgTakepulseServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "bgTakepulseMapper", bgTakepulseMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "baseMapper", bgTakepulseMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "bgTakepulseFeedbackMapper", bgTakepulseFeedbackMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "runtimeService", runtimeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_fill_default_del_flag_on_save() {
|
||||||
|
BgTakepulse main = new BgTakepulse();
|
||||||
|
main.setId("biz-1");
|
||||||
|
BgTakepulseFeedback sub = new BgTakepulseFeedback();
|
||||||
|
|
||||||
|
service.saveMain(main, List.of(sub));
|
||||||
|
|
||||||
|
assertEquals("0", main.getDelFlag());
|
||||||
|
assertEquals("0", sub.getDelFlag());
|
||||||
|
assertEquals("biz-1", sub.getMainId());
|
||||||
|
verify(bgTakepulseMapper).insert(main);
|
||||||
|
verify(bgTakepulseFeedbackMapper).insert(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_keep_existing_del_flag() {
|
||||||
|
BgTakepulse main = new BgTakepulse();
|
||||||
|
main.setDelFlag("1");
|
||||||
|
service.saveMain(main, null);
|
||||||
|
assertEquals("1", main.getDelFlag());
|
||||||
|
verify(bgTakepulseMapper).insert(main);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_update_main_and_replace_sub() {
|
||||||
|
BgTakepulse main = new BgTakepulse();
|
||||||
|
main.setId("biz-1");
|
||||||
|
BgTakepulseFeedback sub = new BgTakepulseFeedback();
|
||||||
|
|
||||||
|
service.updateMain(main, List.of(sub));
|
||||||
|
|
||||||
|
verify(bgTakepulseMapper).updateById(main);
|
||||||
|
verify(bgTakepulseFeedbackMapper).deleteByMainId("biz-1");
|
||||||
|
assertEquals("biz-1", sub.getMainId());
|
||||||
|
verify(bgTakepulseFeedbackMapper).insert(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_delete_main_with_sub() {
|
||||||
|
service.delMain("biz-1");
|
||||||
|
verify(bgTakepulseFeedbackMapper).deleteByMainId("biz-1");
|
||||||
|
verify(bgTakepulseMapper).deleteById("biz-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_delete_batch() {
|
||||||
|
service.delBatchMain(List.of("biz-1", "biz-2"));
|
||||||
|
verify(bgTakepulseFeedbackMapper).deleteByMainId("biz-2");
|
||||||
|
verify(bgTakepulseMapper).deleteById("biz-2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_update_form_and_sync_variable() {
|
||||||
|
BgTakepulse form = new BgTakepulse();
|
||||||
|
service.saveBpmFormAndSyncVariable(form, "proc-1", "json_data", "{\"a\":1}");
|
||||||
|
verify(bgTakepulseMapper).updateById(form);
|
||||||
|
verify(runtimeService).setVariable("proc-1", "json_data", "{\"a\":1}");
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package org.jeecg.modules.supervision.common;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BpmStatus 枚举解析测试。
|
||||||
|
*/
|
||||||
|
class BpmStatusTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_parse_known_codes() {
|
||||||
|
assertEquals(BpmStatus.NOT_START, BpmStatus.fromCode("1"));
|
||||||
|
assertEquals(BpmStatus.SUPERVISING, BpmStatus.fromCode("2"));
|
||||||
|
assertEquals(BpmStatus.FINISH, BpmStatus.fromCode("3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {"0", "4", "abc", ""})
|
||||||
|
void should_return_null_for_unknown_code(String code) {
|
||||||
|
assertNull(BpmStatus.fromCode(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_null_for_null_code() {
|
||||||
|
assertNull(BpmStatus.fromCode(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-9
@@ -1,4 +1,4 @@
|
|||||||
package org.jeecg.modules.supervision.fixcontact.listener;
|
package org.jeecg.modules.supervision.common.flow;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
@@ -9,29 +9,29 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FixContactApprovalHelper.resolveTargetExecutionId 规则测试(TDD 红-绿-重构 演示)。
|
* FlowExecutionSupport.resolveTargetExecutionId 规则测试(TDD 红-绿-重构 演示)。
|
||||||
*
|
*
|
||||||
* 业务规则(来自 FixContactDeptLeaderApproveListener 第 53-69 行):
|
* 业务规则(来自 fixcontact/xispeak 监听器内联重复逻辑):
|
||||||
* 部门领导审批后,领导变量要写入"上两级"执行实例:
|
* 部门领导/经办人审批后,审批人变量要写入"上两级"执行实例:
|
||||||
* - 存在第二层父级(大会签容器/外层作用域)→ 写入第二层 grandParentId
|
* - 存在第二层父级(大会签容器/外层作用域)→ 写入第二层 grandParentId
|
||||||
* - 不存在第二层父级 → 写入第一层 parentId
|
* - 不存在第二层父级 → 写入第一层 parentId
|
||||||
* - 第一层 parentId 为空 → 无目标,返回 null(调用方跳过写入)
|
* - 第一层 parentId 为空 → 无目标,返回 null(调用方跳过写入)
|
||||||
*/
|
*/
|
||||||
class FixContactApprovalHelperTest {
|
class FlowExecutionSupportTest {
|
||||||
|
|
||||||
// ============ 正常路径 ============
|
// ============ 正常路径 ============
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_return_grandParent_when_both_levels_exist() {
|
void should_return_grandParent_when_both_levels_exist() {
|
||||||
// 两级父级都存在(子流程内会签容器场景)
|
// 两级父级都存在(子流程内会签容器场景)
|
||||||
String target = FixContactApprovalHelper.resolveTargetExecutionId("parent-1", "grand-1");
|
String target = FlowExecutionSupport.resolveTargetExecutionId("parent-1", "grand-1");
|
||||||
assertEquals("grand-1", target);
|
assertEquals("grand-1", target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_return_parent_when_no_grand_parent() {
|
void should_return_parent_when_no_grand_parent() {
|
||||||
// 只有第一层父级(无外层容器)
|
// 只有第一层父级(无外层容器)
|
||||||
String target = FixContactApprovalHelper.resolveTargetExecutionId("parent-1", null);
|
String target = FlowExecutionSupport.resolveTargetExecutionId("parent-1", null);
|
||||||
assertEquals("parent-1", target);
|
assertEquals("parent-1", target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class FixContactApprovalHelperTest {
|
|||||||
@ValueSource(strings = {" ", " "})
|
@ValueSource(strings = {" ", " "})
|
||||||
void should_return_parent_when_grand_parent_blank(String grandParentId) {
|
void should_return_parent_when_grand_parent_blank(String grandParentId) {
|
||||||
// 第二层为空串/空白串:视为不存在,退回第一层(原逻辑 isNotBlank 判定)
|
// 第二层为空串/空白串:视为不存在,退回第一层(原逻辑 isNotBlank 判定)
|
||||||
String target = FixContactApprovalHelper.resolveTargetExecutionId("parent-1", grandParentId);
|
String target = FlowExecutionSupport.resolveTargetExecutionId("parent-1", grandParentId);
|
||||||
assertEquals("parent-1", target);
|
assertEquals("parent-1", target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +53,6 @@ class FixContactApprovalHelperTest {
|
|||||||
@ValueSource(strings = {" ", " "})
|
@ValueSource(strings = {" ", " "})
|
||||||
void should_return_null_when_parent_blank(String parentId) {
|
void should_return_null_when_parent_blank(String parentId) {
|
||||||
// 第一层父级都没有:无目标执行实例,返回 null 供调用方跳过
|
// 第一层父级都没有:无目标执行实例,返回 null 供调用方跳过
|
||||||
assertNull(FixContactApprovalHelper.resolveTargetExecutionId(parentId, "grand-1"));
|
assertNull(FlowExecutionSupport.resolveTargetExecutionId(parentId, "grand-1"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+18
-18
@@ -1,4 +1,4 @@
|
|||||||
package org.jeecg.modules.supervision.fixcontact.flow;
|
package org.jeecg.modules.supervision.common.flow;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
@@ -11,31 +11,31 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FixContactFlowSupport 纯函数规则测试(TDD 红-绿-重构,阶段 A 示范)。
|
* FlowVariableSupport 纯函数规则测试(TDD 红-绿-重构,阶段 A 示范)。
|
||||||
*
|
*
|
||||||
* 业务规则(来自 FixContactFlow 内联逻辑,3 处重复的逗号分割 + 部门 ID 清洗):
|
* 业务规则(来自 fixcontact/xispeak 多处重复的逗号分割 + 部门 ID 清洗):
|
||||||
* - splitToTrimmedList:逗号分割 → 去首尾空白 → 过滤空白元素(null/空 → 空列表)
|
* - splitToTrimmedList:逗号分割 → 去首尾空白 → 过滤空白元素(null/空 → 空列表)
|
||||||
* - countTokens:分割后元素个数(等价于 splitToTrimmedList().size())
|
* - countTokens:分割后元素个数(等价于 splitToTrimmedList().size())
|
||||||
* - cleanDeptId:执行实例部门变量清洗(去掉 [] 与首尾空白,与 XiSpeak 监听器同款规则)
|
* - cleanDeptId:执行实例部门变量清洗(去掉 [] 与首尾空白)
|
||||||
*/
|
*/
|
||||||
class FixContactFlowSupportTest {
|
class FlowVariableSupportTest {
|
||||||
|
|
||||||
// ============ splitToTrimmedList:正常路径 ============
|
// ============ splitToTrimmedList:正常路径 ============
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_split_multi_tokens() {
|
void should_split_multi_tokens() {
|
||||||
assertEquals(List.of("a", "b", "c"), FixContactFlowSupport.splitToTrimmedList("a,b,c"));
|
assertEquals(List.of("a", "b", "c"), FlowVariableSupport.splitToTrimmedList("a,b,c"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_trim_tokens_with_spaces() {
|
void should_trim_tokens_with_spaces() {
|
||||||
// 逗号前后有空格:逐 token trim
|
// 逗号前后有空格:逐 token trim
|
||||||
assertEquals(List.of("a", "b"), FixContactFlowSupport.splitToTrimmedList(" a , b "));
|
assertEquals(List.of("a", "b"), FlowVariableSupport.splitToTrimmedList(" a , b "));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_return_single_token() {
|
void should_return_single_token() {
|
||||||
assertEquals(List.of("a"), FixContactFlowSupport.splitToTrimmedList("a"));
|
assertEquals(List.of("a"), FlowVariableSupport.splitToTrimmedList("a"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ splitToTrimmedList:边界路径 ============
|
// ============ splitToTrimmedList:边界路径 ============
|
||||||
@@ -43,61 +43,61 @@ class FixContactFlowSupportTest {
|
|||||||
@Test
|
@Test
|
||||||
void should_filter_empty_tokens() {
|
void should_filter_empty_tokens() {
|
||||||
// 空元素(连续逗号/全空白元素)被过滤
|
// 空元素(连续逗号/全空白元素)被过滤
|
||||||
assertEquals(List.of("a", "b"), FixContactFlowSupport.splitToTrimmedList("a,,b"));
|
assertEquals(List.of("a", "b"), FlowVariableSupport.splitToTrimmedList("a,,b"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@NullAndEmptySource
|
@NullAndEmptySource
|
||||||
@ValueSource(strings = {" ", " ", " , , "})
|
@ValueSource(strings = {" ", " ", " , , "})
|
||||||
void should_return_empty_list_when_no_valid_token(String raw) {
|
void should_return_empty_list_when_no_valid_token(String raw) {
|
||||||
assertEquals(List.of(), FixContactFlowSupport.splitToTrimmedList(raw));
|
assertEquals(List.of(), FlowVariableSupport.splitToTrimmedList(raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ countTokens:正常 + 边界 ============
|
// ============ countTokens:正常 + 边界 ============
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_count_tokens() {
|
void should_count_tokens() {
|
||||||
assertEquals(3, FixContactFlowSupport.countTokens("a,b,c"));
|
assertEquals(3, FlowVariableSupport.countTokens("a,b,c"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_count_trimmed_tokens() {
|
void should_count_trimmed_tokens() {
|
||||||
assertEquals(2, FixContactFlowSupport.countTokens(" a , b "));
|
assertEquals(2, FlowVariableSupport.countTokens(" a , b "));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@NullAndEmptySource
|
@NullAndEmptySource
|
||||||
@ValueSource(strings = {" ", " , , "})
|
@ValueSource(strings = {" ", " , , "})
|
||||||
void should_return_zero_when_no_valid_token(String raw) {
|
void should_return_zero_when_no_valid_token(String raw) {
|
||||||
assertEquals(0, FixContactFlowSupport.countTokens(raw));
|
assertEquals(0, FlowVariableSupport.countTokens(raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ cleanDeptId:正常 + 边界 ============
|
// ============ cleanDeptId:正常 + 边界 ============
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_remove_square_brackets() {
|
void should_remove_square_brackets() {
|
||||||
assertEquals("dept-1", FixContactFlowSupport.cleanDeptId("[dept-1]"));
|
assertEquals("dept-1", FlowVariableSupport.cleanDeptId("[dept-1]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_remove_brackets_and_trim() {
|
void should_remove_brackets_and_trim() {
|
||||||
// 多值列表变量场景:整体去括号 + trim
|
// 多值列表变量场景:整体去括号 + trim
|
||||||
assertEquals("dept-1, dept-2", FixContactFlowSupport.cleanDeptId("[dept-1, dept-2]"));
|
assertEquals("dept-1, dept-2", FlowVariableSupport.cleanDeptId("[dept-1, dept-2]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_keep_plain_id_unchanged() {
|
void should_keep_plain_id_unchanged() {
|
||||||
assertEquals("dept-1", FixContactFlowSupport.cleanDeptId("dept-1"));
|
assertEquals("dept-1", FlowVariableSupport.cleanDeptId("dept-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_handle_non_string_object() {
|
void should_handle_non_string_object() {
|
||||||
// 流程变量可能是任意对象,toString 后处理
|
// 流程变量可能是任意对象,toString 后处理
|
||||||
assertEquals("123", FixContactFlowSupport.cleanDeptId(123L));
|
assertEquals("123", FlowVariableSupport.cleanDeptId(123L));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_return_null_when_raw_null() {
|
void should_return_null_when_raw_null() {
|
||||||
assertNull(FixContactFlowSupport.cleanDeptId(null));
|
assertNull(FlowVariableSupport.cleanDeptId(null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
package org.jeecg.modules.supervision.dashboard.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.modules.supervision.dashboard.dto.ProcessPortalStatsDTO;
|
||||||
|
import org.jeecg.modules.supervision.dashboard.mapper.ProcessPortalMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProcessPortalServiceImpl 测试。
|
||||||
|
* 核心:formatDuration 时长格式化规则(0/分钟/小时/天),经 getStats 间接覆盖。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ProcessPortalServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private ProcessPortalMapper processPortalMapper;
|
||||||
|
|
||||||
|
private ProcessPortalServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new ProcessPortalServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "processPortalMapper", processPortalMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProcessPortalStatsDTO statsWithAvg(Long avgMillis) {
|
||||||
|
when(processPortalMapper.countTodo(eq("u1"))).thenReturn(3L);
|
||||||
|
when(processPortalMapper.countDoneThisMonth(eq("u1"), org.mockito.ArgumentMatchers.anyString())).thenReturn(10L);
|
||||||
|
when(processPortalMapper.avgDurationMillis(eq("u1"))).thenReturn(avgMillis);
|
||||||
|
when(processPortalMapper.countOverdue(eq("u1"))).thenReturn(1L);
|
||||||
|
return service.getStats("u1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_zero_hours_when_null() {
|
||||||
|
assertEquals("0小时", statsWithAvg(null).getAvgDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_format_minutes_when_less_than_hour() {
|
||||||
|
// 30 分钟
|
||||||
|
assertEquals("30分钟", statsWithAvg(30L * 60 * 1000).getAvgDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_format_hours_when_less_than_day() {
|
||||||
|
// 2.5 小时
|
||||||
|
assertEquals("2.5小时", statsWithAvg((long) (2.5 * 3600 * 1000)).getAvgDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_format_days_when_ge_day() {
|
||||||
|
// 48 小时 → 2 天
|
||||||
|
assertEquals("2.0天", statsWithAvg(48L * 3600 * 1000).getAvgDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_fill_all_stats() {
|
||||||
|
ProcessPortalStatsDTO dto = statsWithAvg(null);
|
||||||
|
assertEquals(3, dto.getTodoCount());
|
||||||
|
assertEquals(10, dto.getDoneThisMonth());
|
||||||
|
assertEquals(1, dto.getOverdueCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
+210
@@ -0,0 +1,210 @@
|
|||||||
|
package org.jeecg.modules.supervision.dqinspectproblem.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
|
import org.jeecg.modules.supervision.dqinspectproblem.entity.DqInspectProblem;
|
||||||
|
import org.jeecg.modules.supervision.dqinspectproblem.mapper.DqInspectProblemMapper;
|
||||||
|
import org.jeecg.modules.supervision.dqinspectproblem.service.IDqInspectProblemService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DqInspectProblemServiceImpl 测试。
|
||||||
|
* 核心:validateAndNormalizeNode 三层问题清单校验(分类→面上→具体),经 add/update 触发。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class DqInspectProblemServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private DqInspectProblemMapper baseMapper;
|
||||||
|
|
||||||
|
private DqInspectProblemServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new DqInspectProblemServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "baseMapper", baseMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DqInspectProblem problem(String nodeType, String pid) {
|
||||||
|
DqInspectProblem p = new DqInspectProblem();
|
||||||
|
p.setNodeType(nodeType);
|
||||||
|
p.setPid(pid);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DqInspectProblem category(String id) {
|
||||||
|
DqInspectProblem c = new DqInspectProblem();
|
||||||
|
c.setId(id);
|
||||||
|
c.setNodeType(IDqInspectProblemService.NODE_TYPE_CATEGORY);
|
||||||
|
c.setPid(IDqInspectProblemService.ROOT_PID_VALUE);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DqInspectProblem surface(String id) {
|
||||||
|
DqInspectProblem s = new DqInspectProblem();
|
||||||
|
s.setId(id);
|
||||||
|
s.setNodeType(IDqInspectProblemService.NODE_TYPE_SURFACE);
|
||||||
|
s.setPid("c-1");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== validateAndNormalizeNode:基础校验 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class BasicValidation {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_form_null() {
|
||||||
|
assertThrows(JeecgBootException.class, () -> service.addDqInspectProblem(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_node_type_invalid() {
|
||||||
|
assertThrows(JeecgBootException.class,
|
||||||
|
() -> service.addDqInspectProblem(problem("invalid", "c-1")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 分类节点 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CategoryNode {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_force_root_pid_and_clear_specific_fields() {
|
||||||
|
DqInspectProblem p = problem(IDqInspectProblemService.NODE_TYPE_CATEGORY, "c-2");
|
||||||
|
p.setInspectAdvice("advice");
|
||||||
|
p.setQuestionResLeader("leader");
|
||||||
|
p.setQuestionResDept("dept");
|
||||||
|
|
||||||
|
service.addDqInspectProblem(p);
|
||||||
|
|
||||||
|
assertEquals(IDqInspectProblemService.ROOT_PID_VALUE, p.getPid());
|
||||||
|
assertNull(p.getInspectAdvice());
|
||||||
|
assertNull(p.getQuestionResLeader());
|
||||||
|
assertNull(p.getQuestionResDept());
|
||||||
|
verify(baseMapper).insert(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 面上/具体节点:父级校验 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ParentValidation {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_pid_empty() {
|
||||||
|
assertThrows(JeecgBootException.class,
|
||||||
|
() -> service.addDqInspectProblem(problem(IDqInspectProblemService.NODE_TYPE_SURFACE, "")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_pid_is_root() {
|
||||||
|
assertThrows(JeecgBootException.class,
|
||||||
|
() -> service.addDqInspectProblem(problem(IDqInspectProblemService.NODE_TYPE_SURFACE,
|
||||||
|
IDqInspectProblemService.ROOT_PID_VALUE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_parent_not_exists() {
|
||||||
|
when(baseMapper.selectById("c-9")).thenReturn(null);
|
||||||
|
assertThrows(JeecgBootException.class,
|
||||||
|
() -> service.addDqInspectProblem(problem(IDqInspectProblemService.NODE_TYPE_SURFACE, "c-9")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_parent_type_mismatch() {
|
||||||
|
// 面上问题的父必须是分类;父是面上 → 抛
|
||||||
|
DqInspectProblem wrongParent = surface("s-1");
|
||||||
|
when(baseMapper.selectById("s-1")).thenReturn(wrongParent);
|
||||||
|
assertThrows(JeecgBootException.class,
|
||||||
|
() -> service.addDqInspectProblem(problem(IDqInspectProblemService.NODE_TYPE_SURFACE, "s-1")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_insert_surface_under_category() {
|
||||||
|
when(baseMapper.selectById("c-1")).thenReturn(category("c-1"));
|
||||||
|
service.addDqInspectProblem(problem(IDqInspectProblemService.NODE_TYPE_SURFACE, "c-1"));
|
||||||
|
verify(baseMapper).insert(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_insert_specific_under_surface() {
|
||||||
|
when(baseMapper.selectById("s-1")).thenReturn(surface("s-1"));
|
||||||
|
service.addDqInspectProblem(problem(IDqInspectProblemService.NODE_TYPE_SPECIFIC, "s-1"));
|
||||||
|
verify(baseMapper).insert(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== updateDqInspectProblem:自身/下级校验 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class UpdateValidation {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_entity_not_found() {
|
||||||
|
DqInspectProblem p = problem(IDqInspectProblemService.NODE_TYPE_SPECIFIC, "s-1");
|
||||||
|
p.setId("x-1");
|
||||||
|
when(baseMapper.selectById("x-1")).thenReturn(null);
|
||||||
|
assertThrows(JeecgBootException.class, () -> service.updateDqInspectProblem(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_pid_is_self() {
|
||||||
|
DqInspectProblem existing = problem(IDqInspectProblemService.NODE_TYPE_SPECIFIC, "s-1");
|
||||||
|
existing.setId("x-1");
|
||||||
|
when(baseMapper.selectById("x-1")).thenReturn(existing);
|
||||||
|
|
||||||
|
DqInspectProblem p = problem(IDqInspectProblemService.NODE_TYPE_SPECIFIC, "x-1");
|
||||||
|
p.setId("x-1");
|
||||||
|
assertThrows(JeecgBootException.class, () -> service.updateDqInspectProblem(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_pid_is_descendant() {
|
||||||
|
// x-1 的下级是 d-2:把 x-1 的父设为 d-2 非法
|
||||||
|
DqInspectProblem existing = problem(IDqInspectProblemService.NODE_TYPE_SPECIFIC, "s-1");
|
||||||
|
existing.setId("x-1");
|
||||||
|
when(baseMapper.selectById("x-1")).thenReturn(existing);
|
||||||
|
DqInspectProblem child = new DqInspectProblem();
|
||||||
|
child.setId("d-2");
|
||||||
|
child.setPid("x-1");
|
||||||
|
when(baseMapper.selectById("d-2")).thenReturn(child);
|
||||||
|
when(baseMapper.selectById("x-1")).thenReturn(existing); // isDescendantOf 起点
|
||||||
|
|
||||||
|
DqInspectProblem p = problem(IDqInspectProblemService.NODE_TYPE_SPECIFIC, "d-2");
|
||||||
|
p.setId("x-1");
|
||||||
|
assertThrows(JeecgBootException.class, () -> service.updateDqInspectProblem(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_update_normally_when_pid_unchanged() {
|
||||||
|
DqInspectProblem existing = problem(IDqInspectProblemService.NODE_TYPE_SPECIFIC, "s-1");
|
||||||
|
existing.setId("x-1");
|
||||||
|
existing.setHasChild(IDqInspectProblemService.NOCHILD);
|
||||||
|
when(baseMapper.selectById("x-1")).thenReturn(existing);
|
||||||
|
when(baseMapper.selectById("s-1")).thenReturn(surface("s-1"));
|
||||||
|
|
||||||
|
DqInspectProblem p = problem(IDqInspectProblemService.NODE_TYPE_SPECIFIC, "s-1");
|
||||||
|
p.setId("x-1");
|
||||||
|
service.updateDqInspectProblem(p);
|
||||||
|
|
||||||
|
// hasChild 由树结构维护,忽略前端提交值
|
||||||
|
assertEquals(IDqInspectProblemService.NOCHILD, p.getHasChild());
|
||||||
|
verify(baseMapper, never()).updateTreeNodeStatus(any(), any());
|
||||||
|
verify(baseMapper).updateById(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
package org.jeecg.modules.supervision.dqinspecttask.listener;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.config.InspectImproveConfig;
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.config.InspectImproveConfig.InspectImproveProperties;
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.entity.DqInspectTask;
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.service.IDqInspectTaskService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DqInspectTaskStageIncrementListener 阶段递增监听器测试。
|
||||||
|
* 规则:流程结束时 completedStage 原子递增 1;businessKey 缺失跳过;幂等标记 stage_increment_done 防重复递增。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class DqInspectTaskStageIncrementListenerTest {
|
||||||
|
|
||||||
|
private static final String INCREMENT_FLAG = "stage_increment_done";
|
||||||
|
private static final String BUSINESS_KEY_VAR = "businessKey";
|
||||||
|
|
||||||
|
@Mock private InspectImproveConfig inspectImproveConfig;
|
||||||
|
@Mock private IDqInspectTaskService dqInspectTaskService;
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
@Mock private DelegateExecution execution;
|
||||||
|
@Mock private LambdaUpdateChainWrapper<DqInspectTask> chainWrapper;
|
||||||
|
|
||||||
|
private DqInspectTaskStageIncrementListener listener;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
InspectImproveProperties props = new InspectImproveProperties();
|
||||||
|
props.setBusinessKey(BUSINESS_KEY_VAR);
|
||||||
|
when(inspectImproveConfig.getInspectImprove()).thenReturn(props);
|
||||||
|
listener = new DqInspectTaskStageIncrementListener(inspectImproveConfig, dqInspectTaskService, runtimeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_business_key_missing() {
|
||||||
|
when(execution.getVariable(BUSINESS_KEY_VAR)).thenReturn(null);
|
||||||
|
|
||||||
|
listener.notify(execution);
|
||||||
|
|
||||||
|
verify(runtimeService, never()).setVariable(any(), any(), any());
|
||||||
|
verify(dqInspectTaskService, never()).lambdaUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_already_incremented() {
|
||||||
|
// 幂等:同一流程实例结束仅递增一次
|
||||||
|
when(execution.getVariable(BUSINESS_KEY_VAR)).thenReturn("biz-1");
|
||||||
|
when(execution.getProcessInstanceId()).thenReturn("proc-1");
|
||||||
|
when(runtimeService.getVariable("proc-1", INCREMENT_FLAG)).thenReturn(Boolean.TRUE);
|
||||||
|
|
||||||
|
listener.notify(execution);
|
||||||
|
|
||||||
|
verify(dqInspectTaskService, never()).lambdaUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_increment_stage_and_set_flag() {
|
||||||
|
when(execution.getVariable(BUSINESS_KEY_VAR)).thenReturn("biz-1");
|
||||||
|
when(execution.getProcessInstanceId()).thenReturn("proc-1");
|
||||||
|
when(runtimeService.getVariable("proc-1", INCREMENT_FLAG)).thenReturn(null);
|
||||||
|
when(dqInspectTaskService.lambdaUpdate()).thenReturn(chainWrapper);
|
||||||
|
when(chainWrapper.setSql("completed_stage = completed_stage + 1")).thenReturn(chainWrapper);
|
||||||
|
// 方法引用 DqInspectTask::getId 在测试类与生产类编译为不同 Lambda 实例,用 any() 匹配函数式参数
|
||||||
|
when(chainWrapper.eq(any(), eq("biz-1"))).thenReturn(chainWrapper);
|
||||||
|
when(chainWrapper.update()).thenReturn(true);
|
||||||
|
|
||||||
|
listener.notify(execution);
|
||||||
|
|
||||||
|
verify(runtimeService).setVariable("proc-1", INCREMENT_FLAG, Boolean.TRUE);
|
||||||
|
verify(chainWrapper).setSql("completed_stage = completed_stage + 1");
|
||||||
|
verify(chainWrapper).eq(any(), eq("biz-1"));
|
||||||
|
verify(chainWrapper).update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_throw_when_update_affects_zero_rows() {
|
||||||
|
// 记录可能已删除:update 返回 false 仅 warn,不抛异常
|
||||||
|
when(execution.getVariable(BUSINESS_KEY_VAR)).thenReturn("biz-1");
|
||||||
|
when(execution.getProcessInstanceId()).thenReturn("proc-1");
|
||||||
|
when(runtimeService.getVariable("proc-1", INCREMENT_FLAG)).thenReturn(null);
|
||||||
|
when(dqInspectTaskService.lambdaUpdate()).thenReturn(chainWrapper);
|
||||||
|
when(chainWrapper.setSql("completed_stage = completed_stage + 1")).thenReturn(chainWrapper);
|
||||||
|
when(chainWrapper.eq(any(), eq("biz-1"))).thenReturn(chainWrapper);
|
||||||
|
when(chainWrapper.update()).thenReturn(false);
|
||||||
|
|
||||||
|
// 不抛异常即通过
|
||||||
|
listener.notify(execution);
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package org.jeecg.modules.supervision.dqinspecttask.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.entity.DqInspectProgress;
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.mapper.DqInspectProgressMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DqInspectProgressServiceImpl 测试(简单转发)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class DqInspectProgressServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private DqInspectProgressMapper dqInspectProgressMapper;
|
||||||
|
|
||||||
|
private DqInspectProgressServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new DqInspectProgressServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "dqInspectProgressMapper", dqInspectProgressMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_select_by_main_id() {
|
||||||
|
DqInspectProgress progress = new DqInspectProgress();
|
||||||
|
when(dqInspectProgressMapper.selectByMainId("main-1")).thenReturn(List.of(progress));
|
||||||
|
|
||||||
|
List<DqInspectProgress> result = service.selectByMainId("main-1");
|
||||||
|
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
verify(dqInspectProgressMapper).selectByMainId("main-1");
|
||||||
|
}
|
||||||
|
}
|
||||||
+185
@@ -0,0 +1,185 @@
|
|||||||
|
package org.jeecg.modules.supervision.dqinspecttask.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
|
import org.jeecg.modules.supervision.dqinspectproblem.entity.DqInspectProblem;
|
||||||
|
import org.jeecg.modules.supervision.dqinspectproblem.service.IDqInspectProblemService;
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.entity.DqInspectProgress;
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.entity.DqInspectTask;
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.mapper.DqInspectProgressMapper;
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.mapper.DqInspectTaskMapper;
|
||||||
|
import org.jeecg.modules.supervision.dqinspecttask.vo.DqInspectTaskLedgerVo;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DqInspectTaskServiceImpl 测试。
|
||||||
|
* 核心:validateProblemId 业务规则(整改任务必须挂具体问题节点下)+ saveMain 默认值 + 主从 CRUD。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class DqInspectTaskServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private DqInspectTaskMapper dqInspectTaskMapper;
|
||||||
|
@Mock private DqInspectProgressMapper dqInspectProgressMapper;
|
||||||
|
@Mock private IDqInspectProblemService dqInspectProblemService;
|
||||||
|
|
||||||
|
private DqInspectTaskServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new DqInspectTaskServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "dqInspectTaskMapper", dqInspectTaskMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "dqInspectProgressMapper", dqInspectProgressMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "dqInspectProblemService", dqInspectProblemService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DqInspectProblem specificProblem() {
|
||||||
|
DqInspectProblem p = new DqInspectProblem();
|
||||||
|
p.setId("prob-1");
|
||||||
|
p.setNodeType(IDqInspectProblemService.NODE_TYPE_SPECIFIC);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== validateProblemId 业务规则 ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_problem_id_blank() {
|
||||||
|
DqInspectTask task = new DqInspectTask();
|
||||||
|
assertThrows(JeecgBootException.class, () -> service.saveMain(task, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_problem_not_exists() {
|
||||||
|
DqInspectTask task = new DqInspectTask();
|
||||||
|
task.setProblemId("no-such");
|
||||||
|
when(dqInspectProblemService.getById("no-such")).thenReturn(null);
|
||||||
|
assertThrows(JeecgBootException.class, () -> service.saveMain(task, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_problem_not_specific_node() {
|
||||||
|
DqInspectTask task = new DqInspectTask();
|
||||||
|
task.setProblemId("prob-1");
|
||||||
|
DqInspectProblem category = new DqInspectProblem();
|
||||||
|
category.setNodeType("category");
|
||||||
|
when(dqInspectProblemService.getById("prob-1")).thenReturn(category);
|
||||||
|
assertThrows(JeecgBootException.class, () -> service.saveMain(task, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== saveMain ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_set_default_bpm_status_and_stage() {
|
||||||
|
DqInspectTask task = new DqInspectTask();
|
||||||
|
task.setProblemId("prob-1");
|
||||||
|
when(dqInspectProblemService.getById("prob-1")).thenReturn(specificProblem());
|
||||||
|
|
||||||
|
service.saveMain(task, null);
|
||||||
|
|
||||||
|
assertEquals("1", task.getBpmStatus());
|
||||||
|
assertEquals(0, task.getCompletedStage());
|
||||||
|
verify(dqInspectTaskMapper).insert(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_save_sub_progress_with_main_id() {
|
||||||
|
DqInspectTask task = new DqInspectTask();
|
||||||
|
task.setId("task-1");
|
||||||
|
task.setProblemId("prob-1");
|
||||||
|
when(dqInspectProblemService.getById("prob-1")).thenReturn(specificProblem());
|
||||||
|
DqInspectProgress progress = new DqInspectProgress();
|
||||||
|
|
||||||
|
service.saveMain(task, List.of(progress));
|
||||||
|
|
||||||
|
assertEquals("task-1", progress.getMainId());
|
||||||
|
verify(dqInspectProgressMapper).insert(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_save_sub_when_list_null() {
|
||||||
|
DqInspectTask task = new DqInspectTask();
|
||||||
|
task.setProblemId("prob-1");
|
||||||
|
when(dqInspectProblemService.getById("prob-1")).thenReturn(specificProblem());
|
||||||
|
|
||||||
|
service.saveMain(task, null);
|
||||||
|
|
||||||
|
verify(dqInspectProgressMapper, never()).insert(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== updateMain ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_touch_sub_when_list_null() {
|
||||||
|
// 关键行为:子表 null 表示"不修改子表",不得删除子表数据
|
||||||
|
DqInspectTask task = new DqInspectTask();
|
||||||
|
task.setId("task-1");
|
||||||
|
task.setProblemId("prob-1");
|
||||||
|
when(dqInspectProblemService.getById("prob-1")).thenReturn(specificProblem());
|
||||||
|
|
||||||
|
service.updateMain(task, null);
|
||||||
|
|
||||||
|
verify(dqInspectTaskMapper).updateById(task);
|
||||||
|
verify(dqInspectProgressMapper, never()).deleteByMainId(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_replace_sub_when_list_provided() {
|
||||||
|
DqInspectTask task = new DqInspectTask();
|
||||||
|
task.setId("task-1");
|
||||||
|
task.setProblemId("prob-1");
|
||||||
|
when(dqInspectProblemService.getById("prob-1")).thenReturn(specificProblem());
|
||||||
|
DqInspectProgress progress = new DqInspectProgress();
|
||||||
|
|
||||||
|
service.updateMain(task, List.of(progress));
|
||||||
|
|
||||||
|
verify(dqInspectProgressMapper).deleteByMainId("task-1");
|
||||||
|
assertEquals("task-1", progress.getMainId());
|
||||||
|
verify(dqInspectProgressMapper).insert(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 删除 ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_delete_main_with_sub() {
|
||||||
|
service.delMain("task-1");
|
||||||
|
verify(dqInspectProgressMapper).deleteByMainId("task-1");
|
||||||
|
verify(dqInspectTaskMapper).deleteById("task-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_delete_batch_with_sub() {
|
||||||
|
service.delBatchMain(List.of("task-1", "task-2"));
|
||||||
|
verify(dqInspectProgressMapper).deleteByMainId("task-1");
|
||||||
|
verify(dqInspectTaskMapper).deleteById("task-1");
|
||||||
|
verify(dqInspectProgressMapper).deleteByMainId("task-2");
|
||||||
|
verify(dqInspectTaskMapper).deleteById("task-2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 台账查询 ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_query_ledger_page() {
|
||||||
|
Page<DqInspectTaskLedgerVo> page = new Page<>(1, 10);
|
||||||
|
DqInspectTask query = new DqInspectTask();
|
||||||
|
when(dqInspectTaskMapper.queryLedgerPage(page, query)).thenReturn(page);
|
||||||
|
|
||||||
|
IPage<DqInspectTaskLedgerVo> result = service.queryLedgerPage(page, query);
|
||||||
|
|
||||||
|
assertEquals(page, result);
|
||||||
|
verify(dqInspectTaskMapper).queryLedgerPage(page, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package org.jeecg.modules.supervision.fixcontact.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.modules.supervision.fixcontact.entity.FixedContactFeedback20260730;
|
||||||
|
import org.jeecg.modules.supervision.fixcontact.mapper.FixedContactFeedback20260730Mapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FixedContactFeedback20260730ServiceImpl 测试(简单转发)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class FixedContactFeedback20260730ServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private FixedContactFeedback20260730Mapper fixedContactFeedback20260730Mapper;
|
||||||
|
|
||||||
|
private FixedContactFeedback20260730ServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new FixedContactFeedback20260730ServiceImpl();
|
||||||
|
// 该类直接使用 ServiceImpl 的 baseMapper(无自有 mapper 字段)
|
||||||
|
ReflectionTestUtils.setField(service, "baseMapper", fixedContactFeedback20260730Mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_select_by_main_id() {
|
||||||
|
FixedContactFeedback20260730 feedback = new FixedContactFeedback20260730();
|
||||||
|
when(fixedContactFeedback20260730Mapper.selectByMainId("m-1")).thenReturn(List.of(feedback));
|
||||||
|
assertEquals(1, service.selectByMainId("m-1").size());
|
||||||
|
verify(fixedContactFeedback20260730Mapper).selectByMainId("m-1");
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package org.jeecg.modules.supervision.improvementproposal.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.entity.ImprovementDeptScore;
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.mapper.ImprovementDeptScoreMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImprovementDeptScoreServiceImpl 测试(简单转发)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ImprovementDeptScoreServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private ImprovementDeptScoreMapper improvementDeptScoreMapper;
|
||||||
|
|
||||||
|
private ImprovementDeptScoreServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new ImprovementDeptScoreServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "improvementDeptScoreMapper", improvementDeptScoreMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_select_by_main_id() {
|
||||||
|
ImprovementDeptScore score = new ImprovementDeptScore();
|
||||||
|
when(improvementDeptScoreMapper.selectByMainId("m-1")).thenReturn(List.of(score));
|
||||||
|
assertEquals(1, service.selectByMainId("m-1").size());
|
||||||
|
verify(improvementDeptScoreMapper).selectByMainId("m-1");
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package org.jeecg.modules.supervision.improvementproposal.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.entity.ImprovementInstituteVote;
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.mapper.ImprovementInstituteVoteMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImprovementInstituteVoteServiceImpl 测试(简单转发)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ImprovementInstituteVoteServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private ImprovementInstituteVoteMapper improvementInstituteVoteMapper;
|
||||||
|
|
||||||
|
private ImprovementInstituteVoteServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new ImprovementInstituteVoteServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "improvementInstituteVoteMapper", improvementInstituteVoteMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_select_by_main_id() {
|
||||||
|
ImprovementInstituteVote vote = new ImprovementInstituteVote();
|
||||||
|
when(improvementInstituteVoteMapper.selectByMainId("m-1")).thenReturn(List.of(vote));
|
||||||
|
assertEquals(1, service.selectByMainId("m-1").size());
|
||||||
|
verify(improvementInstituteVoteMapper).selectByMainId("m-1");
|
||||||
|
}
|
||||||
|
}
|
||||||
+233
@@ -0,0 +1,233 @@
|
|||||||
|
package org.jeecg.modules.supervision.improvementproposal.service.impl;
|
||||||
|
|
||||||
|
import org.flowable.engine.HistoryService;
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.flowable.engine.TaskService;
|
||||||
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
import org.jeecg.modules.extbpm.process.service.impl.BpmBaseExtApiImpl;
|
||||||
|
import org.jeecg.modules.extbpm.process.service.IExtActBpmFileService;
|
||||||
|
import org.jeecg.modules.extbpm.process.service.IExtActBpmLogService;
|
||||||
|
import org.jeecg.modules.extbpm.process.service.IExtActFlowDataService;
|
||||||
|
import org.jeecg.modules.extbpm.process.service.IExtActTaskCcService;
|
||||||
|
import org.jeecg.modules.system.service.ISysUserDepartService;
|
||||||
|
import org.jeecg.modules.system.service.ISysRoleService;
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.dto.ImprovementProposalBpmActionDTO;
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.dto.ImprovementDeptScoreBpmDTO;
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.entity.ImprovementProposal;
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.mapper.ImprovementDeptScoreMapper;
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.mapper.ImprovementInstituteVoteMapper;
|
||||||
|
import org.jeecg.modules.supervision.improvementproposal.mapper.ImprovementProposalMapper;
|
||||||
|
import org.jeecg.modules.taskapprovalopinion.service.ITaskApprovalOpinionService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImprovementProposalServiceImpl 测试(mock mapper/流程服务)。
|
||||||
|
* 核心:流水号生成、初评奖级与 needDeptScore 联动、部门评分计算与落库。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ImprovementProposalServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private ImprovementProposalMapper improvementProposalMapper;
|
||||||
|
@Mock private ImprovementDeptScoreMapper improvementDeptScoreMapper;
|
||||||
|
@Mock private ImprovementInstituteVoteMapper improvementInstituteVoteMapper;
|
||||||
|
@Mock private BpmBaseExtApiImpl bpmBaseExtApi;
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
@Mock private HistoryService historyService;
|
||||||
|
@Mock private TaskService taskService;
|
||||||
|
@Mock private ISysRoleService sysRoleService;
|
||||||
|
@Mock private ISysUserDepartService sysUserDepartService;
|
||||||
|
@Mock private IExtActTaskCcService extActTaskCcService;
|
||||||
|
@Mock private ITaskApprovalOpinionService taskApprovalOpinionService;
|
||||||
|
@Mock private IExtActFlowDataService extActFlowDataService;
|
||||||
|
@Mock private IExtActBpmLogService extActBpmLogService;
|
||||||
|
@Mock private IExtActBpmFileService extActBpmFileService;
|
||||||
|
|
||||||
|
private ImprovementProposalServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new ImprovementProposalServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "improvementProposalMapper", improvementProposalMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "improvementDeptScoreMapper", improvementDeptScoreMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "improvementInstituteVoteMapper", improvementInstituteVoteMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "bpmBaseExtApi", bpmBaseExtApi);
|
||||||
|
ReflectionTestUtils.setField(service, "runtimeService", runtimeService);
|
||||||
|
ReflectionTestUtils.setField(service, "historyService", historyService);
|
||||||
|
ReflectionTestUtils.setField(service, "taskService", taskService);
|
||||||
|
ReflectionTestUtils.setField(service, "sysRoleService", sysRoleService);
|
||||||
|
ReflectionTestUtils.setField(service, "sysUserDepartService", sysUserDepartService);
|
||||||
|
ReflectionTestUtils.setField(service, "extActTaskCcService", extActTaskCcService);
|
||||||
|
ReflectionTestUtils.setField(service, "taskApprovalOpinionService", taskApprovalOpinionService);
|
||||||
|
ReflectionTestUtils.setField(service, "extActFlowDataService", extActFlowDataService);
|
||||||
|
ReflectionTestUtils.setField(service, "extActBpmLogService", extActBpmLogService);
|
||||||
|
ReflectionTestUtils.setField(service, "extActBpmFileService", extActBpmFileService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoginUser user(String id) {
|
||||||
|
LoginUser u = new LoginUser();
|
||||||
|
u.setId(id);
|
||||||
|
u.setUsername("u" + id);
|
||||||
|
u.setRealname("用户" + id);
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** mock 用户有效部门:orgType=2 + orgCategory=2 + delFlag=0 才进入可提报部门 */
|
||||||
|
private void mockUserDepartments(String userId, String deptId, String deptName) {
|
||||||
|
org.jeecg.modules.system.entity.SysDepart depart = new org.jeecg.modules.system.entity.SysDepart();
|
||||||
|
depart.setId(deptId);
|
||||||
|
depart.setDepartName(deptName);
|
||||||
|
depart.setOrgCode("org-" + deptId);
|
||||||
|
depart.setOrgType("2");
|
||||||
|
depart.setOrgCategory("2");
|
||||||
|
depart.setDelFlag("0");
|
||||||
|
when(sysUserDepartService.getOrderedDepartListById(userId)).thenReturn(java.util.List.of(depart));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== createNewProposal(流水号) ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CreateNewProposal {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_generate_proposal_no_with_sequence() {
|
||||||
|
ImprovementProposal proposal = new ImprovementProposal();
|
||||||
|
proposal.setProposalDeptId("dept-1");
|
||||||
|
mockUserDepartments("1", "dept-1", "研发部");
|
||||||
|
when(improvementProposalMapper.incrementProposalYearSequence(2026)).thenReturn(1);
|
||||||
|
when(improvementProposalMapper.selectProposalYearSequenceForUpdate(2026)).thenReturn(12);
|
||||||
|
|
||||||
|
service.createNewProposal(proposal, user("1"));
|
||||||
|
|
||||||
|
assertEquals("研发部-2026年-0012", proposal.getProposalNo());
|
||||||
|
verify(improvementProposalMapper).insert(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_sequence_missing() {
|
||||||
|
ImprovementProposal proposal = new ImprovementProposal();
|
||||||
|
proposal.setProposalDeptId("dept-1");
|
||||||
|
mockUserDepartments("1", "dept-1", "研发部");
|
||||||
|
when(improvementProposalMapper.incrementProposalYearSequence(any())).thenReturn(1);
|
||||||
|
when(improvementProposalMapper.selectProposalYearSequenceForUpdate(any())).thenReturn(null);
|
||||||
|
assertThrows(IllegalStateException.class, () -> service.createNewProposal(proposal, user("1")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== saveInitialReview(奖级 → needDeptScore) ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class SaveInitialReview {
|
||||||
|
|
||||||
|
private ImprovementProposal proposal(String id, String procId) {
|
||||||
|
ImprovementProposal p = new ImprovementProposal();
|
||||||
|
p.setId(id);
|
||||||
|
p.setProcessInstanceId(procId);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_set_need_dept_score_when_gold() {
|
||||||
|
ImprovementProposalBpmActionDTO action = new ImprovementProposalBpmActionDTO();
|
||||||
|
action.setMainId("p-1");
|
||||||
|
action.setProcessInstanceId("proc-1");
|
||||||
|
action.setTaskId("t-1");
|
||||||
|
action.setInitialAward("0"); // 金 → 需部门评分
|
||||||
|
when(improvementProposalMapper.selectById("p-1")).thenReturn(proposal("p-1", "proc-1"));
|
||||||
|
org.flowable.task.api.Task task = org.mockito.Mockito.mock(org.flowable.task.api.Task.class);
|
||||||
|
org.flowable.task.api.TaskQuery taskQuery = org.mockito.Mockito.mock(org.flowable.task.api.TaskQuery.class);
|
||||||
|
when(taskService.createTaskQuery()).thenReturn(taskQuery);
|
||||||
|
when(taskQuery.taskId("t-1")).thenReturn(taskQuery);
|
||||||
|
when(taskQuery.singleResult()).thenReturn(task);
|
||||||
|
when(task.getProcessInstanceId()).thenReturn("proc-1");
|
||||||
|
when(task.getTaskDefinitionKey()).thenReturn("Task_improve001");
|
||||||
|
when(task.getAssignee()).thenReturn("u1");
|
||||||
|
|
||||||
|
service.saveInitialReview(action, user("1"));
|
||||||
|
|
||||||
|
verify(improvementProposalMapper).updateById(any());
|
||||||
|
verify(runtimeService).setVariable("proc-1", "need_dept_score", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_need_dept_score_when_bronze() {
|
||||||
|
ImprovementProposalBpmActionDTO action = new ImprovementProposalBpmActionDTO();
|
||||||
|
action.setMainId("p-1");
|
||||||
|
action.setProcessInstanceId("proc-1");
|
||||||
|
action.setTaskId("t-1");
|
||||||
|
action.setInitialAward("2"); // 铜 → 不需部门评分
|
||||||
|
when(improvementProposalMapper.selectById("p-1")).thenReturn(proposal("p-1", "proc-1"));
|
||||||
|
org.flowable.task.api.Task task = org.mockito.Mockito.mock(org.flowable.task.api.Task.class);
|
||||||
|
org.flowable.task.api.TaskQuery taskQuery = org.mockito.Mockito.mock(org.flowable.task.api.TaskQuery.class);
|
||||||
|
when(taskService.createTaskQuery()).thenReturn(taskQuery);
|
||||||
|
when(taskQuery.taskId("t-1")).thenReturn(taskQuery);
|
||||||
|
when(taskQuery.singleResult()).thenReturn(task);
|
||||||
|
when(task.getProcessInstanceId()).thenReturn("proc-1");
|
||||||
|
when(task.getTaskDefinitionKey()).thenReturn("Task_improve001");
|
||||||
|
when(task.getAssignee()).thenReturn("u1");
|
||||||
|
|
||||||
|
service.saveInitialReview(action, user("1"));
|
||||||
|
|
||||||
|
verify(runtimeService).setVariable("proc-1", "need_dept_score", "0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== saveDeptScore(评分计算 + 落库) ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class SaveDeptScore {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_calculate_and_insert_score() {
|
||||||
|
ImprovementDeptScoreBpmDTO scoreAction = new ImprovementDeptScoreBpmDTO();
|
||||||
|
scoreAction.setMainId("p-1");
|
||||||
|
scoreAction.setProcessInstanceId("proc-1");
|
||||||
|
scoreAction.setTaskId("t-1");
|
||||||
|
scoreAction.setSafetyScore(BigDecimal.valueOf(80));
|
||||||
|
scoreAction.setQualityScore(BigDecimal.valueOf(90));
|
||||||
|
scoreAction.setEfficiencyScore(BigDecimal.valueOf(70));
|
||||||
|
scoreAction.setCostScore(BigDecimal.valueOf(60));
|
||||||
|
scoreAction.setPromotionScore(BigDecimal.valueOf(80));
|
||||||
|
scoreAction.setOriginalityScore(BigDecimal.valueOf(90));
|
||||||
|
scoreAction.setEffortScore(BigDecimal.valueOf(60));
|
||||||
|
|
||||||
|
ImprovementProposal proposal = new ImprovementProposal();
|
||||||
|
proposal.setId("p-1");
|
||||||
|
proposal.setProcessInstanceId("proc-1");
|
||||||
|
when(improvementProposalMapper.selectById("p-1")).thenReturn(proposal);
|
||||||
|
when(improvementProposalMapper.selectByIdForUpdate("p-1")).thenReturn(proposal);
|
||||||
|
when(improvementDeptScoreMapper.selectByMainId("p-1")).thenReturn(java.util.List.of());
|
||||||
|
org.flowable.task.api.Task task = org.mockito.Mockito.mock(org.flowable.task.api.Task.class);
|
||||||
|
org.flowable.task.api.TaskQuery taskQuery = org.mockito.Mockito.mock(org.flowable.task.api.TaskQuery.class);
|
||||||
|
when(taskService.createTaskQuery()).thenReturn(taskQuery);
|
||||||
|
when(taskQuery.taskId("t-1")).thenReturn(taskQuery);
|
||||||
|
when(taskQuery.singleResult()).thenReturn(task);
|
||||||
|
when(task.getProcessInstanceId()).thenReturn("proc-1");
|
||||||
|
when(task.getTaskDefinitionKey()).thenReturn("Task_improve002");
|
||||||
|
when(task.getAssignee()).thenReturn("u1");
|
||||||
|
|
||||||
|
service.saveDeptScore(scoreAction, user("1"));
|
||||||
|
|
||||||
|
org.mockito.ArgumentCaptor<org.jeecg.modules.supervision.improvementproposal.entity.ImprovementDeptScore> captor =
|
||||||
|
org.mockito.ArgumentCaptor.forClass(org.jeecg.modules.supervision.improvementproposal.entity.ImprovementDeptScore.class);
|
||||||
|
verify(improvementDeptScoreMapper).insert(captor.capture());
|
||||||
|
// 效果分 (80+90+70+60)/4=75;总分 75*0.5+80*0.2+90*0.2+60*0.1=77.5
|
||||||
|
assertEquals("75.00", captor.getValue().getEffectScore().toPlainString());
|
||||||
|
assertEquals("77.50", captor.getValue().getTotalScore().toPlainString());
|
||||||
|
assertEquals("p-1", captor.getValue().getMainId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
package org.jeecg.modules.supervision.improvementproposal.service.impl;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImprovementProposalSupport 纯函数测试(TDD 提炼自 ImprovementProposalServiceImpl)。
|
||||||
|
*/
|
||||||
|
class ImprovementProposalSupportTest {
|
||||||
|
|
||||||
|
// ============ formatProposalNo ============
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_format_sequence_with_4_digits() {
|
||||||
|
// 原格式:部门-年份年-4位序号(含连字符)
|
||||||
|
assertEquals("研发部-2026年-0001",
|
||||||
|
ImprovementProposalSupport.formatProposalNo("研发部", 2026, 1));
|
||||||
|
assertEquals("研发部-2026年-0012",
|
||||||
|
ImprovementProposalSupport.formatProposalNo(" 研发部 ", 2026, 12));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ calculateEffectScore ============
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_calculate_effect_average() {
|
||||||
|
// (80+90+70+60)/4 = 75
|
||||||
|
BigDecimal score = ImprovementProposalSupport.calculateEffectScore(
|
||||||
|
BigDecimal.valueOf(80), BigDecimal.valueOf(90),
|
||||||
|
BigDecimal.valueOf(70), BigDecimal.valueOf(60));
|
||||||
|
assertEquals(0, score.compareTo(BigDecimal.valueOf(75)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_round_half_up_to_2_digits() {
|
||||||
|
// (85+85+85+86)/4 = 85.25
|
||||||
|
BigDecimal score = ImprovementProposalSupport.calculateEffectScore(
|
||||||
|
BigDecimal.valueOf(85), BigDecimal.valueOf(85),
|
||||||
|
BigDecimal.valueOf(85), BigDecimal.valueOf(86));
|
||||||
|
assertEquals("85.25", score.toPlainString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ calculateTotalScore ============
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_calculate_weighted_total() {
|
||||||
|
// 效果75*0.5 + 推广80*0.2 + 独创90*0.2 + 努力60*0.1 = 37.5+16+18+6 = 77.5
|
||||||
|
BigDecimal total = ImprovementProposalSupport.calculateTotalScore(
|
||||||
|
BigDecimal.valueOf(75), BigDecimal.valueOf(80),
|
||||||
|
BigDecimal.valueOf(90), BigDecimal.valueOf(60));
|
||||||
|
assertEquals("77.50", total.toPlainString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ resolveSuggestedAward ============
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_map_award_by_average_score() {
|
||||||
|
assertEquals("0", ImprovementProposalSupport.resolveSuggestedAward(BigDecimal.valueOf(85))); // 金
|
||||||
|
assertEquals("1", ImprovementProposalSupport.resolveSuggestedAward(BigDecimal.valueOf(70))); // 银
|
||||||
|
assertEquals("2", ImprovementProposalSupport.resolveSuggestedAward(BigDecimal.valueOf(50))); // 铜
|
||||||
|
assertEquals("3", ImprovementProposalSupport.resolveSuggestedAward(BigDecimal.valueOf(30))); // 参与
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_handle_boundaries() {
|
||||||
|
assertEquals("0", ImprovementProposalSupport.resolveSuggestedAward(BigDecimal.valueOf(80))); // 80→金
|
||||||
|
assertEquals("1", ImprovementProposalSupport.resolveSuggestedAward(BigDecimal.valueOf(60))); // 60→银
|
||||||
|
assertEquals("2", ImprovementProposalSupport.resolveSuggestedAward(BigDecimal.valueOf(40))); // 40→铜
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ award 校验/排名/部门评分判断 ============
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_validate_award_values() {
|
||||||
|
assertTrue(ImprovementProposalSupport.isValidAward("0"));
|
||||||
|
assertTrue(ImprovementProposalSupport.isValidAward("3"));
|
||||||
|
assertFalse(ImprovementProposalSupport.isValidAward("4"));
|
||||||
|
assertFalse(ImprovementProposalSupport.isValidAward(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_rank_award() {
|
||||||
|
assertEquals(2, ImprovementProposalSupport.awardRank("2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_need_dept_score_only_for_gold_silver() {
|
||||||
|
assertTrue(ImprovementProposalSupport.needsDeptScore("0"));
|
||||||
|
assertTrue(ImprovementProposalSupport.needsDeptScore("1"));
|
||||||
|
assertFalse(ImprovementProposalSupport.needsDeptScore("2"));
|
||||||
|
assertFalse(ImprovementProposalSupport.needsDeptScore("3"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
package org.jeecg.modules.supervision.tasklist.service.impl;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TaskListDetailSupport 纯函数测试(TDD 提炼自 TaskListDetialServiceImpl)。
|
||||||
|
*/
|
||||||
|
class TaskListDetailSupportTest {
|
||||||
|
|
||||||
|
// ============ normalizePid ============
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_keep_non_empty_pid() {
|
||||||
|
assertEquals("p-1", TaskListDetailSupport.normalizePid("p-1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_normalize_empty_pid_to_null() {
|
||||||
|
assertNull(TaskListDetailSupport.normalizePid(""));
|
||||||
|
assertNull(TaskListDetailSupport.normalizePid(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ collectUserIds ============
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_collect_trimmed_ids() {
|
||||||
|
Set<String> set = new LinkedHashSet<>();
|
||||||
|
TaskListDetailSupport.collectUserIds(" u1 , u2 ,u1 ", set);
|
||||||
|
assertEquals(Set.of("u1", "u2"), set);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_empty_ids_str() {
|
||||||
|
Set<String> set = new LinkedHashSet<>();
|
||||||
|
TaskListDetailSupport.collectUserIds("", set);
|
||||||
|
TaskListDetailSupport.collectUserIds(null, set);
|
||||||
|
assertEquals(0, set.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ buildNamesFromMap ============
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_build_names_in_order() {
|
||||||
|
String names = TaskListDetailSupport.buildNamesFromMap("u1,u2",
|
||||||
|
Map.of("u1", "张三", "u2", "李四"));
|
||||||
|
assertEquals("张三,李四", names);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_ids_without_mapping() {
|
||||||
|
String names = TaskListDetailSupport.buildNamesFromMap("u1,u3",
|
||||||
|
Map.of("u1", "张三"));
|
||||||
|
assertEquals("张三", names);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_null_when_no_match_or_empty() {
|
||||||
|
assertNull(TaskListDetailSupport.buildNamesFromMap("u9", Map.of("u1", "张三")));
|
||||||
|
assertNull(TaskListDetailSupport.buildNamesFromMap("", Map.of("u1", "张三")));
|
||||||
|
assertNull(TaskListDetailSupport.buildNamesFromMap("u1", Map.of()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ mergeIds ============
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_merge_deduplicate_keep_order() {
|
||||||
|
assertEquals("u1,u2,u3", TaskListDetailSupport.mergeIds("u1,u2", "u2,u3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_merge_single_side() {
|
||||||
|
assertEquals("u1,u2", TaskListDetailSupport.mergeIds("u1,u2", null));
|
||||||
|
assertEquals("u3", TaskListDetailSupport.mergeIds(null, "u3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_null_when_both_empty() {
|
||||||
|
assertNull(TaskListDetailSupport.mergeIds("", ""));
|
||||||
|
assertNull(TaskListDetailSupport.mergeIds(null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
+193
@@ -0,0 +1,193 @@
|
|||||||
|
package org.jeecg.modules.supervision.tasklist.service.impl;
|
||||||
|
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.entity.TaskListDetial;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.mapper.TaskListDetialMapper;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.mapper.TaskListMapper;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.mapper.TaskListPermissionMapper;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TaskListDetialServiceImpl 测试(Shiro ThreadContext.bind 提供登录用户 + mock mapper/sysBaseAPI)。
|
||||||
|
* 核心:deleteTask/toggleStatus/moveTask 的权限与父子计数规则 + 查询方法。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class TaskListDetialServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private TaskListDetialMapper taskListDetialMapper;
|
||||||
|
@Mock private TaskListMapper taskListMapper;
|
||||||
|
@Mock private TaskListPermissionMapper taskListPermissionMapper;
|
||||||
|
@Mock private ISysBaseAPI sysBaseAPI;
|
||||||
|
@Mock private Subject subject;
|
||||||
|
|
||||||
|
private TaskListDetialServiceImpl service;
|
||||||
|
|
||||||
|
private final LoginUser loginUser = new LoginUser();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
loginUser.setId("user-1");
|
||||||
|
loginUser.setUsername("admin");
|
||||||
|
loginUser.setRealname("管理员");
|
||||||
|
service = new TaskListDetialServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "taskListDetialMapper", taskListDetialMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "taskListMapper", taskListMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "taskListPermissionMapper", taskListPermissionMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "sysBaseAPI", sysBaseAPI);
|
||||||
|
// Shiro 官方测试机制:绑定 Subject 到当前线程,SecurityUtils.getSubject() 即可取到
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
// lenient:查询方法不经过 SecurityUtils,仅写方法用到
|
||||||
|
lenient().when(subject.getPrincipal()).thenReturn(loginUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
// Shiro 1.13 无 unbind(),用 remove() 清除线程上下文
|
||||||
|
ThreadContext.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskListDetial task(String id, String mainId, String pid, String type, Integer isDefault) {
|
||||||
|
TaskListDetial t = new TaskListDetial();
|
||||||
|
t.setId(id);
|
||||||
|
t.setMainId(mainId);
|
||||||
|
t.setPid(pid);
|
||||||
|
t.setType(type);
|
||||||
|
t.setIsDefault(isDefault);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== deleteTask ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_task_not_exists() {
|
||||||
|
when(taskListDetialMapper.selectById("t1")).thenReturn(null);
|
||||||
|
assertThrows(RuntimeException.class, () -> service.deleteTask("t1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_default_group() {
|
||||||
|
TaskListDetial group = task("g1", "m1", null, "0", 1);
|
||||||
|
when(taskListDetialMapper.selectById("g1")).thenReturn(group);
|
||||||
|
when(taskListPermissionMapper.selectOne(any())).thenReturn(null);
|
||||||
|
assertThrows(RuntimeException.class, () -> service.deleteTask("g1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_delete_task_and_update_parent_counts() {
|
||||||
|
TaskListDetial task = task("t1", "m1", "p1", "1", null);
|
||||||
|
task.setTaskStatus(1);
|
||||||
|
when(taskListDetialMapper.selectById("t1")).thenReturn(task);
|
||||||
|
when(taskListPermissionMapper.selectOne(any())).thenReturn(perm("1"));
|
||||||
|
when(taskListDetialMapper.selectChildrenByPid("t1")).thenReturn(List.of());
|
||||||
|
TaskListDetial parent = task("p1", "m1", null, "1", null);
|
||||||
|
when(taskListDetialMapper.selectById("p1")).thenReturn(parent);
|
||||||
|
when(taskListDetialMapper.countChildrenByPid("p1")).thenReturn(0);
|
||||||
|
|
||||||
|
service.deleteTask("t1");
|
||||||
|
|
||||||
|
verify(taskListDetialMapper).deleteById("t1");
|
||||||
|
verify(taskListDetialMapper).decrementSubTaskCount("p1");
|
||||||
|
verify(taskListDetialMapper).decrementCompletedSubTaskCount("p1");
|
||||||
|
verify(taskListDetialMapper).updateHasChild("p1", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== toggleStatus ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_toggle_status_and_increment_completed() {
|
||||||
|
TaskListDetial task = task("t1", "m1", "p1", "1", null);
|
||||||
|
task.setTaskStatus(0);
|
||||||
|
when(taskListDetialMapper.selectById("t1")).thenReturn(task);
|
||||||
|
when(taskListPermissionMapper.selectOne(any())).thenReturn(perm("2"));
|
||||||
|
TaskListDetial parent = task("p1", "m1", null, "1", null);
|
||||||
|
when(taskListDetialMapper.selectById("p1")).thenReturn(parent);
|
||||||
|
|
||||||
|
service.toggleStatus("t1");
|
||||||
|
|
||||||
|
verify(taskListDetialMapper).toggleTaskStatus(eq("t1"), eq("admin"), eq(1), any());
|
||||||
|
verify(taskListDetialMapper).incrementCompletedSubTaskCount("p1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_reject_reader_operating_others_task() {
|
||||||
|
TaskListDetial task = task("t1", "m1", null, "1", null);
|
||||||
|
task.setAssigneeId("other-user");
|
||||||
|
when(taskListDetialMapper.selectById("t1")).thenReturn(task);
|
||||||
|
when(taskListPermissionMapper.selectOne(any())).thenReturn(perm("3"));
|
||||||
|
assertThrows(RuntimeException.class, () -> service.toggleStatus("t1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== moveTask ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_move_task_and_update_parents_when_pid_changed() {
|
||||||
|
TaskListDetial task = task("t1", "m1", "old-p", "1", null);
|
||||||
|
when(taskListDetialMapper.selectById("t1")).thenReturn(task);
|
||||||
|
when(taskListPermissionMapper.selectOne(any())).thenReturn(perm("2"));
|
||||||
|
when(taskListDetialMapper.selectList(any())).thenReturn(List.of());
|
||||||
|
TaskListDetial oldParent = task("old-p", "m1", null, "1", null);
|
||||||
|
TaskListDetial newParent = task("new-p", "m1", null, "1", null);
|
||||||
|
when(taskListDetialMapper.selectById("old-p")).thenReturn(oldParent);
|
||||||
|
when(taskListDetialMapper.selectById("new-p")).thenReturn(newParent);
|
||||||
|
when(taskListDetialMapper.countChildrenByPid("old-p")).thenReturn(0);
|
||||||
|
|
||||||
|
org.jeecg.modules.supervision.tasklist.vo.MoveTaskReq req = new org.jeecg.modules.supervision.tasklist.vo.MoveTaskReq();
|
||||||
|
req.setTaskId("t1");
|
||||||
|
req.setTargetPid("new-p");
|
||||||
|
req.setTargetSortOrder(1);
|
||||||
|
service.moveTask(req);
|
||||||
|
|
||||||
|
verify(taskListDetialMapper).decrementSubTaskCount("old-p");
|
||||||
|
verify(taskListDetialMapper).incrementSubTaskCount("new-p");
|
||||||
|
verify(taskListDetialMapper).updateHasChild("new-p", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 查询方法 ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_query_all_by_main_id() {
|
||||||
|
// task 无 createBy/assigneeId 等字段 → fillCreateByName 不触发用户查询
|
||||||
|
TaskListDetial task = task("t1", "m1", null, "1", null);
|
||||||
|
when(taskListDetialMapper.selectAllByMainId("m1")).thenReturn(List.of(task));
|
||||||
|
|
||||||
|
List<TaskListDetial> result = service.queryAllByMainId("m1");
|
||||||
|
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
verify(taskListDetialMapper).selectAllByMainId("m1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_query_top_level() {
|
||||||
|
when(taskListDetialMapper.selectTopLevelByMainId("m1")).thenReturn(List.of());
|
||||||
|
assertEquals(0, service.queryTopLevelByMainId("m1").size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 工具 ====================
|
||||||
|
|
||||||
|
private org.jeecg.modules.supervision.tasklist.entity.TaskListPermission perm(String permission) {
|
||||||
|
org.jeecg.modules.supervision.tasklist.entity.TaskListPermission p =
|
||||||
|
new org.jeecg.modules.supervision.tasklist.entity.TaskListPermission();
|
||||||
|
p.setPermission(permission);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
package org.jeecg.modules.supervision.tasklist.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.entity.TaskListFavorite;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.mapper.TaskListFavoriteMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TaskListFavoriteServiceImpl 测试(排序规则 reorderItem 为核心)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class TaskListFavoriteServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private TaskListFavoriteMapper taskListFavoriteMapper;
|
||||||
|
|
||||||
|
private TaskListFavoriteServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new TaskListFavoriteServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "taskListFavoriteMapper", taskListFavoriteMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class MaxSortOrder {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_default_zero_when_null() {
|
||||||
|
when(taskListFavoriteMapper.selectMaxSortOrder("u1", "p1")).thenReturn(null);
|
||||||
|
assertEquals(0, service.getMaxSortOrder("u1", "p1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_max() {
|
||||||
|
when(taskListFavoriteMapper.selectMaxSortOrder("u1", "p1")).thenReturn(5);
|
||||||
|
assertEquals(5, service.getMaxSortOrder("u1", "p1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ReorderItem {
|
||||||
|
|
||||||
|
private TaskListFavorite favorite(String id, Integer sort) {
|
||||||
|
TaskListFavorite f = new TaskListFavorite();
|
||||||
|
f.setId(id);
|
||||||
|
f.setSortOrder(sort);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_moved_not_found() {
|
||||||
|
when(taskListFavoriteMapper.selectById("m1")).thenReturn(null);
|
||||||
|
assertThrows(RuntimeException.class,
|
||||||
|
() -> service.reorderItem("m1", "u1", "p1", null, 1, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_reorder_siblings_within_same_group() {
|
||||||
|
// 同组移动:m1 拖到位置 1,兄弟重排
|
||||||
|
TaskListFavorite moved = favorite("m1", 3);
|
||||||
|
when(taskListFavoriteMapper.selectById("m1")).thenReturn(moved);
|
||||||
|
when(taskListFavoriteMapper.selectList(any(Wrapper.class)))
|
||||||
|
.thenReturn(List.of(favorite("a", 1), favorite("b", 2)));
|
||||||
|
|
||||||
|
service.reorderItem("m1", "u1", "p1", null, 1, null);
|
||||||
|
|
||||||
|
// 兄弟按新位置重排:pos=1 → a 挪到 2,b 挪到 3
|
||||||
|
verify(taskListFavoriteMapper).updateSortOrder(eq("a"), eq(2));
|
||||||
|
verify(taskListFavoriteMapper).updateSortOrder(eq("b"), eq(3));
|
||||||
|
// moved 更新 pid 与位置
|
||||||
|
verify(taskListFavoriteMapper).updatePidAndSort("m1", "p1", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_use_new_pid_when_cross_group() {
|
||||||
|
TaskListFavorite moved = favorite("m1", 1);
|
||||||
|
when(taskListFavoriteMapper.selectById("m1")).thenReturn(moved);
|
||||||
|
when(taskListFavoriteMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
|
||||||
|
|
||||||
|
service.reorderItem("m1", "u1", "p1", "type1", null, "new-group");
|
||||||
|
|
||||||
|
verify(taskListFavoriteMapper).updatePidAndSort("m1", "new-group", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_set_null_pid_when_new_pid_empty() {
|
||||||
|
// newPid="" 表示拖到根级别(无分组)→ pid 置 null
|
||||||
|
TaskListFavorite moved = favorite("m1", 1);
|
||||||
|
when(taskListFavoriteMapper.selectById("m1")).thenReturn(moved);
|
||||||
|
when(taskListFavoriteMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
|
||||||
|
|
||||||
|
service.reorderItem("m1", "u1", "p1", null, null, "");
|
||||||
|
|
||||||
|
verify(taskListFavoriteMapper).updatePidAndSort("m1", null, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_update_sibling_when_sort_unchanged() {
|
||||||
|
TaskListFavorite moved = favorite("m1", 2);
|
||||||
|
when(taskListFavoriteMapper.selectById("m1")).thenReturn(moved);
|
||||||
|
when(taskListFavoriteMapper.selectList(any(Wrapper.class)))
|
||||||
|
.thenReturn(List.of(favorite("a", 1)));
|
||||||
|
|
||||||
|
// pos=2(默认 targetPosition null → siblings.size()+1=2):a 保持 1,不更新
|
||||||
|
service.reorderItem("m1", "u1", "p1", null, null, null);
|
||||||
|
|
||||||
|
verify(taskListFavoriteMapper, never()).updateSortOrder(eq("a"), any());
|
||||||
|
verify(taskListFavoriteMapper).updatePidAndSort("m1", "p1", 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package org.jeecg.modules.supervision.tasklist.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.modules.supervision.tasklist.entity.TaskListPermission;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.mapper.TaskListPermissionMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TaskListPermissionServiceImpl 测试(简单转发)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class TaskListPermissionServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private TaskListPermissionMapper taskListPermissionMapper;
|
||||||
|
|
||||||
|
private TaskListPermissionServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new TaskListPermissionServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "taskListPermissionMapper", taskListPermissionMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_select_by_main_id() {
|
||||||
|
TaskListPermission perm = new TaskListPermission();
|
||||||
|
when(taskListPermissionMapper.selectByMainId("m-1")).thenReturn(List.of(perm));
|
||||||
|
assertEquals(1, service.selectByMainId("m-1").size());
|
||||||
|
verify(taskListPermissionMapper).selectByMainId("m-1");
|
||||||
|
}
|
||||||
|
}
|
||||||
+236
@@ -0,0 +1,236 @@
|
|||||||
|
package org.jeecg.modules.supervision.tasklist.service.impl;
|
||||||
|
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.entity.TaskList;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.entity.TaskListFavorite;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.mapper.TaskListDetialMapper;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.mapper.TaskListFavoriteMapper;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.mapper.TaskListMapper;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.mapper.TaskListPermissionMapper;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.service.ITaskListFavoriteService;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.vo.AddCollaboratorReq;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.vo.CreateTaskListGroupReq;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.vo.CreateTaskListReq;
|
||||||
|
import org.jeecg.modules.supervision.tasklist.vo.MoveTaskListReq;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TaskListServiceImpl 测试(Shiro ThreadContext.bind + mock mapper/service/sysBaseAPI)。
|
||||||
|
* 核心:创建清单的密级/分组校验、创建分组排序、移动清单权限、添加协作人权限。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class TaskListServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private TaskListMapper taskListMapper;
|
||||||
|
@Mock private TaskListDetialMapper taskListDetialMapper;
|
||||||
|
@Mock private TaskListPermissionMapper taskListPermissionMapper;
|
||||||
|
@Mock private TaskListFavoriteMapper taskListFavoriteMapper;
|
||||||
|
@Mock private ITaskListFavoriteService taskListFavoriteService;
|
||||||
|
@Mock private ISysBaseAPI sysBaseAPI;
|
||||||
|
@Mock private Subject subject;
|
||||||
|
|
||||||
|
private TaskListServiceImpl service;
|
||||||
|
|
||||||
|
private final LoginUser loginUser = new LoginUser();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
loginUser.setId("user-1");
|
||||||
|
loginUser.setUsername("admin");
|
||||||
|
loginUser.setRealname("管理员");
|
||||||
|
loginUser.setUserSecurityLevel(3);
|
||||||
|
service = new TaskListServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "taskListMapper", taskListMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "taskListDetialMapper", taskListDetialMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "taskListPermissionMapper", taskListPermissionMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "taskListFavoriteMapper", taskListFavoriteMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "taskListFavoriteService", taskListFavoriteService);
|
||||||
|
ReflectionTestUtils.setField(service, "sysBaseAPI", sysBaseAPI);
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
lenient().when(subject.getPrincipal()).thenReturn(loginUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
ThreadContext.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== createTaskList ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CreateTaskList {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_reject_when_secret_level_too_high() {
|
||||||
|
CreateTaskListReq req = new CreateTaskListReq();
|
||||||
|
req.setSecretLevel(5); // userSecLevel=3,5>=3 → 拒绝
|
||||||
|
assertThrows(RuntimeException.class, () -> service.createTaskList(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_reject_when_group_invalid() {
|
||||||
|
CreateTaskListReq req = new CreateTaskListReq();
|
||||||
|
req.setSecretLevel(1);
|
||||||
|
req.setPid("g-1");
|
||||||
|
when(taskListFavoriteMapper.selectById("g-1")).thenReturn(null);
|
||||||
|
assertThrows(RuntimeException.class, () -> service.createTaskList(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_create_list_with_default_group() {
|
||||||
|
CreateTaskListReq req = new CreateTaskListReq();
|
||||||
|
req.setTasklistName("我的清单");
|
||||||
|
req.setSecretLevel(1);
|
||||||
|
// mock 模拟 MyBatis Plus insert 主键回填(ASSIGN_ID)
|
||||||
|
when(taskListMapper.insert(any())).thenAnswer(inv -> {
|
||||||
|
TaskList tl = inv.getArgument(0);
|
||||||
|
tl.setId("tl-1");
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
String id = service.createTaskList(req);
|
||||||
|
|
||||||
|
assertEquals("tl-1", id);
|
||||||
|
verify(taskListDetialMapper).insert(any()); // 默认分组
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== createTaskListGroup ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CreateTaskListGroup {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_use_max_sort_plus_one() {
|
||||||
|
CreateTaskListGroupReq req = new CreateTaskListGroupReq();
|
||||||
|
req.setTasklistName("新分组");
|
||||||
|
when(taskListFavoriteService.getMaxSortOrderByType("user-1", null, "0")).thenReturn(5);
|
||||||
|
|
||||||
|
String id = service.createTaskListGroup(req);
|
||||||
|
|
||||||
|
assertEquals(id, id);
|
||||||
|
org.mockito.ArgumentCaptor<TaskListFavorite> captor =
|
||||||
|
org.mockito.ArgumentCaptor.forClass(TaskListFavorite.class);
|
||||||
|
verify(taskListFavoriteMapper).insert(captor.capture());
|
||||||
|
assertEquals(6, captor.getValue().getSortOrder());
|
||||||
|
assertEquals("user-1", captor.getValue().getUserId());
|
||||||
|
assertEquals("0", captor.getValue().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== moveTaskList ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class MoveTaskList {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_reject_when_favorite_not_owner() {
|
||||||
|
MoveTaskListReq req = new MoveTaskListReq();
|
||||||
|
req.setFavoriteId("f-1");
|
||||||
|
when(taskListFavoriteMapper.selectById("f-1")).thenReturn(null);
|
||||||
|
assertThrows(RuntimeException.class, () -> service.moveTaskList(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_reject_when_favorite_is_group() {
|
||||||
|
TaskListFavorite group = new TaskListFavorite();
|
||||||
|
group.setId("f-1");
|
||||||
|
group.setUserId("user-1");
|
||||||
|
group.setType("0");
|
||||||
|
MoveTaskListReq req = new MoveTaskListReq();
|
||||||
|
req.setFavoriteId("f-1");
|
||||||
|
when(taskListFavoriteMapper.selectById("f-1")).thenReturn(group);
|
||||||
|
assertThrows(RuntimeException.class, () -> service.moveTaskList(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_move_list_and_reorder() {
|
||||||
|
TaskListFavorite favorite = new TaskListFavorite();
|
||||||
|
favorite.setId("f-1");
|
||||||
|
favorite.setUserId("user-1");
|
||||||
|
favorite.setType("1");
|
||||||
|
favorite.setPid("g-1");
|
||||||
|
when(taskListFavoriteMapper.selectById("f-1")).thenReturn(favorite);
|
||||||
|
|
||||||
|
MoveTaskListReq req = new MoveTaskListReq();
|
||||||
|
req.setFavoriteId("f-1");
|
||||||
|
req.setTargetGroupId("g-2");
|
||||||
|
req.setSortOrder(1);
|
||||||
|
TaskListFavorite targetGroup = new TaskListFavorite();
|
||||||
|
targetGroup.setUserId("user-1");
|
||||||
|
targetGroup.setType("0");
|
||||||
|
when(taskListFavoriteMapper.selectById("g-2")).thenReturn(targetGroup);
|
||||||
|
when(taskListFavoriteService.countChildren("user-1", "g-1")).thenReturn(0);
|
||||||
|
|
||||||
|
service.moveTaskList(req);
|
||||||
|
|
||||||
|
verify(taskListFavoriteService).reorderItem("f-1", "user-1", "g-1", "1", 1, "g-2");
|
||||||
|
// 旧父 hasChild 置 0 + 新父 hasChild 置 1(实体 updateById)
|
||||||
|
verify(taskListFavoriteMapper, times(2)).updateById(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== addCollaborator ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class AddCollaborator {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_reject_when_not_owner() {
|
||||||
|
AddCollaboratorReq req = new AddCollaboratorReq();
|
||||||
|
req.setTaskListId("tl-1");
|
||||||
|
req.setUserId("u-9");
|
||||||
|
req.setPermission("2");
|
||||||
|
when(taskListPermissionMapper.selectCount(any())).thenReturn(0L);
|
||||||
|
assertThrows(RuntimeException.class, () -> service.addCollaborator(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_reject_when_permission_type_invalid() {
|
||||||
|
AddCollaboratorReq req = new AddCollaboratorReq();
|
||||||
|
req.setTaskListId("tl-1");
|
||||||
|
req.setPermission("1");
|
||||||
|
when(taskListPermissionMapper.selectCount(any())).thenReturn(1L);
|
||||||
|
assertThrows(RuntimeException.class, () -> service.addCollaborator(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_add_collaborator() {
|
||||||
|
AddCollaboratorReq req = new AddCollaboratorReq();
|
||||||
|
req.setTaskListId("tl-1");
|
||||||
|
req.setUserId("u-9");
|
||||||
|
req.setPermission("2");
|
||||||
|
// selectCount 调 2 次:ownerCheck=1(通过)+ existCheck=0(不存在则新增)
|
||||||
|
when(taskListPermissionMapper.selectCount(any())).thenReturn(1L, 0L);
|
||||||
|
TaskList taskList = new TaskList();
|
||||||
|
taskList.setSecretLevel(1);
|
||||||
|
when(taskListMapper.selectById("tl-1")).thenReturn(taskList);
|
||||||
|
LoginUser target = new LoginUser();
|
||||||
|
target.setUserSecurityLevel(3); // 密级须 > 清单密级(1) 才能添加
|
||||||
|
when(sysBaseAPI.getUserById("u-9")).thenReturn(target);
|
||||||
|
|
||||||
|
service.addCollaborator(req);
|
||||||
|
|
||||||
|
verify(taskListPermissionMapper).insert(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+259
@@ -0,0 +1,259 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.flow;
|
||||||
|
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
|
import org.jeecg.modules.supervision.common.OrgConfig;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakFeedbackConfig;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
|
||||||
|
import org.jeecg.modules.tasktask.entity.TaskTask;
|
||||||
|
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XiSpeakFeedbackFlow 反馈流程表达式测试(mock 外部依赖)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class XiSpeakFeedbackFlowTest {
|
||||||
|
|
||||||
|
@Mock private XiSpeakFeedbackConfig xiSpeakFeedbackConfig;
|
||||||
|
@Mock private OrgConfig orgConfig;
|
||||||
|
@Mock private ISysBaseAPI iSysBaseAPI;
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
@Mock private IBgXiSpeakService bgXiSpeakService;
|
||||||
|
@Mock private ITaskTaskService taskTaskService;
|
||||||
|
@Mock private DelegateExecution execution;
|
||||||
|
|
||||||
|
private XiSpeakFeedbackFlow flow;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
OrgConfig.Dept dept = new OrgConfig.Dept();
|
||||||
|
dept.setJj("dept-jj");
|
||||||
|
OrgConfig.Role role = new OrgConfig.Role();
|
||||||
|
role.setJjWorker("role-jj-worker");
|
||||||
|
role.setLd("role-ld");
|
||||||
|
lenient().when(orgConfig.getDept()).thenReturn(dept);
|
||||||
|
lenient().when(orgConfig.getRole()).thenReturn(role);
|
||||||
|
flow = new XiSpeakFeedbackFlow(xiSpeakFeedbackConfig, orgConfig, iSysBaseAPI, runtimeService, bgXiSpeakService, taskTaskService);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== getImplDeptList ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ImplDeptList {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_keys_missing() {
|
||||||
|
when(execution.getProcessInstanceId()).thenReturn("proc-1");
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("");
|
||||||
|
assertEquals(List.of(), flow.getImplDeptList(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_entity_not_found() {
|
||||||
|
when(execution.getProcessInstanceId()).thenReturn("proc-1");
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("biz-1");
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(null);
|
||||||
|
assertEquals(List.of(), flow.getImplDeptList(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_approve_info_empty() {
|
||||||
|
when(execution.getProcessInstanceId()).thenReturn("proc-1");
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("biz-1");
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
assertEquals(List.of(), flow.getImplDeptList(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_dept_keys() {
|
||||||
|
when(execution.getProcessInstanceId()).thenReturn("proc-1");
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("biz-1");
|
||||||
|
DeptApproveDetailMap map = new DeptApproveDetailMap();
|
||||||
|
map.put("dept-1", new DeptApproveDetail());
|
||||||
|
map.put("dept-2", new DeptApproveDetail());
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setApproveInfo(map);
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
assertEquals(List.of("dept-1", "dept-2"), flow.getImplDeptList(execution));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== getImplDeptApprove ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ImplDeptApprove {
|
||||||
|
|
||||||
|
private TaskTask taskWithHandler(String handler) {
|
||||||
|
TaskTask tt = new TaskTask();
|
||||||
|
tt.setBusinessId("xi-biz");
|
||||||
|
tt.setDeptHandlerName(handler);
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_business_key_blank() {
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn(" ");
|
||||||
|
assertEquals("", flow.getImplDeptApprove(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_task_missing() {
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("tt-1");
|
||||||
|
when(taskTaskService.getById("tt-1")).thenReturn(null);
|
||||||
|
assertEquals("", flow.getImplDeptApprove(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_matched_approver_name() {
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("tt-1");
|
||||||
|
when(taskTaskService.getById("tt-1")).thenReturn(taskWithHandler("worker1"));
|
||||||
|
DeptApproveDetail detail = new DeptApproveDetail();
|
||||||
|
detail.setImplUserNameList(List.of("worker1"));
|
||||||
|
detail.setApproverName("leader1");
|
||||||
|
DeptApproveDetailMap map = new DeptApproveDetailMap();
|
||||||
|
map.put("dept-1", detail);
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setApproveInfo(map);
|
||||||
|
when(bgXiSpeakService.getById("xi-biz")).thenReturn(entity);
|
||||||
|
|
||||||
|
assertEquals("leader1", flow.getImplDeptApprove(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_no_match() {
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("tt-1");
|
||||||
|
when(taskTaskService.getById("tt-1")).thenReturn(taskWithHandler("nobody"));
|
||||||
|
DeptApproveDetail detail = new DeptApproveDetail();
|
||||||
|
detail.setImplUserNameList(List.of("worker1"));
|
||||||
|
detail.setApproverName("leader1");
|
||||||
|
DeptApproveDetailMap map = new DeptApproveDetailMap();
|
||||||
|
map.put("dept-1", detail);
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setApproveInfo(map);
|
||||||
|
when(bgXiSpeakService.getById("xi-biz")).thenReturn(entity);
|
||||||
|
|
||||||
|
assertEquals("", flow.getImplDeptApprove(execution));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 部门审批详情(经 taskTask 链路) ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class DeptDetailByTask {
|
||||||
|
|
||||||
|
private void mockDetailChain(String deptId, DeptApproveDetail detail) {
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("tt-1");
|
||||||
|
TaskTask tt = new TaskTask();
|
||||||
|
tt.setBusinessId("xi-biz");
|
||||||
|
tt.setDeptId(deptId);
|
||||||
|
when(taskTaskService.getById("tt-1")).thenReturn(tt);
|
||||||
|
DeptApproveDetailMap map = new DeptApproveDetailMap();
|
||||||
|
map.put(deptId, detail);
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setApproveInfo(map);
|
||||||
|
when(bgXiSpeakService.getById("xi-biz")).thenReturn(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_zero_when_detail_missing() {
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("tt-1");
|
||||||
|
when(taskTaskService.getById("tt-1")).thenReturn(new TaskTask());
|
||||||
|
assertEquals(0, flow.getImplDeptIsEnd(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_read_is_end() {
|
||||||
|
DeptApproveDetail detail = new DeptApproveDetail();
|
||||||
|
detail.setIsEnd(1);
|
||||||
|
mockDetailChain("dept-1", detail);
|
||||||
|
assertEquals(1, flow.getImplDeptIsEnd(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_read_impl_worker() {
|
||||||
|
DeptApproveDetail detail = new DeptApproveDetail();
|
||||||
|
detail.setImplWorker("w1");
|
||||||
|
mockDetailChain("dept-1", detail);
|
||||||
|
assertEquals("w1", flow.getImplLeaderAsWorker(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_worker_blank() {
|
||||||
|
mockDetailChain("dept-1", new DeptApproveDetail());
|
||||||
|
assertEquals("", flow.getImplLeaderAsWorker(execution));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== getImplDeptWorker ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ImplDeptWorker {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_business_key_blank() {
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn(" ");
|
||||||
|
assertEquals(List.of(), flow.getImplDeptWorker(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_task_missing() {
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("tt-1");
|
||||||
|
when(taskTaskService.getById("tt-1")).thenReturn(null);
|
||||||
|
assertEquals(List.of(), flow.getImplDeptWorker(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_split_worker_names() {
|
||||||
|
when(execution.getProcessInstanceBusinessKey()).thenReturn("tt-1");
|
||||||
|
TaskTask tt = new TaskTask();
|
||||||
|
tt.setBusinessId("xi-biz");
|
||||||
|
tt.setDeptHandlerName("张三, 李四 ,,王五");
|
||||||
|
when(taskTaskService.getById("tt-1")).thenReturn(tt);
|
||||||
|
assertEquals(List.of("张三", "李四", "王五"), flow.getImplDeptWorker(execution));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== JJ 部门查询 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class JJDeptQuery {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_jj_workers() {
|
||||||
|
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-jj-worker"))
|
||||||
|
.thenReturn(List.of("jj1", "jj2"));
|
||||||
|
assertEquals(List.of("jj1", "jj2"), flow.getJJDeptWorkerList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_jj_leaders() {
|
||||||
|
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-ld"))
|
||||||
|
.thenReturn(List.of("ld1"));
|
||||||
|
assertEquals(List.of("ld1"), flow.getJJDeptLeaderList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_no_worker() {
|
||||||
|
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-jj-worker"))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
assertEquals(0, flow.getJJDeptWorkerListLength());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+300
@@ -0,0 +1,300 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.flow;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
|
import org.jeecg.modules.extbpm.process.common.expression.FlowNodeExpression;
|
||||||
|
import org.jeecg.modules.supervision.common.OrgConfig;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig.XiSpeakProperties;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XiSpeakFlow 流程表达式测试(mock config/orgConfig/iSysBaseAPI/flowNodeExpression/service)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class XiSpeakFlowTest {
|
||||||
|
|
||||||
|
@Mock private XiSpeakConfig xiSpeakConfig;
|
||||||
|
@Mock private OrgConfig orgConfig;
|
||||||
|
@Mock private ISysBaseAPI iSysBaseAPI;
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
@Mock private FlowNodeExpression flowNodeExpression;
|
||||||
|
@Mock private IBgXiSpeakService bgXiSpeakService;
|
||||||
|
@Mock private DelegateExecution execution;
|
||||||
|
|
||||||
|
private XiSpeakFlow flow;
|
||||||
|
private XiSpeakProperties props;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
props = new XiSpeakProperties();
|
||||||
|
props.setNeedSdwApproveCode("need_sdw_approval");
|
||||||
|
props.setFeedbackRightNowCode("feedback_right_now");
|
||||||
|
props.setSdwLeaderListKey("sdw_leader_list");
|
||||||
|
props.setTemImplWorkerKey("tem_impl_worker");
|
||||||
|
props.setTemImplLeaderKey("tem_impl_leader");
|
||||||
|
props.setTemBgLeaderKey("tem_bg_leader");
|
||||||
|
props.setIsEndKey("is_end");
|
||||||
|
props.setBusinessKey("business_id");
|
||||||
|
props.setImplDeptCollectionUsedKey("impl_dept");
|
||||||
|
props.setDeptWorkerKey("dept_worker");
|
||||||
|
props.setImplDeptKey("impl_dept");
|
||||||
|
// lenient:部分测试(getListSize/getImplDeptNums 等)不触碰 config/orgConfig
|
||||||
|
lenient().when(xiSpeakConfig.getXiSpeak()).thenReturn(props);
|
||||||
|
|
||||||
|
OrgConfig.Dept dept = new OrgConfig.Dept();
|
||||||
|
dept.setBg("dept-bg");
|
||||||
|
OrgConfig.Role role = new OrgConfig.Role();
|
||||||
|
role.setLd("role-ld");
|
||||||
|
role.setSdw("role-sdw");
|
||||||
|
lenient().when(orgConfig.getDept()).thenReturn(dept);
|
||||||
|
lenient().when(orgConfig.getRole()).thenReturn(role);
|
||||||
|
|
||||||
|
flow = new XiSpeakFlow(xiSpeakConfig, orgConfig, iSysBaseAPI, runtimeService, flowNodeExpression, bgXiSpeakService);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 部门角色查询 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class DeptRoleQuery {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_bg_dept_ld_users() {
|
||||||
|
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-bg", "role-ld"))
|
||||||
|
.thenReturn(List.of("u1", "u2"));
|
||||||
|
assertEquals(List.of("u1", "u2"), flow.getBgDeptLdUserIdList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_sdw_leaders() {
|
||||||
|
when(iSysBaseAPI.getUserByRoleIdLocalApi("role-sdw")).thenReturn(List.of("sdw1"));
|
||||||
|
assertEquals(List.of("sdw1"), flow.getSDWLeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_impl_dept_lds() {
|
||||||
|
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-x", "role-ld"))
|
||||||
|
.thenReturn(List.of("ld1"));
|
||||||
|
assertEquals(List.of("ld1"), flow.getImplDeptLdsList("dept-x"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== JSON Integer 解析 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class JsonIntegerParse {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_parse_int_from_json_object() {
|
||||||
|
assertEquals(1, flow.getNeedSdwApproval(new JSONObject().fluentPut("need_sdw_approval", 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_parse_int_from_json_string() {
|
||||||
|
assertEquals(1, flow.getNeedSdwApproval("{\"need_sdw_approval\":1}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_zero_when_json_null() {
|
||||||
|
assertEquals(0, flow.getNeedSdwApproval(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_zero_when_key_missing() {
|
||||||
|
assertEquals(0, flow.getFeedBackRightNow(new JSONObject().fluentPut("other", 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_zero_when_invalid_json() {
|
||||||
|
assertEquals(0, flow.getNeedSdwApproval("not-json"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== JSON List 解析 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class JsonListParse {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_split_list_from_json_object() {
|
||||||
|
assertEquals(List.of("sdw1", "sdw2"),
|
||||||
|
flow.getSdwLeaderList(new JSONObject().fluentPut("sdw_leader_list", "sdw1,sdw2")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_trim_tokens() {
|
||||||
|
assertEquals(List.of("sdw1", "sdw2"),
|
||||||
|
flow.getSdwLeaderList(new JSONObject().fluentPut("sdw_leader_list", " sdw1 , sdw2 ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_when_json_null() {
|
||||||
|
assertEquals(List.of(), flow.getSdwLeaderList(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== flowNodeExpression 转发 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FlowNodeForward {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_forward_impl_worker_list() {
|
||||||
|
when(flowNodeExpression.getSonProcessHqVariableList(execution, "tem_impl_worker"))
|
||||||
|
.thenReturn(List.of("w1"));
|
||||||
|
assertEquals(List.of("w1"), flow.getTemImplWorkerList(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_forward_impl_leader_list() {
|
||||||
|
when(flowNodeExpression.getSonProcessHqVariableList(execution, "tem_impl_leader"))
|
||||||
|
.thenReturn(List.of("l1", "l2"));
|
||||||
|
assertEquals(2, flow.getTemImplLeaderListLength(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_forward_bg_leader_list() {
|
||||||
|
when(flowNodeExpression.getSonProcessHqVariableList(execution, "tem_bg_leader"))
|
||||||
|
.thenReturn(List.of("b1"));
|
||||||
|
assertEquals(List.of("b1"), flow.getTemBgLeaderList(execution));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 部门审批详情读取 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class DeptApproveDetailRead {
|
||||||
|
|
||||||
|
private void mockEntityWithDetail(Integer isEnd, String implWorker, List<String> workers) {
|
||||||
|
when(execution.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(execution.getVariableLocal("impl_dept")).thenReturn("[dept-1]");
|
||||||
|
DeptApproveDetail detail = new DeptApproveDetail();
|
||||||
|
detail.setIsEnd(isEnd);
|
||||||
|
detail.setImplWorker(implWorker);
|
||||||
|
detail.setImplUserNameList(workers);
|
||||||
|
DeptApproveDetailMap map = new DeptApproveDetailMap();
|
||||||
|
map.put("dept-1", detail);
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setApproveInfo(map);
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_read_is_end_from_detail() {
|
||||||
|
mockEntityWithDetail(1, "w1", null);
|
||||||
|
assertEquals("1", flow.getImplDeptLeaderIsEnd(execution));
|
||||||
|
assertEquals(1, flow.getImplDeptIsEnd(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_read_impl_worker_from_detail() {
|
||||||
|
mockEntityWithDetail(1, "w1", null);
|
||||||
|
assertEquals("w1", flow.getImplDeptLeader(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_read_worker_list_from_detail() {
|
||||||
|
mockEntityWithDetail(0, null, List.of("w1", "w2"));
|
||||||
|
assertEquals(List.of("w1", "w2"), flow.getImplDeptWorkerListFromJson(execution));
|
||||||
|
assertEquals(List.of("w1", "w2"), flow.getDeptWorkerList(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_fallback_to_variable_when_no_detail() {
|
||||||
|
// 无 detail:isEnd 从流程变量兜底(兼容旧流程)
|
||||||
|
// ⚠️ 现状行为:getSonProcessVariable 返回 List<String>(splitUsernames),
|
||||||
|
// value.toString() 得到 "[1]" 而非 "1";getImplDeptIsEnd 的 parseInt("[1]") 失败返回 0。
|
||||||
|
// 测试锁定现状,若后续修复 isEnd 兜底解析需同步更新。
|
||||||
|
when(execution.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(execution.getVariableLocal("impl_dept")).thenReturn("[dept-1]");
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setApproveInfo(new DeptApproveDetailMap());
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
when(flowNodeExpression.getSonProcessVariable(execution, "is_end")).thenReturn(List.of("1"));
|
||||||
|
|
||||||
|
assertEquals("[1]", flow.getImplDeptLeaderIsEnd(execution));
|
||||||
|
assertEquals(0, flow.getImplDeptIsEnd(execution));
|
||||||
|
assertEquals("", flow.getImplDeptLeader(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_list_to_string_when_variable_empty() {
|
||||||
|
// getSonProcessVariable 从不返回 null(空时返回 emptyList),value.toString() = "[]"
|
||||||
|
when(execution.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(execution.getVariableLocal("impl_dept")).thenReturn("[dept-1]");
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setApproveInfo(new DeptApproveDetailMap());
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
when(flowNodeExpression.getSonProcessVariable(execution, "is_end")).thenReturn(List.of());
|
||||||
|
|
||||||
|
assertEquals("[]", flow.getImplDeptLeaderIsEnd(execution));
|
||||||
|
assertEquals(0, flow.getImplDeptIsEnd(execution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_zero_when_is_end_not_number() {
|
||||||
|
when(execution.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(execution.getVariableLocal("impl_dept")).thenReturn("[dept-1]");
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setApproveInfo(new DeptApproveDetailMap());
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
when(flowNodeExpression.getSonProcessVariable(execution, "is_end")).thenReturn(List.of("abc"));
|
||||||
|
|
||||||
|
assertEquals(0, flow.getImplDeptIsEnd(execution));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== implDeptNums ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ImplDeptNums {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_count_json_array_value() {
|
||||||
|
assertEquals(2, flow.getImplDeptNums("{\"impl_dept\":[\"d1\",\"d2\"]}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_count_comma_string_value() {
|
||||||
|
assertEquals(3, flow.getImplDeptNums("{\"impl_dept\":\"d1,d2,d3\"}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_zero_when_json_null() {
|
||||||
|
assertEquals(0, flow.getImplDeptNums(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== getListSize ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ListSize {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_count_tokens() {
|
||||||
|
assertEquals(3, flow.getListSize("a,b,c"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_zero_when_blank() {
|
||||||
|
assertEquals(0, flow.getListSize(" "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+128
@@ -0,0 +1,128 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.listener;
|
||||||
|
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.flowable.engine.runtime.Execution;
|
||||||
|
import org.flowable.engine.runtime.ExecutionQuery;
|
||||||
|
import org.flowable.task.service.delegate.DelegateTask;
|
||||||
|
import org.jeecg.modules.extbpm.process.common.expression.FlowNodeExpression;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig.XiSpeakProperties;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
|
||||||
|
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AfterBgLeaderApproveListener 测试:将 bg 领导写入上两级执行实例局部变量。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AfterBgLeaderApproveListenerTest {
|
||||||
|
|
||||||
|
@Mock private FlowNodeExpression flowNodeExpression;
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
@Mock private XiSpeakConfig xiSpeakConfig;
|
||||||
|
@Mock private ITaskTaskService taskTaskService;
|
||||||
|
@Mock private IBgXiSpeakService bgXiSpeakService;
|
||||||
|
@Mock private DelegateTask delegateTask;
|
||||||
|
@Mock private Execution currentExecution;
|
||||||
|
@Mock private Execution parentExecution;
|
||||||
|
@Mock private ExecutionQuery query;
|
||||||
|
@Mock private ExecutionQuery parentQuery;
|
||||||
|
|
||||||
|
private AfterBgLeaderApproveListener listener;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
XiSpeakProperties props = new XiSpeakProperties();
|
||||||
|
props.setTemBgLeaderKey("tem_bg_leader");
|
||||||
|
when(xiSpeakConfig.getXiSpeak()).thenReturn(props);
|
||||||
|
listener = new AfterBgLeaderApproveListener(flowNodeExpression, runtimeService, xiSpeakConfig, taskTaskService, bgXiSpeakService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockExecutionChain(String parentId, String grandParentId) {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(runtimeService.createExecutionQuery()).thenReturn(query, parentQuery);
|
||||||
|
when(query.executionId("exec-1")).thenReturn(query);
|
||||||
|
when(query.singleResult()).thenReturn(currentExecution);
|
||||||
|
when(currentExecution.getParentId()).thenReturn(parentId);
|
||||||
|
when(parentQuery.executionId(parentId)).thenReturn(parentQuery);
|
||||||
|
when(parentQuery.singleResult()).thenReturn(parentExecution);
|
||||||
|
when(parentExecution.getParentId()).thenReturn(grandParentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_when_task_null() {
|
||||||
|
listener.notify(null);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_assignee_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn(" ");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_execution_id_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_current_execution_not_found() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(runtimeService.createExecutionQuery()).thenReturn(query);
|
||||||
|
when(query.executionId("exec-1")).thenReturn(query);
|
||||||
|
when(query.singleResult()).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_parent_id_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(runtimeService.createExecutionQuery()).thenReturn(query);
|
||||||
|
when(query.executionId("exec-1")).thenReturn(query);
|
||||||
|
when(query.singleResult()).thenReturn(currentExecution);
|
||||||
|
when(currentExecution.getParentId()).thenReturn(" ");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_write_to_grand_parent_when_both_levels_exist() {
|
||||||
|
mockExecutionChain("parent-1", "grand-1");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService).setVariableLocal("grand-1", "tem_bg_leader", "leader1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_write_to_parent_when_no_grand_parent() {
|
||||||
|
mockExecutionChain("parent-1", null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService).setVariableLocal("parent-1", "tem_bg_leader", "leader1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_set_variable_fails() {
|
||||||
|
// 与 fixcontact 不同:xispeak 监听器 catch 后重新抛出 RuntimeException(写入失败即中断流程)
|
||||||
|
mockExecutionChain("parent-1", "grand-1");
|
||||||
|
doThrow(new RuntimeException("boom")).when(runtimeService).setVariableLocal("grand-1", "tem_bg_leader", "leader1");
|
||||||
|
org.junit.jupiter.api.Assertions.assertThrows(RuntimeException.class, () -> listener.notify(delegateTask));
|
||||||
|
}
|
||||||
|
}
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.listener;
|
||||||
|
|
||||||
|
import org.flowable.task.service.delegate.DelegateTask;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig.XiSpeakProperties;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.constant.XiSpeakConstant;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AfterBgWorkerFinalApproveListener 测试:最终审批将业务表单 completionStatus 置为已完成。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AfterBgWorkerFinalApproveListenerTest {
|
||||||
|
|
||||||
|
@Mock private XiSpeakConfig xiSpeakConfig;
|
||||||
|
@Mock private IBgXiSpeakService bgXiSpeakService;
|
||||||
|
@Mock private DelegateTask delegateTask;
|
||||||
|
|
||||||
|
private AfterBgWorkerFinalApproveListener listener;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
XiSpeakProperties props = new XiSpeakProperties();
|
||||||
|
props.setBusinessKey("business_id");
|
||||||
|
when(xiSpeakConfig.getXiSpeak()).thenReturn(props);
|
||||||
|
listener = new AfterBgWorkerFinalApproveListener(xiSpeakConfig, bgXiSpeakService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_business_key_missing() {
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_update_completion_status_finished() {
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(bgXiSpeakService.updateById(any())).thenReturn(true);
|
||||||
|
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
|
||||||
|
ArgumentCaptor<BgXiSpeak> captor = ArgumentCaptor.forClass(BgXiSpeak.class);
|
||||||
|
verify(bgXiSpeakService).updateById(captor.capture());
|
||||||
|
assertEquals("biz-1", captor.getValue().getId());
|
||||||
|
assertEquals(XiSpeakConstant.CompletionStatus.FINISHED, captor.getValue().getCompletionStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.listener;
|
||||||
|
|
||||||
|
import org.flowable.task.service.delegate.DelegateTask;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig.XiSpeakProperties;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakFeedbackConfig;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakFeedbackService;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
|
||||||
|
import org.jeecg.modules.tasktask.service.ITaskTaskService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AfterImplDeptLeaderApproveFeedbackListener 测试(反馈流程部门领导审批)。
|
||||||
|
* 规则:将部门领导写入 approveInfo 对应部门的 approverName(幂等)。
|
||||||
|
* parseDeptId 语义:Collection 取第一个;String 去 [] 保留全部(与 StoreJson 版不同)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AfterImplDeptLeaderApproveFeedbackListenerTest {
|
||||||
|
|
||||||
|
@Mock private XiSpeakFeedbackConfig xiSpeakFeedbackConfig;
|
||||||
|
@Mock private IBgXiSpeakFeedbackService bgXiSpeakFeedbackService;
|
||||||
|
@Mock private ITaskTaskService taskTaskService;
|
||||||
|
@Mock private XiSpeakConfig xiSpeakConfig;
|
||||||
|
@Mock private IBgXiSpeakService bgXiSpeakService;
|
||||||
|
@Mock private DelegateTask delegateTask;
|
||||||
|
|
||||||
|
private AfterImplDeptLeaderApproveFeedbackListener listener;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
XiSpeakProperties props = new XiSpeakProperties();
|
||||||
|
props.setBusinessKey("business_id");
|
||||||
|
props.setImplDeptCollectionUsedKey("impl_dept");
|
||||||
|
when(xiSpeakConfig.getXiSpeak()).thenReturn(props);
|
||||||
|
listener = new AfterImplDeptLeaderApproveFeedbackListener(
|
||||||
|
xiSpeakFeedbackConfig, bgXiSpeakFeedbackService, taskTaskService, xiSpeakConfig, bgXiSpeakService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_assignee_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn(" ");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_business_key_null() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_entity_not_found() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_store_approver_name_when_changed() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(delegateTask.getVariableLocal("impl_dept")).thenReturn("[dept-1]");
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
|
||||||
|
// parseDeptId("[dept-1]"):String 形式去 [] 保留全部 → "dept-1"
|
||||||
|
assertEquals("leader1", entity.getApproveInfo().get("dept-1").getApproverName());
|
||||||
|
verify(bgXiSpeakService).updateById(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_update_when_approver_name_unchanged() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(delegateTask.getVariableLocal("impl_dept")).thenReturn("[dept-1]");
|
||||||
|
DeptApproveDetail detail = new DeptApproveDetail();
|
||||||
|
detail.setApproverName("leader1");
|
||||||
|
DeptApproveDetailMap map = new DeptApproveDetailMap();
|
||||||
|
map.put("dept-1", detail);
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
entity.setApproveInfo(map);
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_parse_first_element_from_collection() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(delegateTask.getVariableLocal("impl_dept")).thenReturn(List.of("dept-a", "dept-b"));
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
|
||||||
|
assertEquals("leader1", entity.getApproveInfo().get("dept-a").getApproverName());
|
||||||
|
}
|
||||||
|
}
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.listener;
|
||||||
|
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.flowable.engine.runtime.Execution;
|
||||||
|
import org.flowable.engine.runtime.ExecutionQuery;
|
||||||
|
import org.flowable.task.service.delegate.DelegateTask;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AfterImplDeptLeaderApproveListener 测试:将部门领导写入上两级执行实例局部变量(tem_impl_leader)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AfterImplDeptLeaderApproveListenerTest {
|
||||||
|
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
@Mock private DelegateTask delegateTask;
|
||||||
|
@Mock private Execution currentExecution;
|
||||||
|
@Mock private Execution parentExecution;
|
||||||
|
@Mock private ExecutionQuery query;
|
||||||
|
@Mock private ExecutionQuery parentQuery;
|
||||||
|
|
||||||
|
private AfterImplDeptLeaderApproveListener listener;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
listener = new AfterImplDeptLeaderApproveListener(runtimeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockExecutionChain(String parentId, String grandParentId) {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(runtimeService.createExecutionQuery()).thenReturn(query, parentQuery);
|
||||||
|
when(query.executionId("exec-1")).thenReturn(query);
|
||||||
|
when(query.singleResult()).thenReturn(currentExecution);
|
||||||
|
when(currentExecution.getParentId()).thenReturn(parentId);
|
||||||
|
when(parentQuery.executionId(parentId)).thenReturn(parentQuery);
|
||||||
|
when(parentQuery.singleResult()).thenReturn(parentExecution);
|
||||||
|
when(parentExecution.getParentId()).thenReturn(grandParentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_when_task_null() {
|
||||||
|
listener.notify(null);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_assignee_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn(" ");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_execution_id_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_current_execution_not_found() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(runtimeService.createExecutionQuery()).thenReturn(query);
|
||||||
|
when(query.executionId("exec-1")).thenReturn(query);
|
||||||
|
when(query.singleResult()).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_parent_id_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(runtimeService.createExecutionQuery()).thenReturn(query);
|
||||||
|
when(query.executionId("exec-1")).thenReturn(query);
|
||||||
|
when(query.singleResult()).thenReturn(currentExecution);
|
||||||
|
when(currentExecution.getParentId()).thenReturn(" ");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_write_to_grand_parent_when_both_levels_exist() {
|
||||||
|
mockExecutionChain("parent-1", "grand-1");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService).setVariableLocal("grand-1", "tem_impl_leader", "leader1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_write_to_parent_when_no_grand_parent() {
|
||||||
|
mockExecutionChain("parent-1", null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService).setVariableLocal("parent-1", "tem_impl_leader", "leader1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_set_variable_fails() {
|
||||||
|
// 与 fixcontact 不同:xispeak 监听器 catch 后重新抛出 RuntimeException(写入失败即中断流程)
|
||||||
|
mockExecutionChain("parent-1", "grand-1");
|
||||||
|
doThrow(new RuntimeException("boom")).when(runtimeService).setVariableLocal("grand-1", "tem_impl_leader", "leader1");
|
||||||
|
org.junit.jupiter.api.Assertions.assertThrows(RuntimeException.class, () -> listener.notify(delegateTask));
|
||||||
|
}
|
||||||
|
}
|
||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.listener;
|
||||||
|
|
||||||
|
import org.flowable.task.service.delegate.DelegateTask;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig.XiSpeakProperties;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AfterImplDeptLeaderStoreJsonListener 测试。
|
||||||
|
* 规则:将部门领导写入 approveInfo 对应部门的 approverName(幂等:值相同跳过更新)。
|
||||||
|
* parseDeptId 语义:取第一个部门 ID(与 FlowVariableSupport.cleanDeptId 不同,保留单独实现)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AfterImplDeptLeaderStoreJsonListenerTest {
|
||||||
|
|
||||||
|
@Mock private XiSpeakConfig xiSpeakConfig;
|
||||||
|
@Mock private IBgXiSpeakService bgXiSpeakService;
|
||||||
|
@Mock private DelegateTask delegateTask;
|
||||||
|
|
||||||
|
private AfterImplDeptLeaderStoreJsonListener listener;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
XiSpeakProperties props = new XiSpeakProperties();
|
||||||
|
props.setBusinessKey("business_id");
|
||||||
|
props.setImplDeptCollectionUsedKey("impl_dept");
|
||||||
|
when(xiSpeakConfig.getXiSpeak()).thenReturn(props);
|
||||||
|
listener = new AfterImplDeptLeaderStoreJsonListener(xiSpeakConfig, bgXiSpeakService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_assignee_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn(" ");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_business_key_null() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_entity_not_found() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_dept_var_missing() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(new BgXiSpeak());
|
||||||
|
when(delegateTask.getVariableLocal("impl_dept")).thenReturn(null);
|
||||||
|
when(delegateTask.getVariable("impl_dept")).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_store_approver_name_when_changed() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(delegateTask.getVariableLocal("impl_dept")).thenReturn("[dept-1]");
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
|
||||||
|
// parseDeptId("[dept-1]"):字符串形式,取第一个元素 → "dept-1"
|
||||||
|
DeptApproveDetail detail = entity.getApproveInfo().get("dept-1");
|
||||||
|
assertEquals("leader1", detail.getApproverName());
|
||||||
|
verify(bgXiSpeakService).updateById(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_update_when_approver_name_unchanged() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(delegateTask.getVariableLocal("impl_dept")).thenReturn("[dept-1]");
|
||||||
|
DeptApproveDetail detail = new DeptApproveDetail();
|
||||||
|
detail.setDeptId("dept-1");
|
||||||
|
detail.setApproverName("leader1");
|
||||||
|
DeptApproveDetailMap map = new DeptApproveDetailMap();
|
||||||
|
map.put("dept-1", detail);
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
entity.setApproveInfo(map);
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_parse_first_element_from_list_like_string() {
|
||||||
|
// 字符串 "[d1,d2]":只取第一个元素 d1(与 cleanDeptId 保留全部不同)
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(delegateTask.getVariableLocal("impl_dept")).thenReturn("[d1,d2]");
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
|
||||||
|
assertEquals("leader1", entity.getApproveInfo().get("d1").getApproverName());
|
||||||
|
assertEquals(1, entity.getApproveInfo().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_parse_first_element_from_collection() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("leader1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(delegateTask.getVariableLocal("impl_dept")).thenReturn(List.of("dept-a", "dept-b"));
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
when(bgXiSpeakService.getById("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
|
||||||
|
assertEquals("leader1", entity.getApproveInfo().get("dept-a").getApproverName());
|
||||||
|
}
|
||||||
|
}
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.listener;
|
||||||
|
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.flowable.task.service.delegate.DelegateTask;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig.XiSpeakProperties;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AfterImplWorkerApproveListener 经办人会签监听器测试。
|
||||||
|
* 规则:写子流程局部变量 tem_impl_worker + 记录经办人到 approveInfo 对应部门(去重)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AfterImplWorkerApproveListenerTest {
|
||||||
|
|
||||||
|
@Mock private XiSpeakConfig xiSpeakConfig;
|
||||||
|
@Mock private IBgXiSpeakService bgXiSpeakService;
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
@Mock private DelegateTask delegateTask;
|
||||||
|
|
||||||
|
private AfterImplWorkerApproveListener listener;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
XiSpeakProperties props = new XiSpeakProperties();
|
||||||
|
props.setBusinessKey("business_id");
|
||||||
|
props.setTemImplWorkerKey("tem_impl_worker");
|
||||||
|
props.setImplDeptCollectionUsedKey("impl_dept");
|
||||||
|
// lenient:notify(null) 用例在 NPE 前不会用到 config
|
||||||
|
lenient().when(xiSpeakConfig.getXiSpeak()).thenReturn(props);
|
||||||
|
listener = new AfterImplWorkerApproveListener(xiSpeakConfig, bgXiSpeakService, runtimeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockNormalTask(String deptVar) {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("worker1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
// lenient:getVariableLocal 在 getByIdForUpdate 之后才调用,entity 为 null 时不会走到(短路径)
|
||||||
|
lenient().when(delegateTask.getVariableLocal("impl_dept")).thenReturn(deptVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_npe_when_task_null() {
|
||||||
|
// 现状行为:该监听器没有 null 防御(与 FixContactWorkerApproveListener 不同),notify(null) 抛 NPE
|
||||||
|
org.junit.jupiter.api.Assertions.assertThrows(NullPointerException.class, () -> listener.notify(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_assignee_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn(" ");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_record_when_business_key_missing() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("worker1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_record_when_dept_var_missing() {
|
||||||
|
// deptVar 读取在 getByIdForUpdate 之后:须先返回 entity 才能走到 deptVar 检查
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("worker1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(delegateTask.getVariable("business_id")).thenReturn("biz-1");
|
||||||
|
when(bgXiSpeakService.getByIdForUpdate("biz-1")).thenReturn(new BgXiSpeak());
|
||||||
|
when(delegateTask.getVariableLocal("impl_dept")).thenReturn(null);
|
||||||
|
when(delegateTask.getVariable("impl_dept")).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_record_when_entity_not_found() {
|
||||||
|
mockNormalTask("dept-1");
|
||||||
|
when(bgXiSpeakService.getByIdForUpdate("biz-1")).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_write_var_and_record_worker() {
|
||||||
|
mockNormalTask("dept-1");
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
entity.setApproveInfo(new DeptApproveDetailMap());
|
||||||
|
when(bgXiSpeakService.getByIdForUpdate("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
|
||||||
|
verify(runtimeService).setVariableLocal("exec-1", "tem_impl_worker", "worker1");
|
||||||
|
assertEquals(List.of("worker1"), entity.getApproveInfo().get("dept-1").getImplUserNameList());
|
||||||
|
verify(bgXiSpeakService).updateById(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_update_when_worker_already_in_list() {
|
||||||
|
mockNormalTask("dept-1");
|
||||||
|
DeptApproveDetail detail = new DeptApproveDetail();
|
||||||
|
detail.setImplUserNameList(new ArrayList<>(List.of("worker1")));
|
||||||
|
DeptApproveDetailMap map = new DeptApproveDetailMap();
|
||||||
|
map.put("dept-1", detail);
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
entity.setApproveInfo(map);
|
||||||
|
when(bgXiSpeakService.getByIdForUpdate("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
|
||||||
|
verify(bgXiSpeakService, never()).updateById(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.listener;
|
||||||
|
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.flowable.engine.runtime.Execution;
|
||||||
|
import org.flowable.engine.runtime.ExecutionQuery;
|
||||||
|
import org.flowable.task.service.delegate.DelegateTask;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AfterImplWorkerTemStoreListener 测试:将部门经办人写入上两级执行实例局部变量(tem_impl_worker)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AfterImplWorkerTemStoreListenerTest {
|
||||||
|
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
@Mock private DelegateTask delegateTask;
|
||||||
|
@Mock private Execution currentExecution;
|
||||||
|
@Mock private Execution parentExecution;
|
||||||
|
@Mock private ExecutionQuery query;
|
||||||
|
@Mock private ExecutionQuery parentQuery;
|
||||||
|
|
||||||
|
private AfterImplWorkerTemStoreListener listener;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
listener = new AfterImplWorkerTemStoreListener(runtimeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockExecutionChain(String parentId, String grandParentId) {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("worker1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(runtimeService.createExecutionQuery()).thenReturn(query, parentQuery);
|
||||||
|
when(query.executionId("exec-1")).thenReturn(query);
|
||||||
|
when(query.singleResult()).thenReturn(currentExecution);
|
||||||
|
when(currentExecution.getParentId()).thenReturn(parentId);
|
||||||
|
when(parentQuery.executionId(parentId)).thenReturn(parentQuery);
|
||||||
|
when(parentQuery.singleResult()).thenReturn(parentExecution);
|
||||||
|
when(parentExecution.getParentId()).thenReturn(grandParentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_when_task_null() {
|
||||||
|
listener.notify(null);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_assignee_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn(" ");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_execution_id_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("worker1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_current_execution_not_found() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("worker1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(runtimeService.createExecutionQuery()).thenReturn(query);
|
||||||
|
when(query.executionId("exec-1")).thenReturn(query);
|
||||||
|
when(query.singleResult()).thenReturn(null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_when_parent_id_blank() {
|
||||||
|
when(delegateTask.getAssignee()).thenReturn("worker1");
|
||||||
|
when(delegateTask.getExecutionId()).thenReturn("exec-1");
|
||||||
|
when(runtimeService.createExecutionQuery()).thenReturn(query);
|
||||||
|
when(query.executionId("exec-1")).thenReturn(query);
|
||||||
|
when(query.singleResult()).thenReturn(currentExecution);
|
||||||
|
when(currentExecution.getParentId()).thenReturn(" ");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService, never()).setVariableLocal(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_write_to_grand_parent_when_both_levels_exist() {
|
||||||
|
mockExecutionChain("parent-1", "grand-1");
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService).setVariableLocal("grand-1", "tem_impl_worker", "worker1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_write_to_parent_when_no_grand_parent() {
|
||||||
|
mockExecutionChain("parent-1", null);
|
||||||
|
listener.notify(delegateTask);
|
||||||
|
verify(runtimeService).setVariableLocal("parent-1", "tem_impl_worker", "worker1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_set_variable_fails() {
|
||||||
|
// 与 fixcontact 不同:xispeak 监听器 catch 后重新抛出 RuntimeException(写入失败即中断流程)
|
||||||
|
mockExecutionChain("parent-1", "grand-1");
|
||||||
|
doThrow(new RuntimeException("boom")).when(runtimeService).setVariableLocal("grand-1", "tem_impl_worker", "worker1");
|
||||||
|
org.junit.jupiter.api.Assertions.assertThrows(RuntimeException.class, () -> listener.notify(delegateTask));
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.service.impl;
|
||||||
|
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeakFeedback;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.mapper.BgXiSpeakFeedbackMapper;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.mapper.BgXiSpeakMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BgXiSpeakFeedbackServiceImpl 测试。
|
||||||
|
* 核心:syncFeedbackCounts 统计反馈总数/已闭环数并回写主表 implCount/closedCount。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class BgXiSpeakFeedbackServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private BgXiSpeakFeedbackMapper baseMapper;
|
||||||
|
@Mock private BgXiSpeakMapper bgXiSpeakMapper;
|
||||||
|
|
||||||
|
private BgXiSpeakFeedbackServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new BgXiSpeakFeedbackServiceImpl();
|
||||||
|
ReflectionTestUtils.setField(service, "baseMapper", baseMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "bgXiSpeakMapper", bgXiSpeakMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_when_main_id_empty() {
|
||||||
|
// oConvertUtils.isEmpty 只认 null/空串(不认空格)
|
||||||
|
service.syncFeedbackCounts("");
|
||||||
|
verify(baseMapper, never()).selectCount(any());
|
||||||
|
verify(bgXiSpeakMapper, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_sync_counts_to_main() {
|
||||||
|
when(baseMapper.selectCount(any())).thenReturn(5L, 2L);
|
||||||
|
|
||||||
|
service.syncFeedbackCounts("biz-1");
|
||||||
|
|
||||||
|
// 两次 count:全部反馈 + 已闭环
|
||||||
|
verify(baseMapper, times(2)).selectCount(any());
|
||||||
|
org.mockito.ArgumentCaptor<BgXiSpeak> captor = org.mockito.ArgumentCaptor.forClass(BgXiSpeak.class);
|
||||||
|
verify(bgXiSpeakMapper).updateById(captor.capture());
|
||||||
|
assertEquals("biz-1", captor.getValue().getId());
|
||||||
|
assertEquals(5, captor.getValue().getImplCount());
|
||||||
|
assertEquals(2, captor.getValue().getClosedCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
+345
@@ -0,0 +1,345 @@
|
|||||||
|
package org.jeecg.modules.supervision.xispeak.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||||
|
import org.flowable.engine.HistoryService;
|
||||||
|
import org.flowable.engine.RuntimeService;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstanceQuery;
|
||||||
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
|
import org.jeecg.modules.extbpm.process.entity.ExtActTaskCc;
|
||||||
|
import org.jeecg.modules.extbpm.process.service.IExtActTaskCcService;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeakFeedback;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.mapper.BgXiSpeakMapper;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakFeedbackService;
|
||||||
|
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
|
||||||
|
import org.jeecg.modules.taskapprovalopinion.entity.TaskApprovalOpinion;
|
||||||
|
import org.jeecg.modules.taskapprovalopinion.service.ITaskApprovalOpinionService;
|
||||||
|
import org.jeecg.modules.tasktask.entity.TaskTask;
|
||||||
|
import org.jeecg.modules.tasktask.service.impl.TaskTaskServiceImpl;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
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.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BgXiSpeakServiceImpl 测试(树节点 CRUD 规则 + 流程清理 + 审批保存)。
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class BgXiSpeakServiceImplTest {
|
||||||
|
|
||||||
|
@Mock private TaskTaskServiceImpl taskTaskServiceImpl;
|
||||||
|
@Mock private RuntimeService runtimeService;
|
||||||
|
@Mock private HistoryService historyService;
|
||||||
|
@Mock private ITaskApprovalOpinionService taskApprovalOpinionService;
|
||||||
|
@Mock private IExtActTaskCcService extActTaskCcService;
|
||||||
|
@Mock private IBgXiSpeakFeedbackService bgXiSpeakFeedbackService;
|
||||||
|
@Mock private BgXiSpeakMapper baseMapper;
|
||||||
|
@Mock private ProcessInstanceQuery processInstanceQuery;
|
||||||
|
|
||||||
|
private BgXiSpeakServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new BgXiSpeakServiceImpl(taskTaskServiceImpl, runtimeService, historyService,
|
||||||
|
taskApprovalOpinionService, extActTaskCcService, bgXiSpeakFeedbackService);
|
||||||
|
ReflectionTestUtils.setField(service, "baseMapper", baseMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== addBgXiSpeak 树节点规则 ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class AddNode {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_set_root_values_when_pid_empty() {
|
||||||
|
BgXiSpeak node = new BgXiSpeak();
|
||||||
|
service.addBgXiSpeak(node);
|
||||||
|
assertEquals(IBgXiSpeakService.NOCHILD, node.getHasChild());
|
||||||
|
assertEquals(IBgXiSpeakService.ROOT_PID_VALUE, node.getPid());
|
||||||
|
assertEquals(0, node.getTreeDepth());
|
||||||
|
assertEquals("1", node.getBpmStatus());
|
||||||
|
verify(baseMapper).insert(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_mark_parent_has_child_and_increment_depth() {
|
||||||
|
BgXiSpeak parent = new BgXiSpeak();
|
||||||
|
parent.setPid(IBgXiSpeakService.ROOT_PID_VALUE);
|
||||||
|
parent.setHasChild(IBgXiSpeakService.NOCHILD);
|
||||||
|
parent.setTreeDepth(1);
|
||||||
|
when(baseMapper.selectById("p-1")).thenReturn(parent);
|
||||||
|
|
||||||
|
BgXiSpeak node = new BgXiSpeak();
|
||||||
|
node.setPid("p-1");
|
||||||
|
service.addBgXiSpeak(node);
|
||||||
|
|
||||||
|
assertEquals("1", parent.getHasChild());
|
||||||
|
verify(baseMapper).updateById(parent);
|
||||||
|
assertEquals(2, node.getTreeDepth());
|
||||||
|
verify(baseMapper).insert(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_set_depth_zero_when_parent_missing() {
|
||||||
|
when(baseMapper.selectById("p-1")).thenReturn(null);
|
||||||
|
BgXiSpeak node = new BgXiSpeak();
|
||||||
|
node.setPid("p-1");
|
||||||
|
service.addBgXiSpeak(node);
|
||||||
|
assertEquals(0, node.getTreeDepth());
|
||||||
|
verify(baseMapper).insert(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== updateBgXiSpeak ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class UpdateNode {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_entity_not_found() {
|
||||||
|
BgXiSpeak node = new BgXiSpeak();
|
||||||
|
node.setId("n-1");
|
||||||
|
when(baseMapper.selectById("n-1")).thenReturn(null);
|
||||||
|
assertThrows(JeecgBootException.class, () -> service.updateBgXiSpeak(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_update_new_parent_status_when_pid_changed() {
|
||||||
|
BgXiSpeak old = new BgXiSpeak();
|
||||||
|
old.setId("n-1");
|
||||||
|
old.setPid("old-p");
|
||||||
|
when(baseMapper.selectById("n-1")).thenReturn(old);
|
||||||
|
// 旧父节点无其他子节点 → 置 NOCHILD
|
||||||
|
when(baseMapper.selectCount(any(Wrapper.class))).thenReturn(1L);
|
||||||
|
|
||||||
|
BgXiSpeak node = new BgXiSpeak();
|
||||||
|
node.setId("n-1");
|
||||||
|
node.setPid("new-p");
|
||||||
|
service.updateBgXiSpeak(node);
|
||||||
|
|
||||||
|
verify(baseMapper).updateTreeNodeStatus("old-p", IBgXiSpeakService.NOCHILD);
|
||||||
|
verify(baseMapper).updateTreeNodeStatus("new-p", IBgXiSpeakService.HASCHILD);
|
||||||
|
verify(baseMapper).updateById(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_touch_parents_when_pid_unchanged() {
|
||||||
|
BgXiSpeak old = new BgXiSpeak();
|
||||||
|
old.setId("n-1");
|
||||||
|
old.setPid("p-1");
|
||||||
|
when(baseMapper.selectById("n-1")).thenReturn(old);
|
||||||
|
|
||||||
|
BgXiSpeak node = new BgXiSpeak();
|
||||||
|
node.setId("n-1");
|
||||||
|
node.setPid("p-1");
|
||||||
|
service.updateBgXiSpeak(node);
|
||||||
|
|
||||||
|
verify(baseMapper, never()).updateTreeNodeStatus(any(), any());
|
||||||
|
verify(baseMapper).updateById(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== deleteBgXiSpeak ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class DeleteNode {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_delete_single_node_and_cleanup() {
|
||||||
|
BgXiSpeak node = new BgXiSpeak();
|
||||||
|
node.setId("n-1");
|
||||||
|
node.setPid("p-1");
|
||||||
|
when(baseMapper.selectById("n-1")).thenReturn(node);
|
||||||
|
when(baseMapper.selectCount(any(Wrapper.class))).thenReturn(0L);
|
||||||
|
// 无关联 task_task:cleanup 只删反馈子表
|
||||||
|
when(taskTaskServiceImpl.list(any(Wrapper.class))).thenReturn(List.of());
|
||||||
|
|
||||||
|
service.deleteBgXiSpeak("n-1");
|
||||||
|
|
||||||
|
verify(baseMapper).updateTreeNodeStatus("p-1", IBgXiSpeakService.NOCHILD);
|
||||||
|
verify(baseMapper).deleteById("n-1");
|
||||||
|
verify(bgXiSpeakFeedbackService).remove(any(Wrapper.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_single_node_not_found() {
|
||||||
|
when(baseMapper.selectById("n-1")).thenReturn(null);
|
||||||
|
assertThrows(JeecgBootException.class, () -> service.deleteBgXiSpeak("n-1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_delete_tree_with_children() {
|
||||||
|
// n-1 有两个子节点 n-2/n-3:递归收集后批量删除
|
||||||
|
BgXiSpeak n1 = new BgXiSpeak();
|
||||||
|
n1.setId("n-1");
|
||||||
|
n1.setPid("p-1");
|
||||||
|
BgXiSpeak n2 = new BgXiSpeak();
|
||||||
|
n2.setId("n-2");
|
||||||
|
n2.setPid("n-1");
|
||||||
|
BgXiSpeak n3 = new BgXiSpeak();
|
||||||
|
n3.setId("n-3");
|
||||||
|
n3.setPid("n-1");
|
||||||
|
when(baseMapper.selectById("n-1")).thenReturn(n1);
|
||||||
|
when(baseMapper.selectById("n-2")).thenReturn(n2);
|
||||||
|
when(baseMapper.selectById("n-3")).thenReturn(n3);
|
||||||
|
// selectList 按调用序列:查 n-1 子→[n2,n3];递归查 n-2/n-3 子→空(终止递归);查 p-1 其他子→空
|
||||||
|
when(baseMapper.selectList(any(Wrapper.class)))
|
||||||
|
.thenReturn(List.of(n2, n3), List.of(), List.of(), List.of());
|
||||||
|
|
||||||
|
service.deleteBgXiSpeak("n-1");
|
||||||
|
|
||||||
|
verify(baseMapper).deleteBatchIds(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== withDrawXiSpeak ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class WithDraw {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_false_when_no_related_task() {
|
||||||
|
when(taskTaskServiceImpl.list(any(Wrapper.class))).thenReturn(List.of());
|
||||||
|
assertFalse(service.withDrawXiSpeak("biz-1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_terminate_process_and_cleanup() {
|
||||||
|
TaskTask tt = new TaskTask();
|
||||||
|
tt.setBusinessId("biz-1");
|
||||||
|
tt.setProcessInstId("proc-1");
|
||||||
|
when(taskTaskServiceImpl.list(any(Wrapper.class))).thenReturn(List.of(tt));
|
||||||
|
when(runtimeService.createProcessInstanceQuery()).thenReturn(processInstanceQuery);
|
||||||
|
when(processInstanceQuery.processInstanceId("proc-1")).thenReturn(processInstanceQuery);
|
||||||
|
when(processInstanceQuery.count()).thenReturn(1L);
|
||||||
|
|
||||||
|
assertTrue(service.withDrawXiSpeak("biz-1"));
|
||||||
|
|
||||||
|
verify(runtimeService).deleteProcessInstance(eq("proc-1"), any());
|
||||||
|
verify(taskApprovalOpinionService).remove(any(Wrapper.class));
|
||||||
|
verify(extActTaskCcService).remove(any(Wrapper.class));
|
||||||
|
verify(taskTaskServiceImpl).remove(any(Wrapper.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== checkAndMarkCompleted ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CheckCompleted {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_when_process_inst_id_empty() {
|
||||||
|
service.checkAndMarkCompletedByProcessInstId("");
|
||||||
|
verify(taskTaskServiceImpl, never()).getOne(any(Wrapper.class), anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_mark_when_any_process_running() {
|
||||||
|
when(taskTaskServiceImpl.getOne(any(Wrapper.class), anyBoolean())).thenReturn(taskWithProc("biz-1", "proc-1"));
|
||||||
|
when(taskTaskServiceImpl.list(any(Wrapper.class))).thenReturn(List.of(taskWithProc("biz-1", "proc-1")));
|
||||||
|
when(runtimeService.createProcessInstanceQuery()).thenReturn(processInstanceQuery);
|
||||||
|
when(processInstanceQuery.processInstanceId("proc-1")).thenReturn(processInstanceQuery);
|
||||||
|
when(processInstanceQuery.count()).thenReturn(1L);
|
||||||
|
|
||||||
|
service.checkAndMarkCompletedByProcessInstId("proc-1");
|
||||||
|
|
||||||
|
verify(baseMapper, never()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_mark_completed_when_all_processes_finished() {
|
||||||
|
when(taskTaskServiceImpl.getOne(any(Wrapper.class), anyBoolean())).thenReturn(taskWithProc("biz-1", "proc-1"));
|
||||||
|
when(taskTaskServiceImpl.list(any(Wrapper.class))).thenReturn(List.of(taskWithProc("biz-1", "proc-1")));
|
||||||
|
when(runtimeService.createProcessInstanceQuery()).thenReturn(processInstanceQuery);
|
||||||
|
when(processInstanceQuery.processInstanceId("proc-1")).thenReturn(processInstanceQuery);
|
||||||
|
when(processInstanceQuery.count()).thenReturn(0L);
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
when(baseMapper.selectById("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
service.checkAndMarkCompletedByProcessInstId("proc-1");
|
||||||
|
|
||||||
|
assertEquals(1, entity.getCompletionStatus());
|
||||||
|
verify(baseMapper).updateById(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskTask taskWithProc(String biz, String proc) {
|
||||||
|
TaskTask tt = new TaskTask();
|
||||||
|
tt.setBusinessId(biz);
|
||||||
|
tt.setProcessInstId(proc);
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== saveSubApprove ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class SaveSubApprove {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_throw_when_main_not_exists() {
|
||||||
|
when(baseMapper.selectById("biz-1")).thenReturn(null);
|
||||||
|
assertThrows(JeecgBootException.class, () -> service.saveSubApprove("biz-1", "dept-1", 1, "w1", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_store_impl_worker_when_is_end_one() {
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
when(baseMapper.selectById("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
service.saveSubApprove("biz-1", "dept-1", 1, "w1", List.of("w2"));
|
||||||
|
|
||||||
|
DeptApproveDetail detail = entity.getApproveInfo().get("dept-1");
|
||||||
|
assertEquals("w1", detail.getImplWorker());
|
||||||
|
assertNull(detail.getImplUserNameList());
|
||||||
|
verify(baseMapper).updateById(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_store_impl_user_name_list_when_is_end_zero() {
|
||||||
|
BgXiSpeak entity = new BgXiSpeak();
|
||||||
|
entity.setId("biz-1");
|
||||||
|
when(baseMapper.selectById("biz-1")).thenReturn(entity);
|
||||||
|
|
||||||
|
service.saveSubApprove("biz-1", "dept-1", 0, "w1", List.of("w2", "w3"));
|
||||||
|
|
||||||
|
DeptApproveDetail detail = entity.getApproveInfo().get("dept-1");
|
||||||
|
assertNull(detail.getImplWorker());
|
||||||
|
assertEquals(List.of("w2", "w3"), detail.getImplUserNameList());
|
||||||
|
verify(baseMapper).updateById(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== saveBpmFormAndSyncVariable ====================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class SaveBpmForm {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_update_form_and_sync_variable() {
|
||||||
|
BgXiSpeak form = new BgXiSpeak();
|
||||||
|
service.saveBpmFormAndSyncVariable(form, "proc-1", "json_data", "{\"a\":1}");
|
||||||
|
verify(baseMapper).updateById(form);
|
||||||
|
verify(runtimeService).setVariable("proc-1", "json_data", "{\"a\":1}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user