feat: code about my processed

This commit is contained in:
建国
2026-05-07 18:57:49 +08:00
parent 50ae55deb9
commit b1c9307559
8 changed files with 319 additions and 38 deletions

View File

@@ -1,15 +1,22 @@
package cn.cordys.crm.approval.controller;
import cn.cordys.crm.approval.dto.response.ApprovalTodoResponse;
import cn.cordys.common.pager.Pager;
import cn.cordys.crm.approval.dto.request.ApprovalProcessedPageRequest;
import cn.cordys.crm.approval.dto.request.ApprovalTodoPageRequest;
import cn.cordys.crm.approval.dto.response.ApprovalTodoItemResponse;
import cn.cordys.crm.approval.service.ApprovalTodoService;
import cn.cordys.security.SessionUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/approval-todo")
@Tag(name = "审核代办")
@@ -18,9 +25,15 @@ public class ApprovalTodoController {
@Resource
private ApprovalTodoService approvalTodoService;
@GetMapping("/list")
@Operation(summary = "审核代办-当前用户待审核资源")
public ApprovalTodoResponse todo() {
return approvalTodoService.getTodoList(SessionUtils.getUserId());
@PostMapping("/pending/page")
@Operation(summary = "审核代办-当前用户待审核资源分页")
public Pager<List<ApprovalTodoItemResponse>> todo(@Validated @RequestBody ApprovalTodoPageRequest request) {
return approvalTodoService.getTodoPage(request, SessionUtils.getUserId());
}
@PostMapping("/processed/page")
@Operation(summary = "审核代办-当前用户已处理审批分页")
public Pager<List<ApprovalTodoItemResponse>> processedPage(@Validated @RequestBody ApprovalProcessedPageRequest request) {
return approvalTodoService.getProcessedPage(request, SessionUtils.getUserId());
}
}

View File

@@ -0,0 +1,10 @@
package cn.cordys.crm.approval.dto.request;
import cn.cordys.common.dto.BasePageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class ApprovalProcessedPageRequest extends BasePageRequest {
}

View File

@@ -0,0 +1,14 @@
package cn.cordys.crm.approval.dto.request;
import cn.cordys.common.dto.BasePageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class ApprovalTodoPageRequest extends BasePageRequest {
@Schema(description = "资源类型过滤ALL/QUOTATION/CONTRACT/ORDER/INVOICE")
private String resourceType = "ALL";
}

View File

@@ -20,4 +20,10 @@ public class ApprovalTodoItemResponse {
@Schema(description = "提交时间")
private Long submitTime;
@Schema(description = "审批操作")
private String approvalOperation;
@Schema(description = "数据结果")
private String dataResult;
}

View File

@@ -1,11 +1,14 @@
package cn.cordys.crm.approval.service;
import cn.cordys.common.pager.PageUtils;
import cn.cordys.common.pager.Pager;
import cn.cordys.crm.approval.dto.request.ApprovalProcessedPageRequest;
import cn.cordys.crm.approval.dto.request.ApprovalTodoPageRequest;
import cn.cordys.crm.approval.constants.ApprovalFormTypeEnum;
import cn.cordys.crm.approval.constants.ApprovalState;
import cn.cordys.crm.approval.domain.ApprovalInstance;
import cn.cordys.crm.approval.domain.ApprovalTask;
import cn.cordys.crm.approval.dto.response.ApprovalTodoItemResponse;
import cn.cordys.crm.approval.dto.response.ApprovalTodoResponse;
import cn.cordys.crm.contract.domain.Contract;
import cn.cordys.crm.contract.domain.ContractInvoice;
import cn.cordys.crm.opportunity.domain.OpportunityQuotation;
@@ -13,6 +16,8 @@ import cn.cordys.crm.order.domain.Order;
import cn.cordys.crm.system.domain.User;
import cn.cordys.mybatis.BaseMapper;
import cn.cordys.mybatis.lambda.LambdaQueryWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
@@ -40,20 +45,39 @@ public class ApprovalTodoService {
@Resource
private BaseMapper<ContractInvoice> invoiceMapper;
public ApprovalTodoResponse getTodoList(String userId) {
// 初始化分类响应对象
ApprovalTodoResponse response = initResponse();
public Pager<List<ApprovalTodoItemResponse>> getTodoPage(ApprovalTodoPageRequest request, String userId) {
// 在未登录场景下直接返回空分页数据
if (StringUtils.isBlank(userId)) {
return response;
return new Pager<>(Collections.<ApprovalTodoItemResponse>emptyList(), 0, request.getPageSize(), request.getCurrent());
}
// 查询当前用户待审批任务
// 解析资源类型过滤参数,支持 ALL 或具体类型
ApprovalFormTypeEnum filterType = parseFilterType(request.getResourceType());
if (!isAllType(request.getResourceType()) && filterType == null) {
return new Pager<>(Collections.<ApprovalTodoItemResponse>emptyList(), 0, request.getPageSize(), request.getCurrent());
}
// 在指定类型场景下先预查实例ID缩小任务查询范围。
List<String> scopedInstanceIds = Collections.emptyList();
if (filterType != null) {
scopedInstanceIds = loadInstanceIdsByType(filterType);
if (scopedInstanceIds.isEmpty()) {
return new Pager<>(Collections.<ApprovalTodoItemResponse>emptyList(), 0, request.getPageSize(), request.getCurrent());
}
}
// 分页查询当前用户待审批任务,并按更新时间倒序返回。
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize());
LambdaQueryWrapper<ApprovalTask> taskWrapper = new LambdaQueryWrapper<>();
taskWrapper.eq(ApprovalTask::getApproverId, userId)
.eq(ApprovalTask::getStatus, ApprovalState.PENDING.getId());
.eq(ApprovalTask::getTaskStatus, ApprovalState.PENDING.getId())
.orderByDesc(ApprovalTask::getUpdateTime);
if (!scopedInstanceIds.isEmpty()) {
taskWrapper.in(ApprovalTask::getInstanceId, scopedInstanceIds);
}
List<ApprovalTask> tasks = approvalTaskMapper.selectListByLambda(taskWrapper);
if (tasks.isEmpty()) {
return response;
return PageUtils.setPageInfo(page, Collections.<ApprovalTodoItemResponse>emptyList());
}
// 收集任务对应审批实例ID。
@@ -63,14 +87,14 @@ public class ApprovalTodoService {
.distinct()
.toList();
if (instanceIds.isEmpty()) {
return response;
return PageUtils.setPageInfo(page, Collections.<ApprovalTodoItemResponse>emptyList());
}
// 批量查询审批实例并建立映射。
Map<String, ApprovalInstance> instanceMap = approvalInstanceMapper.selectByIds(instanceIds).stream()
.collect(Collectors.toMap(ApprovalInstance::getId, Function.identity(), (prev, next) -> prev));
if (instanceMap.isEmpty()) {
return response;
return PageUtils.setPageInfo(page, Collections.<ApprovalTodoItemResponse>emptyList());
}
// 仅保留当前节点上的待审批任务,避免混入历史节点任务。
@@ -81,7 +105,7 @@ public class ApprovalTodoService {
})
.toList();
if (currentNodeTasks.isEmpty()) {
return response;
return PageUtils.setPageInfo(page, Collections.<ApprovalTodoItemResponse>emptyList());
}
// 预加载申请人名称和资源名称映射,减少循环内查询。
@@ -92,24 +116,24 @@ public class ApprovalTodoService {
Map<String, String> submitterMap = loadSubmitterNameMap(instances);
Map<ApprovalFormTypeEnum, Map<String, String>> resourceNameMap = loadResourceNameMap(instances);
// 逐条组装待办并按资源类型归类返回
// 逐条组装待办分页数据并去重实例
List<ApprovalTodoItemResponse> list = new ArrayList<>(currentNodeTasks.size());
Set<String> processedInstanceIds = new HashSet<>();
for (ApprovalTask task : currentNodeTasks) {
// 同一实例只返回一条代办记录。
if (!processedInstanceIds.add(task.getInstanceId())) {
continue;
}
ApprovalInstance instance = instanceMap.get(task.getInstanceId());
// 跳过异常实例数据。
if (instance == null) {
continue;
}
ApprovalFormTypeEnum formType = parseFormType(instance.getType());
// 跳过不支持的资源类型。
if (formType == null) {
continue;
}
// 获取资源名称并构建待办项。
if (filterType != null && filterType != formType) {
continue;
}
String resourceName = Optional.ofNullable(resourceNameMap.get(formType))
.map(map -> map.get(instance.getResourceId()))
.orElse(StringUtils.EMPTY);
@@ -119,25 +143,78 @@ public class ApprovalTodoService {
item.setResourceType(formType.name());
item.setApplicant(submitterMap.get(instance.getSubmitterId()));
item.setSubmitTime(instance.getSubmitTime());
switch (formType) {
case QUOTATION -> response.getQuotation().add(item);
case CONTRACT -> response.getContract().add(item);
case ORDER -> response.getOrder().add(item);
case INVOICE -> response.getInvoice().add(item);
}
item.setApprovalOperation(task.getTaskStatus());
item.setDataResult(instance.getResult());
list.add(item);
}
// 返回分类后的待办集合
return response;
// 返回分页待办列表
return PageUtils.setPageInfo(page, list);
}
private ApprovalTodoResponse initResponse() {
// 初始化四类待办列表,避免空指针
ApprovalTodoResponse response = new ApprovalTodoResponse();
response.setQuotation(new ArrayList<>());
response.setContract(new ArrayList<>());
response.setOrder(new ArrayList<>());
response.setInvoice(new ArrayList<>());
return response;
public Pager<List<ApprovalTodoItemResponse>> getProcessedPage(ApprovalProcessedPageRequest request, String userId) {
// 在未登录场景下直接返回空分页数据
if (StringUtils.isBlank(userId)) {
return new Pager<>(Collections.<ApprovalTodoItemResponse>emptyList(), 0, request.getPageSize(), request.getCurrent());
}
// 分页查询当前用户已处理任务,并按更新时间倒序返回最新处理记录。
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize());
LambdaQueryWrapper<ApprovalTask> taskWrapper = new LambdaQueryWrapper<>();
taskWrapper.eq(ApprovalTask::getApproverId, userId)
.nq(ApprovalTask::getTaskStatus, ApprovalState.PENDING.getId())
.orderByDesc(ApprovalTask::getUpdateTime);
List<ApprovalTask> tasks = approvalTaskMapper.selectListByLambda(taskWrapper);
if (tasks.isEmpty()) {
return PageUtils.setPageInfo(page, Collections.<ApprovalTodoItemResponse>emptyList());
}
// 批量加载任务对应审批实例,避免循环查询实例数据。
List<String> instanceIds = tasks.stream()
.map(ApprovalTask::getInstanceId)
.filter(StringUtils::isNotBlank)
.distinct()
.toList();
Map<String, ApprovalInstance> instanceMap = approvalInstanceMapper.selectByIds(instanceIds).stream()
.collect(Collectors.toMap(ApprovalInstance::getId, Function.identity(), (prev, next) -> prev));
if (instanceMap.isEmpty()) {
return PageUtils.setPageInfo(page, Collections.<ApprovalTodoItemResponse>emptyList());
}
// 预加载申请人名称和资源名称映射,减少组装阶段的重复计算。
List<ApprovalInstance> instances = tasks.stream()
.map(task -> instanceMap.get(task.getInstanceId()))
.filter(Objects::nonNull)
.toList();
Map<String, String> submitterMap = loadSubmitterNameMap(instances);
Map<ApprovalFormTypeEnum, Map<String, String>> resourceNameMap = loadResourceNameMap(instances);
// 逐条组装已处理审批数据并回填审批操作和数据结果字段。
List<ApprovalTodoItemResponse> list = new ArrayList<>(tasks.size());
for (ApprovalTask task : tasks) {
ApprovalInstance instance = instanceMap.get(task.getInstanceId());
if (instance == null) {
continue;
}
ApprovalFormTypeEnum formType = parseFormType(instance.getType());
if (formType == null) {
continue;
}
String resourceName = Optional.ofNullable(resourceNameMap.get(formType))
.map(map -> map.get(instance.getResourceId()))
.orElse(StringUtils.EMPTY);
ApprovalTodoItemResponse item = new ApprovalTodoItemResponse();
item.setResourceId(instance.getResourceId());
item.setResourceName(resourceName);
item.setResourceType(formType.name());
item.setApplicant(submitterMap.get(instance.getSubmitterId()));
item.setSubmitTime(instance.getSubmitTime());
item.setApprovalOperation(task.getTaskStatus());
item.setDataResult(instance.getResult());
list.add(item);
}
// 返回分页结果,分页元信息沿用 PageHelper 查询结果。
return PageUtils.setPageInfo(page, list);
}
private Map<String, String> loadSubmitterNameMap(List<ApprovalInstance> instances) {
@@ -203,4 +280,31 @@ public class ApprovalTodoService {
default -> null;
};
}
private boolean isAllType(String resourceType) {
return StringUtils.isBlank(resourceType) || StringUtils.equalsIgnoreCase(resourceType, "ALL");
}
private ApprovalFormTypeEnum parseFilterType(String resourceType) {
if (isAllType(resourceType)) {
return null;
}
return parseFormType(resourceType);
}
private List<String> loadInstanceIdsByType(ApprovalFormTypeEnum formType) {
List<String> aliases = switch (formType) {
case QUOTATION -> List.of("quotation", "quote");
case CONTRACT -> List.of("contract");
case ORDER -> List.of("order");
case INVOICE -> List.of("invoice");
};
LambdaQueryWrapper<ApprovalInstance> wrapper = new LambdaQueryWrapper<>();
wrapper.in(ApprovalInstance::getType, aliases);
return approvalInstanceMapper.selectListByLambda(wrapper).stream()
.map(ApprovalInstance::getId)
.filter(StringUtils::isNotBlank)
.distinct()
.toList();
}
}

View File

@@ -0,0 +1,102 @@
package cn.cordys.crm.approval.controller;
import cn.cordys.common.pager.Pager;
import cn.cordys.crm.approval.dto.response.ApprovalTodoItemResponse;
import cn.cordys.crm.base.BaseTest;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.web.servlet.MvcResult;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class ApprovalTodoControllerTests extends BaseTest {
private static final String TODO_LIST = "/pending/page";
private static final String PROCESSED_PAGE = "/processed/page";
@Override
protected String getBasePath() {
return "/approval-todo";
}
@Sql(
scripts = {"/dml/init_approval_todo_list_test.sql"},
config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED),
executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD
)
@Test
@Order(1)
void testTodoListPageWithAllType() throws Exception {
Map<String, Object> request = new HashMap<>();
request.put("current", 1);
request.put("pageSize", 10);
request.put("resourceType", "ALL");
MvcResult mvcResult = requestPostWithOkAndReturn(TODO_LIST, request);
Pager<List<ApprovalTodoItemResponse>> pager = getPageResult(mvcResult, ApprovalTodoItemResponse.class);
Assertions.assertNotNull(pager);
Assertions.assertEquals(3, pager.getTotal());
Assertions.assertEquals(2, pager.getList().size());
Assertions.assertTrue(pager.getList().stream().allMatch(item -> StringUtils.equals("PENDING", item.getApprovalOperation())));
}
@Sql(
scripts = {"/dml/init_approval_todo_list_test.sql"},
config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED),
executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD
)
@Test
@Order(2)
void testTodoListPageWithContractType() throws Exception {
Map<String, Object> request = new HashMap<>();
request.put("current", 1);
request.put("pageSize", 10);
request.put("resourceType", "CONTRACT");
MvcResult mvcResult = requestPostWithOkAndReturn(TODO_LIST, request);
Pager<List<ApprovalTodoItemResponse>> pager = getPageResult(mvcResult, ApprovalTodoItemResponse.class);
Assertions.assertNotNull(pager);
Assertions.assertEquals(2, pager.getTotal());
Assertions.assertEquals(1, pager.getList().size());
Assertions.assertEquals("CONTRACT", pager.getList().getFirst().getResourceType());
}
@Sql(
scripts = {"/dml/init_approval_todo_processed_test.sql"},
config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED),
executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD
)
@Test
@Order(3)
void testProcessedPage() throws Exception {
Map<String, Object> request = new HashMap<>();
request.put("current", 1);
request.put("pageSize", 10);
MvcResult mvcResult = requestPostWithOkAndReturn(PROCESSED_PAGE, request);
Pager<List<ApprovalTodoItemResponse>> pager = getPageResult(mvcResult, ApprovalTodoItemResponse.class);
Assertions.assertNotNull(pager);
Assertions.assertEquals(1, pager.getTotal());
Assertions.assertEquals(1, pager.getList().size());
ApprovalTodoItemResponse item = pager.getList().getFirst();
Assertions.assertEquals("todo_processed_resource_001", item.getResourceId());
Assertions.assertEquals("CONTRACT", item.getResourceType());
Assertions.assertTrue(StringUtils.isNotBlank(item.getApplicant()));
Assertions.assertEquals("APPROVED", item.getApprovalOperation());
Assertions.assertEquals("SIGNED", item.getDataResult());
}
}

