feat(approval): PC 待办改派接口与集成测试 (#22)
- 新增 POST /api/approval/task/{id}/assign:权限 approval:instance:assign,
任务须 PENDING、目标同租户启用用户、原因必填;事务内锁实例后条件更新
处理人并写 ASSIGN 记录(opinion=原因),@OperationLog 记录原/新处理人与原因
- 新增 ApprovalTaskAssignParam / ApprovalAssignVO
- 新增 ApprovalPcTodoIT:改派成功与归属变化、无效目标、非待办任务、原因必填、
无权限 403、ASSIGN 记录、操作日志、改派候选用户
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package com.gxwebsoft.approval.controller;
|
||||
|
||||
import com.gxwebsoft.approval.param.ApprovalTaskActionParam;
|
||||
import com.gxwebsoft.approval.param.ApprovalTaskAssignParam;
|
||||
import com.gxwebsoft.approval.param.ApprovalTaskListParam;
|
||||
import com.gxwebsoft.approval.service.ApprovalInstanceService;
|
||||
import com.gxwebsoft.approval.service.ApprovalTaskService;
|
||||
import com.gxwebsoft.approval.vo.ApprovalAssignVO;
|
||||
import com.gxwebsoft.approval.vo.ApprovalInstanceVO;
|
||||
import com.gxwebsoft.approval.vo.ApprovalItemVO;
|
||||
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||
import com.gxwebsoft.common.core.web.ApiResult;
|
||||
import com.gxwebsoft.common.core.web.BaseController;
|
||||
import com.gxwebsoft.common.core.web.PageResult;
|
||||
@@ -85,4 +88,13 @@ public class ApprovalTaskController extends BaseController {
|
||||
ApprovalTaskService.Result result = taskService.withdraw(id, user);
|
||||
return success("撤回成功", instanceService.detail(result.getInstanceId(), user));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('approval:instance:assign')")
|
||||
@OperationLog(value = "改派审批待办", module = "审批管理")
|
||||
@ApiOperation("改派")
|
||||
@PostMapping("/task/{id}/assign")
|
||||
public ApiResult<ApprovalAssignVO> assign(@PathVariable("id") Long id,
|
||||
@RequestBody(required = false) ApprovalTaskAssignParam param) {
|
||||
return success("改派成功", taskService.assign(id, param, getLoginUser()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.gxwebsoft.approval.param;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 待办改派参数。
|
||||
*/
|
||||
@Data
|
||||
public class ApprovalTaskAssignParam implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("改派对象用户id")
|
||||
private Integer targetUserId;
|
||||
|
||||
@ApiModelProperty("改派原因;必填")
|
||||
private String reason;
|
||||
}
|
||||
@@ -8,8 +8,10 @@ import com.gxwebsoft.approval.entity.ApprovalInstance;
|
||||
import com.gxwebsoft.approval.entity.ApprovalTask;
|
||||
import com.gxwebsoft.approval.mapper.ApprovalInstanceMapper;
|
||||
import com.gxwebsoft.approval.mapper.ApprovalTaskMapper;
|
||||
import com.gxwebsoft.approval.param.ApprovalTaskAssignParam;
|
||||
import com.gxwebsoft.approval.support.ApprovalApprover;
|
||||
import com.gxwebsoft.approval.support.ApprovalNode;
|
||||
import com.gxwebsoft.approval.vo.ApprovalAssignVO;
|
||||
import com.gxwebsoft.approval.vo.ApprovalUserVO;
|
||||
import com.gxwebsoft.common.core.exception.BusinessException;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
@@ -109,6 +111,28 @@ public class ApprovalTaskService {
|
||||
return new Result(instanceId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 改派:管理员把待处理任务换给同租户其他启用用户,原因必填并写 ASSIGN 记录。
|
||||
*
|
||||
* <p>改派不改变任务状态,只替换处理人;操作人不必是任务当前处理人。</p>
|
||||
*/
|
||||
public ApprovalAssignVO assign(Long taskId, ApprovalTaskAssignParam param, User user) {
|
||||
if (param == null || param.getTargetUserId() == null) {
|
||||
throw new BusinessException("请选择改派对象");
|
||||
}
|
||||
String reason = requireAssignReason(param.getReason());
|
||||
ApprovalTask task = requireAssignableTask(taskId);
|
||||
// 事务外解析目标用户:启用、未删除且属于当前租户(@DS("server") 查询显式带租户)
|
||||
List<ApprovalUserVO> targets =
|
||||
directoryService.listActiveUsersByIds(Collections.singletonList(param.getTargetUserId()));
|
||||
if (targets.isEmpty()) {
|
||||
throw new BusinessException("改派对象不存在或已停用");
|
||||
}
|
||||
ApprovalUserVO target = targets.get(0);
|
||||
String targetName = displayName(target);
|
||||
return inTransaction(() -> doAssign(taskId, task, target.getUserId(), targetName, reason, user));
|
||||
}
|
||||
|
||||
public Integer todoCount(User user) {
|
||||
return taskMapper.countPendingInstances(user.getUserId());
|
||||
}
|
||||
@@ -171,6 +195,58 @@ public class ApprovalTaskService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务内改派:锁实例后条件更新处理人,并追加 ASSIGN 记录。
|
||||
*/
|
||||
private ApprovalAssignVO doAssign(Long taskId, ApprovalTask task, Integer targetUserId,
|
||||
String targetName, String reason, User user) {
|
||||
ApprovalInstance locked = engineService.lockInstance(task.getInstanceId());
|
||||
ensureTaskHandleable(task, locked);
|
||||
int rows = taskMapper.update(null, new UpdateWrapper<ApprovalTask>()
|
||||
.eq("id", taskId)
|
||||
.eq("status", ApprovalConstants.TASK_PENDING)
|
||||
.set("assignee_id", targetUserId)
|
||||
.set("assignee_name", targetName));
|
||||
if (rows == 0) {
|
||||
throw new BusinessException("任务已处理");
|
||||
}
|
||||
engineService.addRecord(locked, taskId, task.getNodeKey(), user,
|
||||
ApprovalConstants.ACTION_ASSIGN, reason);
|
||||
ApprovalAssignVO vo = new ApprovalAssignVO();
|
||||
vo.setInstanceId(locked.getId());
|
||||
vo.setTaskId(taskId);
|
||||
vo.setOriginalAssigneeId(task.getAssigneeId());
|
||||
vo.setOriginalAssigneeName(task.getAssigneeName());
|
||||
vo.setAssigneeId(targetUserId);
|
||||
vo.setAssigneeName(targetName);
|
||||
vo.setReason(reason);
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 改派前的基本校验:任务存在、待处理且单据仍在审批中。
|
||||
*/
|
||||
private ApprovalTask requireAssignableTask(Long taskId) {
|
||||
if (taskId == null) {
|
||||
throw new BusinessException("任务不存在");
|
||||
}
|
||||
ApprovalTask task = taskMapper.selectById(taskId);
|
||||
if (task == null) {
|
||||
throw new BusinessException("任务不存在");
|
||||
}
|
||||
if (!ApprovalConstants.TASK_PENDING.equals(task.getStatus())) {
|
||||
throw new BusinessException("任务已处理");
|
||||
}
|
||||
ApprovalInstance instance = instanceMapper.selectById(task.getInstanceId());
|
||||
if (instance == null) {
|
||||
throw new BusinessException("单据不存在");
|
||||
}
|
||||
if (!ApprovalConstants.STATUS_PENDING.equals(instance.getStatus())) {
|
||||
throw new BusinessException("单据不在审批中,无法改派");
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务必须属于实例当前轮次、当前节点,否则视为已被处理或已失效。
|
||||
*/
|
||||
@@ -310,6 +386,14 @@ public class ApprovalTaskService {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String requireAssignReason(String reason) {
|
||||
String normalized = normalizeOpinion(reason);
|
||||
if (normalized == null) {
|
||||
throw new BusinessException("请填写改派原因");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private <T> T inTransaction(Supplier<T> action) {
|
||||
return new TransactionTemplate(transactionManager).execute(status -> action.get());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.gxwebsoft.approval.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 改派结果:原处理人、新处理人与原因,同时用于操作日志留痕。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(description = "改派结果")
|
||||
public class ApprovalAssignVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long instanceId;
|
||||
|
||||
private Long taskId;
|
||||
|
||||
private Integer originalAssigneeId;
|
||||
|
||||
private String originalAssigneeName;
|
||||
|
||||
private Integer assigneeId;
|
||||
|
||||
private String assigneeName;
|
||||
|
||||
private String reason;
|
||||
}
|
||||
Reference in New Issue
Block a user