# Conflicts:
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
This commit is contained in:
YunaiV 2024-12-19 21:27:52 +08:00
commit 50536f1af0
21 changed files with 868 additions and 51 deletions

View File

@ -1,11 +1,12 @@
package cn.iocoder.yudao.framework.common.pojo;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.Objects;
@ -41,7 +42,7 @@ public class CommonResult<T> implements Serializable {
* 因为 A 方法返回的 CommonResult 对象不满足调用其的 B 方法的返回所以需要进行转换
*
* @param result 传入的 result 对象
* @param <T> 返回的泛型
* @param <T> 返回的泛型
* @return 新的 CommonResult 对象
*/
public static <T> CommonResult<T> error(CommonResult<?> result) {
@ -49,13 +50,21 @@ public class CommonResult<T> implements Serializable {
}
public static <T> CommonResult<T> error(Integer code, String message) {
Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 必须是错误的!");
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!");
CommonResult<T> result = new CommonResult<>();
result.code = code;
result.msg = message;
return result;
}
public static <T> CommonResult<T> error(ErrorCode errorCode, Object... params) {
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), errorCode.getCode(), "code 必须是错误的!");
CommonResult<T> result = new CommonResult<>();
result.code = errorCode.getCode();
result.msg = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), params);
return result;
}
public static <T> CommonResult<T> error(ErrorCode errorCode) {
return error(errorCode.getCode(), errorCode.getMsg());
}

View File

@ -4,8 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@ -17,6 +15,9 @@ public class BpmTaskPageReqVO extends PageParam {
@Schema(description = "流程任务名", example = "芋道")
private String name;
@Schema(description = "流程分类", example = "1")
private String category;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;

View File

@ -124,12 +124,18 @@ public interface BpmTaskConvert {
}
default BpmTaskRespVO buildTodoTask(Task todoTask, List<Task> childrenTasks,
Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting) {
return BeanUtils.toBean(todoTask, BpmTaskRespVO.class)
Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting,
BpmFormDO form) {
BpmTaskRespVO bpmTaskRespVO = BeanUtils.toBean(todoTask, BpmTaskRespVO.class)
.setStatus(FlowableUtils.getTaskStatus(todoTask)).setReason(FlowableUtils.getTaskReason(todoTask))
.setButtonsSetting(buttonsSetting)
.setChildren(convertList(childrenTasks, childTask -> BeanUtils.toBean(childTask, BpmTaskRespVO.class)
.setStatus(FlowableUtils.getTaskStatus(childTask))));
if (form != null) {
bpmTaskRespVO.setFormId(form.getId()).setFormName(form.getName())
.setFormConf(form.getConf()).setFormFields(form.getFields());
}
return bpmTaskRespVO;
}
default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,

View File

@ -54,13 +54,13 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
if (assigneeUserIds == null) {
assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
execution.setVariable(super.collectionVariable, assigneeUserIds);
if (CollUtil.isEmpty(assigneeUserIds)) {
// 特殊如果没有处理人的情况下至少有一个 null 空元素避免自动通过
// 这样保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
// 用途1审批人为空时2审批类型为自动通过自动拒绝时
assigneeUserIds = SetUtils.asSet((Long) null);
}
execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
}
return assigneeUserIds.size();
}

View File

@ -43,17 +43,18 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步获取任务的所有处理人
// 不使用 execution.getVariable 原因目前依次审批任务回退后 collectionVariable 变量没有清理 如果重新进入该任务不会重新分配审批人
@SuppressWarnings("unchecked")
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
if (assigneeUserIds == null) {
assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
execution.setVariable(super.collectionVariable, assigneeUserIds);
if (CollUtil.isEmpty(assigneeUserIds)) {
// 特殊如果没有处理人的情况下至少有一个 null 空元素避免自动通过
// 这样保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
// 用途1审批人为空时2审批类型为自动通过自动拒绝时
assigneeUserIds = SetUtils.asSet((Long) null);
}
execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
}
return assigneeUserIds.size();
}