View File

@@ -0,0 +1,20 @@
DELETE FROM approval_task WHERE id IN (
'todo_list_task_contract',
'todo_list_task_quote',
'todo_list_task_old_node'
);
DELETE FROM approval_instance WHERE id IN (
'todo_list_inst_contract',
'todo_list_inst_quote'
);
INSERT INTO approval_instance (`id`, `flow_id`, `type`, `resource_id`, `submitter_id`, `current_node_id`, `approval_status`, `submit_time`, `approval_time`, `result`, `create_time`, `update_time`, `create_user`, `update_user`)
VALUES
('todo_list_inst_contract', 'approval_flow_test_001', 'contract', 'todo_list_resource_contract', 'admin', 'node_contract_current', 'APPROVING', 1736240043609, NULL, NULL, 1736240043609, 1736240043609, 'admin', 'admin'),
('todo_list_inst_quote', 'approval_flow_test_001', 'quotation', 'todo_list_resource_quote', 'admin', 'node_quote_current', 'APPROVING', 1736241043609, NULL, NULL, 1736241043609, 1736241043609, 'admin', 'admin');
INSERT INTO approval_task (`id`, `node_id`, `instance_id`, `approver_id`, `task_status`, `is_add_sign`, `is_return`, `is_cc`, `create_time`, `update_time`, `create_user`, `update_user`)
VALUES
('todo_list_task_contract', 'node_contract_current', 'todo_list_inst_contract', 'admin', 'PENDING', 0, 0, 0, 1736240043609, 1736240043609, 'admin', 'admin'),
('todo_list_task_quote', 'node_quote_current', 'todo_list_inst_quote', 'admin', 'PENDING', 0, 0, 0, 1736241043609, 1736241043609, 'admin', 'admin'),
('todo_list_task_old_node', 'node_contract_old', 'todo_list_inst_contract', 'admin', 'PENDING', 0, 0, 0, 1736242043609, 1736242043609, 'admin', 'admin');

