Merge branch 'feat/approval-phase1' into feat/approval-t26
This commit is contained in:
@@ -163,6 +163,13 @@ public final class ApprovalConstants {
|
||||
return CATEGORY_NAMES.getOrDefault(category, category);
|
||||
}
|
||||
|
||||
/**
|
||||
* 归档后单据为只读终态:任何写动作统一返回该提示。
|
||||
*/
|
||||
public static String writeBlockedMessage(String status) {
|
||||
return STATUS_ARCHIVED.equals(status) ? "单据已归档,不能修改" : "当前状态不可修改";
|
||||
}
|
||||
|
||||
public static String departmentLeaderRoleCode(Integer organizationId) {
|
||||
return organizationId == null ? null : "dept_leader_" + organizationId;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gxwebsoft.approval.controller;
|
||||
|
||||
import com.gxwebsoft.approval.param.ApprovalCompletionParam;
|
||||
import com.gxwebsoft.approval.param.ApprovalCoordinationParam;
|
||||
import com.gxwebsoft.approval.param.ApprovalMineParam;
|
||||
import com.gxwebsoft.approval.service.ApprovalInstanceService;
|
||||
import com.gxwebsoft.approval.vo.ApprovalInstanceVO;
|
||||
@@ -40,6 +41,14 @@ public class ApprovalInstanceController extends BaseController {
|
||||
return success("保存成功", instanceService.saveCompletion(param, user));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('approval:instance:submit')")
|
||||
@ApiOperation("新建或保存内部协调单草稿")
|
||||
@PostMapping("/instance/coordination")
|
||||
public ApiResult<Long> saveCoordination(@RequestBody ApprovalCoordinationParam param) {
|
||||
User user = getLoginUser();
|
||||
return success("保存成功", instanceService.saveCoordination(param, user));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('approval:instance:submit')")
|
||||
@ApiOperation("提交或重提")
|
||||
@PostMapping("/instance/{id}/submit")
|
||||
|
||||
@@ -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;
|
||||
@@ -77,6 +80,25 @@ public class ApprovalTaskController extends BaseController {
|
||||
return success("驳回成功", instanceService.detail(result.getInstanceId(), user));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('approval:instance:approve')")
|
||||
@ApiOperation("存档确认")
|
||||
@PostMapping("/task/{id}/confirm")
|
||||
public ApiResult<ApprovalInstanceVO> confirm(@PathVariable("id") Long id) {
|
||||
User user = getLoginUser();
|
||||
ApprovalTaskService.Result result = taskService.confirm(id, user);
|
||||
return success(result.getWarning() == null ? "存档成功" : result.getWarning(),
|
||||
instanceService.detail(result.getInstanceId(), user));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('approval:instance:archive')")
|
||||
@ApiOperation("归档")
|
||||
@PostMapping("/task/{id}/archive")
|
||||
public ApiResult<ApprovalInstanceVO> archive(@PathVariable("id") Long id) {
|
||||
User user = getLoginUser();
|
||||
ApprovalTaskService.Result result = taskService.archive(id, user);
|
||||
return success("归档成功", instanceService.detail(result.getInstanceId(), user));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('approval:instance:submit')")
|
||||
@ApiOperation("撤回")
|
||||
@PostMapping("/instance/{id}/withdraw")
|
||||
@@ -85,4 +107,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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
i.reporter_id,
|
||||
i.reporter_name,
|
||||
i.report_org_name,
|
||||
i.accept_org_name,
|
||||
COALESCE(i.accept_org_name, co.accept_org_name) AS accept_org_name,
|
||||
i.archive_time,
|
||||
i.current_node_key,
|
||||
i.definition_id,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.gxwebsoft.approval.param;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 内部协调单草稿参数;id 为审批实例id,更新草稿时传入。
|
||||
*
|
||||
* <p>报送部门与经办人由服务端按登录态带出,不在参数中接收。</p>
|
||||
*/
|
||||
@Data
|
||||
public class ApprovalCoordinationParam implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("审批实例id(更新草稿时传入)")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("受理部门id,必须是启用组织")
|
||||
private Integer acceptOrgId;
|
||||
|
||||
@ApiModelProperty("标题")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty("内容")
|
||||
private String content;
|
||||
|
||||
@ApiModelProperty("备注")
|
||||
private String remark;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -162,9 +162,11 @@ public class ApprovalAttachmentService {
|
||||
}
|
||||
|
||||
private void checkEditable(ApprovalInstance instance, User user) {
|
||||
if (user == null || !instance.getReporterId().equals(user.getUserId())
|
||||
|| !ApprovalConstants.EDITABLE_STATUSES.contains(instance.getStatus())) {
|
||||
throw new BusinessException("当前状态不可修改");
|
||||
if (user == null || !instance.getReporterId().equals(user.getUserId())) {
|
||||
throw new BusinessException("无权操作该单据");
|
||||
}
|
||||
if (!ApprovalConstants.EDITABLE_STATUSES.contains(instance.getStatus())) {
|
||||
throw new BusinessException(ApprovalConstants.writeBlockedMessage(instance.getStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.gxwebsoft.approval.mapper.ApprovalInstanceMapper;
|
||||
import com.gxwebsoft.approval.mapper.ApprovalRecordMapper;
|
||||
import com.gxwebsoft.approval.mapper.ApprovalTaskMapper;
|
||||
import com.gxwebsoft.approval.param.ApprovalCompletionParam;
|
||||
import com.gxwebsoft.approval.param.ApprovalCoordinationParam;
|
||||
import com.gxwebsoft.approval.param.ApprovalMineParam;
|
||||
import com.gxwebsoft.approval.param.ApprovalTaskListParam;
|
||||
import com.gxwebsoft.approval.support.ApprovalApprover;
|
||||
@@ -99,6 +100,30 @@ public class ApprovalInstanceService {
|
||||
return inTransaction(() -> createDraft(param, user, definition, organizationId, organizationName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建或更新内部协调单草稿,返回实例id。
|
||||
*
|
||||
* <p>报送部门与经办人取登录态;受理部门必须是启用组织,保存时即校验。</p>
|
||||
*/
|
||||
public Long saveCoordination(ApprovalCoordinationParam param, User user) {
|
||||
validateCoordination(param);
|
||||
ApprovalOrganizationVO acceptOrganization = directoryService.getOrganization(param.getAcceptOrgId());
|
||||
if (acceptOrganization == null) {
|
||||
throw new BusinessException("受理部门不存在或已停用");
|
||||
}
|
||||
Integer organizationId = user.getOrganizationId();
|
||||
ApprovalOrganizationVO organization = directoryService.getOrganization(organizationId);
|
||||
String organizationName = organization == null ? null : organization.getOrganizationName();
|
||||
|
||||
if (param.getId() != null) {
|
||||
return inTransaction(() ->
|
||||
updateCoordinationDraft(param, user, organizationId, organizationName, acceptOrganization));
|
||||
}
|
||||
ApprovalFlowDefinition definition = flowService.getOrCreate(ApprovalConstants.FLOW_COORDINATION_INTERNAL);
|
||||
return inTransaction(() ->
|
||||
createCoordinationDraft(param, user, definition, organizationId, organizationName, acceptOrganization));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交或重提,返回节点缺配提示;正常提交返回 null。
|
||||
*/
|
||||
@@ -113,7 +138,14 @@ public class ApprovalInstanceService {
|
||||
if (!ApprovalConstants.EDITABLE_STATUSES.contains(instance.getStatus())) {
|
||||
throw new BusinessException(submitBlockedMessage(instance.getStatus()));
|
||||
}
|
||||
if (completionForm(instanceId) == null) {
|
||||
boolean coordination = ApprovalConstants.DOC_COORDINATION.equals(instance.getDocType());
|
||||
ApprovalCoordinationForm coordinationForm = null;
|
||||
if (coordination) {
|
||||
coordinationForm = coordinationForm(instanceId);
|
||||
if (coordinationForm == null) {
|
||||
throw new BusinessException("单据内容不存在");
|
||||
}
|
||||
} else if (completionForm(instanceId) == null) {
|
||||
throw new BusinessException("单据内容不存在");
|
||||
}
|
||||
|
||||
@@ -130,6 +162,10 @@ public class ApprovalInstanceService {
|
||||
if (leaders.isEmpty()) {
|
||||
throw new BusinessException("报送部门负责人角色缺配");
|
||||
}
|
||||
ApprovalOrganizationVO acceptOrganization = null;
|
||||
if (coordination) {
|
||||
acceptOrganization = preflightAcceptOrganization(coordinationForm);
|
||||
}
|
||||
if (directoryService.listRoleMembers(ApprovalConstants.ROLE_ARCHIVIST).isEmpty()) {
|
||||
throw new BusinessException("收文员角色缺配");
|
||||
}
|
||||
@@ -141,11 +177,30 @@ public class ApprovalInstanceService {
|
||||
.map(this::toApprover)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return inTransaction(() -> doSubmit(instanceId, user, organization, approvers));
|
||||
ApprovalOrganizationVO acceptOrg = acceptOrganization;
|
||||
return inTransaction(() -> doSubmit(instanceId, user, organization, acceptOrg, approvers));
|
||||
}
|
||||
|
||||
/**
|
||||
* 协调单提交预检:受理部门负责人角色、受理部门停用。
|
||||
*/
|
||||
private ApprovalOrganizationVO preflightAcceptOrganization(ApprovalCoordinationForm form) {
|
||||
Integer acceptOrgId = form.getAcceptOrgId();
|
||||
if (acceptOrgId == null || acceptOrgId <= 0) {
|
||||
throw new BusinessException("请选择受理部门");
|
||||
}
|
||||
if (directoryService.listRoleMembers(ApprovalConstants.departmentLeaderRoleCode(acceptOrgId)).isEmpty()) {
|
||||
throw new BusinessException("受理部门负责人角色缺配");
|
||||
}
|
||||
ApprovalOrganizationVO acceptOrganization = directoryService.getOrganization(acceptOrgId);
|
||||
if (acceptOrganization == null) {
|
||||
throw new BusinessException("受理部门已停用");
|
||||
}
|
||||
return acceptOrganization;
|
||||
}
|
||||
|
||||
private String doSubmit(Long instanceId, User user, ApprovalOrganizationVO organization,
|
||||
List<ApprovalApprover> approvers) {
|
||||
ApprovalOrganizationVO acceptOrganization, List<ApprovalApprover> approvers) {
|
||||
ApprovalInstance locked = engineService.lockInstance(instanceId);
|
||||
if (!locked.getReporterId().equals(user.getUserId())
|
||||
|| !ApprovalConstants.EDITABLE_STATUSES.contains(locked.getStatus())) {
|
||||
@@ -154,8 +209,15 @@ public class ApprovalInstanceService {
|
||||
boolean firstSubmit = StrUtil.isBlank(locked.getBizNo());
|
||||
locked.setReportOrgId(organization.getOrganizationId());
|
||||
locked.setReportOrgName(organization.getOrganizationName());
|
||||
if (acceptOrganization != null) {
|
||||
locked.setAcceptOrgId(acceptOrganization.getOrganizationId());
|
||||
locked.setAcceptOrgName(acceptOrganization.getOrganizationName());
|
||||
}
|
||||
if (firstSubmit) {
|
||||
locked.setBizNo(noService.nextNo(ApprovalConstants.NO_PREFIX_COMPLETION));
|
||||
boolean coordination = ApprovalConstants.DOC_COORDINATION.equals(locked.getDocType());
|
||||
locked.setBizNo(noService.nextNo(coordination
|
||||
? ApprovalConstants.NO_PREFIX_COORDINATION
|
||||
: ApprovalConstants.NO_PREFIX_COMPLETION));
|
||||
}
|
||||
locked.setStatus(ApprovalConstants.STATUS_PENDING);
|
||||
locked.setRound((locked.getRound() == null ? 0 : locked.getRound()) + 1);
|
||||
@@ -305,7 +367,7 @@ public class ApprovalInstanceService {
|
||||
throw new BusinessException("无权操作该单据");
|
||||
}
|
||||
if (!ApprovalConstants.EDITABLE_STATUSES.contains(instance.getStatus())) {
|
||||
throw new BusinessException("当前状态不可修改");
|
||||
throw new BusinessException(ApprovalConstants.writeBlockedMessage(instance.getStatus()));
|
||||
}
|
||||
ApprovalCompletionForm form = completionForm(instance.getId());
|
||||
if (form == null) {
|
||||
@@ -323,6 +385,79 @@ public class ApprovalInstanceService {
|
||||
return instance.getId();
|
||||
}
|
||||
|
||||
private Long createCoordinationDraft(ApprovalCoordinationParam param, User user,
|
||||
ApprovalFlowDefinition definition,
|
||||
Integer organizationId, String organizationName,
|
||||
ApprovalOrganizationVO acceptOrganization) {
|
||||
ApprovalInstance instance = new ApprovalInstance();
|
||||
instance.setFlowCode(ApprovalConstants.FLOW_COORDINATION_INTERNAL);
|
||||
instance.setDefinitionId(definition.getId());
|
||||
instance.setDefinitionVersion(definition.getVersion());
|
||||
instance.setDocType(ApprovalConstants.DOC_COORDINATION);
|
||||
instance.setBizId(0L);
|
||||
instance.setStatus(ApprovalConstants.STATUS_DRAFT);
|
||||
instance.setRound(0);
|
||||
instance.setReporterId(user.getUserId());
|
||||
instance.setReporterName(displayName(user));
|
||||
instance.setReportOrgId(organizationId);
|
||||
instance.setReportOrgName(organizationName);
|
||||
instanceMapper.insert(instance);
|
||||
|
||||
ApprovalCoordinationForm form = new ApprovalCoordinationForm();
|
||||
form.setInstanceId(instance.getId());
|
||||
form.setAcceptOrgId(acceptOrganization.getOrganizationId());
|
||||
form.setAcceptOrgName(acceptOrganization.getOrganizationName());
|
||||
form.setTitle(param.getTitle().trim());
|
||||
form.setContent(param.getContent().trim());
|
||||
form.setRemark(normalizeRemark(param.getRemark()));
|
||||
coordinationFormMapper.insert(form);
|
||||
|
||||
instance.setBizId(form.getId());
|
||||
instanceMapper.updateById(instance);
|
||||
return instance.getId();
|
||||
}
|
||||
|
||||
private Long updateCoordinationDraft(ApprovalCoordinationParam param, User user,
|
||||
Integer organizationId, String organizationName,
|
||||
ApprovalOrganizationVO acceptOrganization) {
|
||||
ApprovalInstance instance = instanceMapper.selectById(param.getId());
|
||||
if (instance == null) {
|
||||
throw new BusinessException("单据不存在");
|
||||
}
|
||||
if (!ApprovalConstants.DOC_COORDINATION.equals(instance.getDocType())) {
|
||||
throw new BusinessException("单据类型不匹配");
|
||||
}
|
||||
if (!instance.getReporterId().equals(user.getUserId())) {
|
||||
throw new BusinessException("无权操作该单据");
|
||||
}
|
||||
if (!ApprovalConstants.EDITABLE_STATUSES.contains(instance.getStatus())) {
|
||||
throw new BusinessException("当前状态不可修改");
|
||||
}
|
||||
ApprovalCoordinationForm form = coordinationForm(instance.getId());
|
||||
if (form == null) {
|
||||
throw new BusinessException("单据内容不存在");
|
||||
}
|
||||
form.setAcceptOrgId(acceptOrganization.getOrganizationId());
|
||||
form.setAcceptOrgName(acceptOrganization.getOrganizationName());
|
||||
form.setTitle(param.getTitle().trim());
|
||||
form.setContent(param.getContent().trim());
|
||||
form.setRemark(normalizeRemark(param.getRemark()));
|
||||
coordinationFormMapper.updateById(form);
|
||||
|
||||
instance.setReportOrgId(organizationId);
|
||||
instance.setReportOrgName(organizationName);
|
||||
instanceMapper.updateById(instance);
|
||||
return instance.getId();
|
||||
}
|
||||
|
||||
private String normalizeRemark(String remark) {
|
||||
if (remark == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = remark.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
private ApprovalInstanceVO baseVO(ApprovalInstance instance) {
|
||||
ApprovalInstanceVO vo = new ApprovalInstanceVO();
|
||||
vo.setId(instance.getId());
|
||||
@@ -502,6 +637,33 @@ public class ApprovalInstanceService {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateCoordination(ApprovalCoordinationParam param) {
|
||||
if (param == null) {
|
||||
throw new BusinessException("参数不能为空");
|
||||
}
|
||||
if (param.getAcceptOrgId() == null || param.getAcceptOrgId() <= 0) {
|
||||
throw new BusinessException("请选择受理部门");
|
||||
}
|
||||
String title = param.getTitle() == null ? "" : param.getTitle().trim();
|
||||
if (title.isEmpty()) {
|
||||
throw new BusinessException("标题不能为空");
|
||||
}
|
||||
if (title.length() > 100) {
|
||||
throw new BusinessException("标题不能超过100字");
|
||||
}
|
||||
String content = param.getContent() == null ? "" : param.getContent().trim();
|
||||
if (content.isEmpty()) {
|
||||
throw new BusinessException("内容不能为空");
|
||||
}
|
||||
if (content.length() > 2000) {
|
||||
throw new BusinessException("内容不能超过2000字");
|
||||
}
|
||||
String remark = normalizeRemark(param.getRemark());
|
||||
if (remark != null && remark.length() > 500) {
|
||||
throw new BusinessException("备注不能超过500字");
|
||||
}
|
||||
}
|
||||
|
||||
private String submitBlockedMessage(String status) {
|
||||
if (ApprovalConstants.STATUS_PENDING.equals(status)) {
|
||||
return "单据正在审批中,不能重复提交";
|
||||
|
||||
@@ -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;
|
||||
@@ -75,6 +77,45 @@ public class ApprovalTaskService {
|
||||
return new Result(instance.getId(), warning);
|
||||
}
|
||||
|
||||
/**
|
||||
* 存档确认:CONFIRM 任务只能确认(无意见),同节点同轮全部完成后实例进入审批完成,
|
||||
* 并激活归档节点;归档节点缺配时实例停在审批中并提示管理员补配置。
|
||||
*/
|
||||
public Result confirm(Long taskId, User user) {
|
||||
ApprovalTask task = requireTask(taskId, user);
|
||||
if (!ApprovalConstants.NODE_CONFIRM.equals(task.getNodeType())) {
|
||||
throw new BusinessException("当前任务不支持存档确认");
|
||||
}
|
||||
ApprovalInstance instance = instanceMapper.selectById(task.getInstanceId());
|
||||
if (instance == null) {
|
||||
throw new BusinessException("单据不存在");
|
||||
}
|
||||
ensureNotArchived(instance);
|
||||
ApprovalNode next = engineService.nextNode(instance, task.getNodeKey());
|
||||
List<ApprovalApprover> approvers =
|
||||
next == null ? Collections.emptyList() : resolveApprovers(instance, next);
|
||||
|
||||
String warning = inTransaction(() -> doConfirm(taskId, task, user, next, approvers));
|
||||
return new Result(instance.getId(), warning);
|
||||
}
|
||||
|
||||
/**
|
||||
* 归档:ARCHIVE 任务或签,处理后取消同节点其余待办,实例进入只读终态。
|
||||
*/
|
||||
public Result archive(Long taskId, User user) {
|
||||
ApprovalTask task = requireTask(taskId, user);
|
||||
if (!ApprovalConstants.NODE_ARCHIVE.equals(task.getNodeType())) {
|
||||
throw new BusinessException("当前任务不支持归档");
|
||||
}
|
||||
ApprovalInstance instance = instanceMapper.selectById(task.getInstanceId());
|
||||
if (instance == null) {
|
||||
throw new BusinessException("单据不存在");
|
||||
}
|
||||
ensureNotArchived(instance);
|
||||
inTransaction(() -> doArchive(taskId, task, user));
|
||||
return new Result(instance.getId(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 驳回:意见必填,同轮待办全部取消,实例回到报送人。
|
||||
*/
|
||||
@@ -101,6 +142,9 @@ public class ApprovalTaskService {
|
||||
if (user == null || !instance.getReporterId().equals(user.getUserId())) {
|
||||
throw new BusinessException("无权操作该单据");
|
||||
}
|
||||
if (ApprovalConstants.STATUS_ARCHIVED.equals(instance.getStatus())) {
|
||||
throw new BusinessException("单据已归档,不能修改");
|
||||
}
|
||||
if (!ApprovalConstants.STATUS_PENDING.equals(instance.getStatus())) {
|
||||
throw new BusinessException("当前状态不可撤回");
|
||||
}
|
||||
@@ -109,6 +153,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());
|
||||
}
|
||||
@@ -131,6 +197,67 @@ public class ApprovalTaskService {
|
||||
return created == 0 ? "当前节点缺少审批人,请联系管理员补配置" : null;
|
||||
}
|
||||
|
||||
private String doConfirm(Long taskId, ApprovalTask task, User user,
|
||||
ApprovalNode next, List<ApprovalApprover> approvers) {
|
||||
ApprovalInstance locked = engineService.lockInstance(task.getInstanceId());
|
||||
ensureNotArchived(locked);
|
||||
ensureTaskHandleable(task, locked);
|
||||
if (updatePendingTask(taskId, ApprovalConstants.TASK_DONE, null) == 0) {
|
||||
throw new BusinessException("任务已处理");
|
||||
}
|
||||
engineService.addRecord(locked, taskId, task.getNodeKey(), user,
|
||||
ApprovalConstants.ACTION_CONFIRM, null);
|
||||
Integer remaining = taskMapper.selectCount(new LambdaQueryWrapper<ApprovalTask>()
|
||||
.eq(ApprovalTask::getInstanceId, locked.getId())
|
||||
.eq(ApprovalTask::getRound, locked.getRound())
|
||||
.eq(ApprovalTask::getNodeKey, task.getNodeKey())
|
||||
.eq(ApprovalTask::getStatus, ApprovalConstants.TASK_PENDING));
|
||||
if (remaining != null && remaining > 0) {
|
||||
return null;
|
||||
}
|
||||
if (next == null) {
|
||||
return "当前节点缺少审批人,请联系管理员补配置";
|
||||
}
|
||||
int created = engineService.moveToNode(locked, next, locked.getRound(), approvers);
|
||||
if (created == 0) {
|
||||
return "当前节点缺少审批人,请联系管理员补配置";
|
||||
}
|
||||
int rows = instanceMapper.update(null, new UpdateWrapper<ApprovalInstance>()
|
||||
.eq("id", locked.getId())
|
||||
.eq("status", ApprovalConstants.STATUS_PENDING)
|
||||
.set("status", ApprovalConstants.STATUS_APPROVED)
|
||||
.set("approve_time", LocalDateTime.now()));
|
||||
if (rows == 0) {
|
||||
throw new BusinessException("单据状态已变化,请刷新后重试");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void doArchive(Long taskId, ApprovalTask task, User user) {
|
||||
ApprovalInstance locked = engineService.lockInstance(task.getInstanceId());
|
||||
ensureNotArchived(locked);
|
||||
if (!ApprovalConstants.STATUS_APPROVED.equals(locked.getStatus())) {
|
||||
throw new BusinessException("单据状态已变化,请刷新后重试");
|
||||
}
|
||||
ensureTaskCurrent(task, locked);
|
||||
if (updatePendingTask(taskId, ApprovalConstants.TASK_DONE, null) == 0) {
|
||||
throw new BusinessException("任务已处理");
|
||||
}
|
||||
engineService.cancelPendingTasks(locked.getId(), locked.getRound(), taskId);
|
||||
engineService.addRecord(locked, taskId, task.getNodeKey(), user,
|
||||
ApprovalConstants.ACTION_ARCHIVE, null);
|
||||
int rows = instanceMapper.update(null, new UpdateWrapper<ApprovalInstance>()
|
||||
.eq("id", locked.getId())
|
||||
.eq("status", ApprovalConstants.STATUS_APPROVED)
|
||||
.set("status", ApprovalConstants.STATUS_ARCHIVED)
|
||||
.set("archive_time", LocalDateTime.now())
|
||||
.set("archivist_id", user.getUserId())
|
||||
.set("archivist_name", displayName(user)));
|
||||
if (rows == 0) {
|
||||
throw new BusinessException("单据状态已变化,请刷新后重试");
|
||||
}
|
||||
}
|
||||
|
||||
private void doReject(Long taskId, ApprovalTask task, String opinion, User user) {
|
||||
ApprovalInstance locked = engineService.lockInstance(task.getInstanceId());
|
||||
ensureTaskHandleable(task, locked);
|
||||
@@ -171,6 +298,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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务必须属于实例当前轮次、当前节点,否则视为已被处理或已失效。
|
||||
*/
|
||||
@@ -178,12 +357,22 @@ public class ApprovalTaskService {
|
||||
if (!ApprovalConstants.STATUS_PENDING.equals(instance.getStatus())) {
|
||||
throw new BusinessException("单据状态已变化,请刷新后重试");
|
||||
}
|
||||
ensureTaskCurrent(task, instance);
|
||||
}
|
||||
|
||||
private void ensureTaskCurrent(ApprovalTask task, ApprovalInstance instance) {
|
||||
if (!task.getRound().equals(instance.getRound())
|
||||
|| !task.getNodeKey().equals(instance.getCurrentNodeKey())) {
|
||||
throw new BusinessException("任务已处理");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureNotArchived(ApprovalInstance instance) {
|
||||
if (ApprovalConstants.STATUS_ARCHIVED.equals(instance.getStatus())) {
|
||||
throw new BusinessException("单据已归档,不能修改");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureNoHandledTask(ApprovalInstance instance) {
|
||||
Integer handled = taskMapper.selectCount(new LambdaQueryWrapper<ApprovalTask>()
|
||||
.eq(ApprovalTask::getInstanceId, instance.getId())
|
||||
@@ -288,6 +477,16 @@ public class ApprovalTaskService {
|
||||
return user.getUsername();
|
||||
}
|
||||
|
||||
private String displayName(User user) {
|
||||
if (StrUtil.isNotBlank(user.getRealName())) {
|
||||
return user.getRealName();
|
||||
}
|
||||
if (StrUtil.isNotBlank(user.getNickname())) {
|
||||
return user.getNickname();
|
||||
}
|
||||
return user.getUsername();
|
||||
}
|
||||
|
||||
private String normalizeOpinion(String opinion) {
|
||||
if (opinion == null) {
|
||||
return null;
|
||||
@@ -310,6 +509,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