View File

@ -18,7 +18,7 @@ import java.util.Set;
* @author jason
*/
@Component
public class BpmTaskCandidateFormSDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
public class BpmTaskCandidateFormDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {

View File

@ -4,10 +4,14 @@ import cn.hutool.core.convert.Convert;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ -17,6 +21,7 @@ import java.util.Set;
* @author 芋道源码
*/
@Component
@Slf4j
public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy {
@Override
@ -38,8 +43,16 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
@Override
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
Object result = FlowableUtils.getExpressionValue(processVariables, param);
return Convert.toSet(Long.class, result);
Map<String, Object> variables = processVariables == null ? new HashMap<>() : processVariables;
try {
Object result = FlowableUtils.getExpressionValue(variables, param);
return Convert.toSet(Long.class, result);
} catch (FlowableException ex) {
// 预测未运行的节点时候表达式如果包含 execution 或者不存在的流程变量会抛异常
log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);
// 不能预测候选人返回空列表, 避免流程无法进行
return Sets.newHashSet();
}
}
}

View File

@ -73,7 +73,6 @@ public class BpmnModelUtils {
extensionElement.setName(name);
attributes.forEach((key, value) -> {
ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value);
extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
extensionElement.addAttribute(extensionAttribute);
});
element.addExtensionElement(extensionElement);
@ -278,8 +277,8 @@ public class BpmnModelUtils {
}
Map<String, String> fieldsPermission = MapUtil.newHashMap();
extensionElements.forEach(element -> {
String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE);
String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE);
String field = element.getAttributeValue(null, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE);
String permission = element.getAttributeValue(null, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE);
if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) {
fieldsPermission.put(field, permission);
}
@ -321,9 +320,9 @@ public class BpmnModelUtils {
}
Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size());
extensionElements.forEach(element -> {
String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
String enable = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE);
String id = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
String displayName = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
String enable = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE);
if (StrUtil.isNotEmpty(id)) {
BpmTaskRespVO.OperationButtonSetting setting = new BpmTaskRespVO.OperationButtonSetting();
buttonSettings.put(Integer.valueOf(id), setting.setDisplayName(displayName).setEnable(Boolean.parseBoolean(enable)));
@ -720,7 +719,7 @@ public class BpmnModelUtils {
&& evalConditionExpress(variables, flow.getConditionExpression()));
if (matchSequenceFlow == null) {
matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
// 特殊没有默认的情况下并且只有 1 个条件则认为它是默认的
if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
matchSequenceFlow = gateway.getOutgoingFlows().get(0);
@ -742,7 +741,7 @@ public class BpmnModelUtils {
&& evalConditionExpress(variables, flow.getConditionExpression()));
if (CollUtil.isEmpty(matchSequenceFlows)) {
matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
// 特殊没有默认的情况下并且只有 1 个条件则认为它是默认的
if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
matchSequenceFlows = gateway.getOutgoingFlows();

View File

@ -83,7 +83,7 @@ public class SimpleModelUtils {
private static BpmSimpleModelNodeVO buildStartNode() {
return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID)
.setName(BpmSimpleModelNodeType.START_USER_NODE.getName())
.setName(BpmSimpleModelNodeType.START_NODE.getName())
.setType(BpmSimpleModelNodeType.START_NODE.getType());
}

View File

@ -65,7 +65,7 @@ public class BpmModelServiceImpl implements BpmModelService {
public List<Model> getModelList(String name) {
ModelQuery modelQuery = repositoryService.createModelQuery();
if (StrUtil.isNotEmpty(name)) {
modelQuery.modelNameLike(name);
modelQuery.modelNameLike("%" + name + "%");
}
return modelQuery.list();
}

View File

@ -12,6 +12,7 @@ import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.enums.definition.*;
import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
@ -20,6 +21,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
@ -91,6 +93,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
private BpmModelService modelService;
@Resource
private BpmMessageService messageService;
@Resource
private BpmFormService formService;
@Resource
private AdminUserApi adminUserApi;
@ -109,6 +113,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
if (StrUtil.isNotBlank(pageVO.getName())) {
taskQuery.taskNameLike("%" + pageVO.getName() + "%");
}
if (StrUtil.isNotEmpty(pageVO.getCategory())) {
taskQuery.taskCategory(pageVO.getCategory());
}
if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
@ -153,7 +160,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId());
Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting = BpmnModelUtils.parseButtonsSetting(
bpmnModel, todoTask.getTaskDefinitionKey());
return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting);
// 4. 任务表单
BpmFormDO taskForm = null;
if (StrUtil.isNotBlank(todoTask.getFormKey())){
taskForm = formService.getForm(NumberUtils.parseLong(todoTask.getFormKey()));
}
return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting, taskForm);
}
@Override
@ -188,6 +201,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
if (StrUtil.isNotBlank(pageVO.getName())) {
taskQuery.taskNameLike("%" + pageVO.getName() + "%");
}
if (StrUtil.isNotEmpty(pageVO.getCategory())) {
taskQuery.taskCategory(pageVO.getCategory());
}
if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
@ -441,7 +457,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
* 判断指定用户是否是当前任务的加签人
*
* @param userId 用户 Id
* @param task 任务
* @param task 任务
* @return 是否
*/
private boolean isAddSignUserTask(Long userId, Task task) {
@ -669,7 +685,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId());
// 2. 调用 Flowable 框架的退回逻辑
returnTask(task, targetElement, reqVO);
returnTask(userId, task, targetElement, reqVO);
}
/**
@ -701,11 +717,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/**
* 执行退回逻辑
*
* @param userId 用户编号
* @param currentTask 当前退回的任务
* @param targetElement 需要退回到的目标任务
* @param reqVO 前端参数封装
*/
public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
public void returnTask(Long userId, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
// 1. 获得所有需要回撤的任务 taskDefinitionKey用于稍后的 moveActivityIdsToSingleActivityId 回撤
// 1.1 获取所有正常进行的任务节点 Key
List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list();
@ -721,22 +738,29 @@ public class BpmTaskServiceImpl implements BpmTaskService {
if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
return;
}
// 2.1 添加评论
taskService.addComment(task.getId(), currentTask.getProcessInstanceId(), BpmCommentTypeEnum.RETURN.getType(),
BpmCommentTypeEnum.RETURN.formatComment(reqVO.getReason()));
// 2.2 更新 task 状态 + 原因
updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason());
// 判断是否分配给自己任务因为会签任务一个节点会有多个任务
if (isAssignUserTask(userId, task)) { // 情况一自己的任务进行 RETURN 标记
// 2.1.1 添加评论
taskService.addComment(task.getId(), currentTask.getProcessInstanceId(), BpmCommentTypeEnum.RETURN.getType(),
BpmCommentTypeEnum.RETURN.formatComment(reqVO.getReason()));
// 2.1.2 更新 task 状态 + 原因
updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason());
} else { // 情况二别人的任务进行 CANCEL 标记
processTaskCanceled(task.getId());
}
});
// 3. 设置流程变量节点驳回标记用于驳回到节点不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略导致自动通过
runtimeService.setVariable(currentTask.getProcessInstanceId(),
String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE);
// 4. 执行驳回
// 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因
// 当多实例任务回退的时候有问题相关 issue: https://github.com/flowable/flowable-engine/issues/3944
List<String> runExecutionIds = convertList(taskList, Task::getExecutionId);
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(currentTask.getProcessInstanceId())
.moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多)
reqVO.getTargetTaskDefinitionKey()) // targetKey 跳转到的节点(1)
.moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey())
.changeState();
}
@ -1021,14 +1045,22 @@ public class BpmTaskServiceImpl implements BpmTaskService {
Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTaskElement);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
/**
* 特殊情况部分情况下TransactionSynchronizationManager 注册 afterCommit 监听时不会被调用但是 afterCompletion 可以
* 例如说第一个 task 就是配置自动通过或者自动拒绝
* 参见 <a href="https://gitee.com/zhijiantianya/yudao-cloud/issues/IB7V7Q">issue</a> 反馈
*/
@Override
public void afterCompletion(int transactionStatus) {
// 特殊情况部分情况下TransactionSynchronizationManager 注册 afterCommit 监听时不会被调用但是 afterCompletion 可以
// 例如说第一个 task 就是配置自动通过或者自动拒绝
if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) {
// 回滚情况直接返回
if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_ROLLED_BACK)) {
return;
}
// 特殊情况第一个 task 自动通过第二个任务设置审批人时 transactionStatus 会为 STATUS_UNKNOWN不知道啥原因
if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_UNKNOWN)
&& getTask(task.getId()) == null) {
return;
}
// TODO 芋艿可以后续优化成 getSelf();
// 特殊情况一人工审核审批人为空根据配置是否要自动通过自动拒绝
if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) {
// 如果有审批人或者拥有人则说明不满足情况一不自动通过不自动拒绝
@ -1036,19 +1068,19 @@ public class BpmTaskServiceImpl implements BpmTaskService {
return;
}
if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) {
SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
getSelf().approveTask(null, new BpmTaskApproveReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason()));
} else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) {
SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
getSelf().rejectTask(null, new BpmTaskRejectReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason()));
}
// 特殊情况二自动审核审批类型为自动通过不通过
} else {
if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) {
SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
getSelf().approveTask(null, new BpmTaskApproveReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason()));
} else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
getSelf().rejectTask(null, new BpmTaskRejectReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason()));
}
}
@ -1087,8 +1119,22 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 发送通知在事务提交时批量执行操作所以直接查询会无法查询到 ProcessInstance所以这里是通过监听事务的提交来实现
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
/**
* 特殊情况部分情况下TransactionSynchronizationManager 注册 afterCommit 监听时不会被调用但是 afterCompletion 可以
* 例如说第一个 task 就是配置自动通过或者自动拒绝
* 参见 <a href="https://gitee.com/zhijiantianya/yudao-cloud/issues/IB7V7Q">issue</a> 反馈
*/
@Override
public void afterCommit() {
public void afterCompletion(int transactionStatus) {
// 回滚情况直接返回
if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_ROLLED_BACK)) {
return;
}
// 特殊情况第一个 task 自动通过第二个任务设置审批人时 transactionStatus 会为 STATUS_UNKNOWN不知道啥原因
if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_UNKNOWN)
&& getTask(task.getId()) == null) {
return;
}
if (StrUtil.isEmpty(task.getAssignee())) {
log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId());
return;

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -11,6 +12,7 @@ import java.util.List;
@Schema(description = "管理后台 - ERP 付款单 Response VO")
@Data
@ExcelIgnoreUnannotated
public class ErpFinancePaymentRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23752")

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -11,6 +12,7 @@ import java.util.List;
@Schema(description = "管理后台 - ERP 收款单 Response VO")
@Data
@ExcelIgnoreUnannotated
public class ErpFinanceReceiptRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23752")