View File

@@ -0,0 +1,12 @@
DELETE FROM approval_task WHERE id IN ('todo_processed_task_001', 'todo_pending_task_001');
DELETE FROM approval_instance WHERE id IN ('todo_processed_inst_001', 'todo_pending_inst_001');
INSERT INTO approval_instance (`id`, `flow_id`, `type`, `resource_id`, `submitter_id`, `current_node_id`, `approval_status`, `submit_time`, `approval_time`, `result`, `create_time`, `update_time`, `create_user`, `update_user`)
VALUES
('todo_processed_inst_001', 'approval_flow_test_001', 'contract', 'todo_processed_resource_001', 'admin', 'node_done', 'APPROVED', 1736240043609, 1736241043609, 'SIGNED', 1736240043609, 1736241043609, 'admin', 'admin'),
('todo_pending_inst_001', 'approval_flow_test_001', 'contract', 'todo_pending_resource_001', 'admin', 'node_wait', 'APPROVING', 1736242043609, NULL, NULL, 1736242043609, 1736242043609, 'admin', 'admin');
INSERT INTO approval_task (`id`, `node_id`, `instance_id`, `approver_id`, `task_status`, `is_add_sign`, `is_return`, `is_cc`, `create_time`, `update_time`, `create_user`, `update_user`)
VALUES
('todo_processed_task_001', 'node_done', 'todo_processed_inst_001', 'admin', 'APPROVED', 0, 0, 0, 1736241043609, 1736241043609, 'admin', 'admin'),
('todo_pending_task_001', 'node_wait', 'todo_pending_inst_001', 'admin', 'PENDING', 0, 0, 0, 1736242043609, 1736242043609, 'admin', 'admin');