View File

@ -15,8 +15,8 @@ import java.util.Arrays;
@AllArgsConstructor
public enum ProductCommentAuditStatusEnum implements IntArrayValuable {
NONE(1, "待审核"),
APPROVE(2, "审批通过"),
NONE(0, "待审核"),
APPROVE(1, "审批通过"),
REJECT(2, "审批不通过"),;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductCommentAuditStatusEnum::getStatus).toArray();

View File

@ -20,7 +20,7 @@ import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 售后日志")
@Tag(name = "用户 App - 售后日志")
@RestController
@RequestMapping("/trade/after-sale-log")
@Validated

View File

@ -688,8 +688,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
List<TradeOrderItemDO> updateItems = new ArrayList<>();
for (int i = 0; i < orderOrderItems.size(); i++) {
TradeOrderItemDO item = orderOrderItems.get(i);
updateItems.add(new TradeOrderItemDO().setId(item.getId()).setAdjustPrice(item.getAdjustPrice() + dividePrices.get(i))
.setPayPrice((item.getPayPrice() - item.getAdjustPrice()) + dividePrices.get(i)));
updateItems.add(new TradeOrderItemDO().setId(item.getId())
.setAdjustPrice(item.getAdjustPrice() + dividePrices.get(i))
.setPayPrice(item.getPayPrice() + dividePrices.get(i)));
}
tradeOrderItemMapper.updateBatch(updateItems);

View File

@ -21,7 +21,7 @@ import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 签到记录")
@Tag(name = "用户 App - 签到记录")
@RestController
@RequestMapping("/member/sign-in/record")
@Validated

View File

@ -111,6 +111,7 @@ public interface ErrorCodeConstants {
ErrorCode TENANT_PACKAGE_NOT_EXISTS = new ErrorCode(1_002_016_000, "租户套餐不存在");
ErrorCode TENANT_PACKAGE_USED = new ErrorCode(1_002_016_001, "租户正在使用该套餐,请给租户重新设置套餐后再尝试删除");
ErrorCode TENANT_PACKAGE_DISABLE = new ErrorCode(1_002_016_002, "名字为【{}】的租户套餐已被禁用");
ErrorCode TENANT_PACKAGE_NAME_DUPLICATE = new ErrorCode(1_002_016_003, "已经存在该名字的租户套餐");
// ========== 社交用户 1-002-018-000 ==========
ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1_002_018_000, "社交授权失败,原因是:{}");

View File

@ -29,4 +29,8 @@ public interface TenantPackageMapper extends BaseMapperX<TenantPackageDO> {
default List<TenantPackageDO> selectListByStatus(Integer status) {
return selectList(TenantPackageDO::getStatus, status);
}
default TenantPackageDO selectByName(String name) {
return selectOne(TenantPackageDO::getName, name);
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.service.tenant;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@ -10,6 +11,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.google.common.annotations.VisibleForTesting;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -38,6 +40,8 @@ public class TenantPackageServiceImpl implements TenantPackageService {
@Override
public Long createTenantPackage(TenantPackageSaveReqVO createReqVO) {
// 校验套餐名是否重复
validateTenantPackageNameUnique(null, createReqVO.getName());
// 插入
TenantPackageDO tenantPackage = BeanUtils.toBean(createReqVO, TenantPackageDO.class);
tenantPackageMapper.insert(tenantPackage);
@ -50,6 +54,8 @@ public class TenantPackageServiceImpl implements TenantPackageService {
public void updateTenantPackage(TenantPackageSaveReqVO updateReqVO) {
// 校验存在
TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId());
// 校验套餐名是否重复
validateTenantPackageNameUnique(updateReqVO.getId(), updateReqVO.getName());
// 更新
TenantPackageDO updateObj = BeanUtils.toBean(updateReqVO, TenantPackageDO.class);
tenantPackageMapper.updateById(updateObj);
@ -111,4 +117,23 @@ public class TenantPackageServiceImpl implements TenantPackageService {
return tenantPackageMapper.selectListByStatus(status);
}
@VisibleForTesting
void validateTenantPackageNameUnique(Long id, String name) {
if (StrUtil.isBlank(name)) {
return;
}
TenantPackageDO tenantPackage = tenantPackageMapper.selectByName(name);
if (tenantPackage == null) {
return;
}
// 如果 id 为空说明不用比较是否为相同 id 的用户
if (id == null) {
throw exception(TENANT_PACKAGE_NAME_DUPLICATE);
}
if (!tenantPackage.getId().equals(id)) {
throw exception(TENANT_PACKAGE_NAME_DUPLICATE);
}
}
}