diff --git a/docs/plan/2026-07-16-todo-center-task-tracker.md b/docs/plan/2026-07-16-todo-center-task-tracker.md index 1690dd494..a7e4165a6 100644 --- a/docs/plan/2026-07-16-todo-center-task-tracker.md +++ b/docs/plan/2026-07-16-todo-center-task-tracker.md @@ -46,9 +46,9 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重 | B03 | 当前用户查询、已读、完成、取消接口及测试 | P0 | done | 全量依赖编译、9 个专项测试通过 | `f84ec43ce` | | B04 | 前端 API、待办中心页面和路由跳转 | P0 | done | ESLint 和生产构建通过 | 前端 `ff6b890` | | B05 | 首页待办卡片、数量展示和轮询刷新 | P0 | done | ESLint 和生产构建通过 | 前端 `0627061` | -| B06 | 菜单权限 SQL、联调和首期回归 | P0 | done | 菜单 SQL、构建和专项回归通过;待测试库执行 SQL | 本批提交 | -| B07 | 平台采购开票待办接入 | P1 | todo | - | - | -| B08 | 司机开票申请待办接入 | P1 | todo | - | - | +| B06 | 菜单权限 SQL、联调和首期回归 | P0 | done | 菜单 SQL、构建和专项回归通过;待测试库执行 SQL | `974e68cf5` | +| B07 | 平台采购开票待办接入 | P1 | blocked | 仓库内未找到平台采购开票模型、审核状态或接口入口 | - | +| B08 | 司机开票申请待办接入 | P1 | done | 全依赖编译、12 个专项测试通过 | 本批提交 | | B09 | 管理端创建、分配、取消和隐藏能力 | P1 | todo | - | - | | B10 | 归档清理、过期任务和操作审计增强 | P2 | todo | - | - | | B11 | 路由白名单、性能压测和安全专项验证 | P2 | todo | - | - | @@ -121,7 +121,7 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重 - 优先级:P0 - 批次:B06 -- 状态:in_progress +- 状态:done - 当前进展: - 菜单、查询、已读、完成和取消权限 SQL 已完成。 - 列表、查询和已读权限默认授予正常角色;完成和取消权限保留为按业务角色授权。 @@ -138,7 +138,8 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重 - 优先级:P1 - 批次:B07 -- 状态:todo +- 状态:blocked +- 阻塞原因:当前仓库检索不到平台采购开票对应的数据库模型、审核拒绝/补充材料状态和业务接口,无法安全判断创建与完成时机。 - 目标:在平台采购开票审核拒绝或要求补充材料时创建待办,重新提交或平台关闭时完成。 - 验收标准:同一开票事件对同一接收人只产生一条有效待办,跳转参数和完成条件正确。 @@ -146,7 +147,7 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重 - 优先级:P1 - 批次:B08 -- 状态:todo +- 状态:in_progress - 目标:收到司机开票申请时创建待办,审核通过、驳回或关闭时完成。 - 验收标准:创建和完成与开票业务事务一致,异常回滚时不产生脏任务状态。 @@ -184,4 +185,6 @@ PRD 同时要求“幂等键唯一”和“任务完成后允许同一业务重 - 2026-07-16,B03:完成当前用户数量、首页列表、分页列表、详情、已读、全部已读、完成和取消接口;`mvn -pl jsowell-admin -am -DskipTests compile` 通过,9 个 Service/Controller 专项测试通过;commit `f84ec43ce`。 - 2026-07-16,B04:完成前端 API、待办中心筛选分页、批量已读和安全路由跳转;新增文件专项 ESLint 通过,`NODE_OPTIONS=--openssl-legacy-provider npm run build:prd` 构建通过;前端 commit `ff6b890`。 - 2026-07-16,B05:完成首页右侧待办卡片、`99+` 数量、最近 3 条、空状态、错误重试和 60 秒轮询;专项 ESLint 与生产构建通过;前端 commit `0627061`。 -- 2026-07-16,B06:完成幂等菜单/权限 SQL;列表、查询和已读权限默认授予正常角色,完成和取消权限需按业务角色显式授权;后端 9 个专项测试再次通过,前端构建沿用 B05 验证结果;commit hash 在下一批记录中回填。 +- 2026-07-16,B06:完成幂等菜单/权限 SQL;列表、查询和已读权限默认授予正常角色,完成和取消权限需按业务角色显式授权;后端 9 个专项测试再次通过,前端构建沿用 B05 验证结果;commit `974e68cf5`。 +- 2026-07-16,B07 分析:当前仓库未发现平台采购开票业务落点,需补充对应模块/表/接口信息后继续。 +- 2026-07-16,B08:司机开票记录创建后,按运营商部门树为正常后台用户幂等创建待办;开票状态更新为已开票时自动完成,删除/关闭记录时自动取消;业务与待办状态处于同一事务;全依赖编译和 12 个专项测试通过;commit hash 在下一批记录中回填。 diff --git a/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderInvoiceRecordController.java b/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderInvoiceRecordController.java index ac1d0fa36..51f1160fe 100644 --- a/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderInvoiceRecordController.java +++ b/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderInvoiceRecordController.java @@ -90,7 +90,7 @@ public class OrderInvoiceRecordController extends BaseController { @Log(title = "申请开票", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@RequestBody OrderInvoiceRecord orderInvoiceRecord) { - return toAjax(orderInvoiceRecordService.updateOrderInvoiceRecord(orderInvoiceRecord)); + return toAjax(orderInvoiceRecordService.updateOrderInvoiceRecord(orderInvoiceRecord, getUserId(), getUsername())); } /** @@ -100,6 +100,6 @@ public class OrderInvoiceRecordController extends BaseController { @Log(title = "申请开票", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public AjaxResult remove(@PathVariable Integer[] ids) { - return toAjax(orderInvoiceRecordService.deleteOrderInvoiceRecordByIds(ids)); + return toAjax(orderInvoiceRecordService.deleteOrderInvoiceRecordByIds(ids, getUsername())); } } diff --git a/jsowell-admin/src/test/java/com/jsowell/pile/service/OrderInvoiceRecordTodoTest.java b/jsowell-admin/src/test/java/com/jsowell/pile/service/OrderInvoiceRecordTodoTest.java new file mode 100644 index 000000000..c7c59385e --- /dev/null +++ b/jsowell-admin/src/test/java/com/jsowell/pile/service/OrderInvoiceRecordTodoTest.java @@ -0,0 +1,117 @@ +package com.jsowell.pile.service; + +import com.jsowell.pile.domain.OrderInvoiceRecord; +import com.jsowell.pile.domain.PileMerchantInfo; +import com.jsowell.pile.mapper.OrderInvoiceRecordMapper; +import com.jsowell.pile.service.impl.OrderInvoiceRecordServiceImpl; +import com.jsowell.system.constant.TodoTaskConstants; +import com.jsowell.system.domain.SysTodoTask; +import com.jsowell.system.domain.dto.TodoTaskCreateCommand; +import com.jsowell.system.service.TodoTaskAssigneeService; +import com.jsowell.system.service.TodoTaskService; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class OrderInvoiceRecordTodoTest { + @Test + void insert_shouldCreateTodoForEachMerchantUser() { + OrderInvoiceRecordMapper mapper = mock(OrderInvoiceRecordMapper.class); + PileMerchantInfoService merchantService = mock(PileMerchantInfoService.class); + TodoTaskAssigneeService assigneeService = mock(TodoTaskAssigneeService.class); + TodoTaskService todoTaskService = mock(TodoTaskService.class); + OrderInvoiceRecordServiceImpl service = newService(mapper, merchantService, assigneeService, todoTaskService); + + OrderInvoiceRecord invoiceRecord = new OrderInvoiceRecord(); + invoiceRecord.setMerchantId("100"); + doAnswer(invocation -> { + OrderInvoiceRecord record = invocation.getArgument(0); + record.setId(55); + return 1; + }).when(mapper).insertOrderInvoiceRecord(invoiceRecord); + + PileMerchantInfo merchantInfo = new PileMerchantInfo(); + merchantInfo.setDeptId("200"); + when(merchantService.selectPileMerchantInfoById(100L)).thenReturn(merchantInfo); + when(assigneeService.findActiveUserIdsByDeptTree(200L)).thenReturn(Arrays.asList(8L, 9L)); + when(todoTaskService.createTask(any(TodoTaskCreateCommand.class))).thenReturn(new SysTodoTask()); + + service.insertOrderInvoiceRecord(invoiceRecord); + + ArgumentCaptor captor = ArgumentCaptor.forClass(TodoTaskCreateCommand.class); + verify(todoTaskService, times(2)).createTask(captor.capture()); + List commands = captor.getAllValues(); + assertEquals(Arrays.asList(8L, 9L), Arrays.asList( + commands.get(0).getAssigneeUserId(), commands.get(1).getAssigneeUserId())); + assertEquals("ORDER_INVOICE_RECORD", commands.get(0).getBusinessType()); + assertEquals("55", commands.get(0).getBusinessId()); + assertEquals("invoiceDetail", commands.get(0).getRouteName()); + assertEquals(TodoTaskConstants.PRIORITY_IMPORTANT, commands.get(0).getPriority()); + assertEquals("INVOICE_REVIEW:ORDER_INVOICE_RECORD:55:8", commands.get(0).getIdempotentKey()); + } + + @Test + void updateCompleted_shouldCompleteBusinessTodos() { + OrderInvoiceRecordMapper mapper = mock(OrderInvoiceRecordMapper.class); + TodoTaskService todoTaskService = mock(TodoTaskService.class); + OrderInvoiceRecordServiceImpl service = newService(mapper, mock(PileMerchantInfoService.class), + mock(TodoTaskAssigneeService.class), todoTaskService); + OrderInvoiceRecord invoiceRecord = new OrderInvoiceRecord(); + invoiceRecord.setId(55); + invoiceRecord.setStatus("1"); + when(mapper.updateOrderInvoiceRecord(invoiceRecord)).thenReturn(1); + + service.updateOrderInvoiceRecord(invoiceRecord, 1L, "admin"); + + verify(todoTaskService).completeByBusiness("ORDER_INVOICE_RECORD", "55", null, 1L, "admin"); + } + + @Test + void delete_shouldCancelBusinessTodos() { + OrderInvoiceRecordMapper mapper = mock(OrderInvoiceRecordMapper.class); + TodoTaskService todoTaskService = mock(TodoTaskService.class); + OrderInvoiceRecordServiceImpl service = newService(mapper, mock(PileMerchantInfoService.class), + mock(TodoTaskAssigneeService.class), todoTaskService); + Integer[] ids = { 55, 56 }; + when(mapper.deleteOrderInvoiceRecordByIds(ids)).thenReturn(2); + + service.deleteOrderInvoiceRecordByIds(ids, "admin"); + + verify(todoTaskService).cancelByBusiness("ORDER_INVOICE_RECORD", "55", null, + "admin", "开票申请已关闭"); + verify(todoTaskService).cancelByBusiness("ORDER_INVOICE_RECORD", "56", null, + "admin", "开票申请已关闭"); + } + + private static OrderInvoiceRecordServiceImpl newService(OrderInvoiceRecordMapper mapper, + PileMerchantInfoService merchantService, TodoTaskAssigneeService assigneeService, + TodoTaskService todoTaskService) { + OrderInvoiceRecordServiceImpl service = new OrderInvoiceRecordServiceImpl(); + setField(service, "orderInvoiceRecordMapper", mapper); + setField(service, "pileMerchantInfoService", merchantService); + setField(service, "todoTaskAssigneeService", assigneeService); + setField(service, "todoTaskService", todoTaskService); + return service; + } + + private static void setField(Object target, String fieldName, Object value) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/OrderInvoiceRecordService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/OrderInvoiceRecordService.java index 19dcf5146..37f7a5642 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/OrderInvoiceRecordService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/OrderInvoiceRecordService.java @@ -61,7 +61,7 @@ public interface OrderInvoiceRecordService { * @param orderInvoiceRecord 申请开票 * @return 结果 */ - public int updateOrderInvoiceRecord(OrderInvoiceRecord orderInvoiceRecord); + public int updateOrderInvoiceRecord(OrderInvoiceRecord orderInvoiceRecord, Long operatorId, String operatorName); /** * 批量删除申请开票 @@ -69,7 +69,7 @@ public interface OrderInvoiceRecordService { * @param ids 需要删除的申请开票主键集合 * @return 结果 */ - public int deleteOrderInvoiceRecordByIds(Integer[] ids); + public int deleteOrderInvoiceRecordByIds(Integer[] ids, String operatorName); /** * 删除申请开票信息 @@ -77,5 +77,5 @@ public interface OrderInvoiceRecordService { * @param id 申请开票主键 * @return 结果 */ - public int deleteOrderInvoiceRecordById(Integer id); + public int deleteOrderInvoiceRecordById(Integer id, String operatorName); } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderInvoiceRecordServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderInvoiceRecordServiceImpl.java index f8498c8f7..63be22e8b 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderInvoiceRecordServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderInvoiceRecordServiceImpl.java @@ -2,21 +2,31 @@ package com.jsowell.pile.service.impl; import com.google.common.collect.Lists; import com.jsowell.common.core.domain.vo.AuthorizedDeptVO; +import com.jsowell.common.util.StringUtils; import com.jsowell.pile.domain.OrderInvoiceRecord; +import com.jsowell.pile.domain.PileMerchantInfo; import com.jsowell.pile.dto.GetInvoiceInfoDTO; import com.jsowell.pile.dto.QueryInvoiceRecordDTO; import com.jsowell.pile.mapper.OrderInvoiceRecordMapper; import com.jsowell.pile.service.MemberInvoiceTitleService; import com.jsowell.pile.service.OrderInvoiceRecordService; import com.jsowell.pile.service.OrderBasicInfoService; +import com.jsowell.pile.service.PileMerchantInfoService; import com.jsowell.pile.util.UserUtils; import com.jsowell.pile.vo.base.OrderAmountDetailVO; import com.jsowell.pile.vo.uniapp.customer.InvoiceTitleVO; import com.jsowell.pile.vo.web.InvoiceRecordVO; import com.jsowell.pile.vo.web.OrderInvoiceRecordVO; +import com.jsowell.system.constant.TodoTaskConstants; +import com.jsowell.system.domain.dto.TodoTaskCreateCommand; +import com.jsowell.system.service.TodoTaskAssigneeService; +import com.jsowell.system.service.TodoTaskService; import org.apache.commons.collections4.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.ArrayList; @@ -30,6 +40,11 @@ import java.util.List; */ @Service public class OrderInvoiceRecordServiceImpl implements OrderInvoiceRecordService { + private static final Logger log = LoggerFactory.getLogger(OrderInvoiceRecordServiceImpl.class); + private static final String TODO_TASK_TYPE = "INVOICE_REVIEW"; + private static final String TODO_BUSINESS_TYPE = "ORDER_INVOICE_RECORD"; + private static final String TODO_ROUTE_NAME = "invoiceDetail"; + @Autowired private OrderInvoiceRecordMapper orderInvoiceRecordMapper; @@ -39,6 +54,15 @@ public class OrderInvoiceRecordServiceImpl implements OrderInvoiceRecordService @Autowired private OrderBasicInfoService orderBasicInfoService; + @Autowired + private PileMerchantInfoService pileMerchantInfoService; + + @Autowired + private TodoTaskAssigneeService todoTaskAssigneeService; + + @Autowired + private TodoTaskService todoTaskService; + /** * 查询申请开票 * @@ -132,8 +156,13 @@ public class OrderInvoiceRecordServiceImpl implements OrderInvoiceRecordService * @return 结果 */ @Override + @Transactional(rollbackFor = Exception.class) public int insertOrderInvoiceRecord(OrderInvoiceRecord orderInvoiceRecord) { - return orderInvoiceRecordMapper.insertOrderInvoiceRecord(orderInvoiceRecord); + int rows = orderInvoiceRecordMapper.insertOrderInvoiceRecord(orderInvoiceRecord); + if (rows > 0) { + createInvoiceReviewTodos(orderInvoiceRecord); + } + return rows; } /** @@ -143,8 +172,14 @@ public class OrderInvoiceRecordServiceImpl implements OrderInvoiceRecordService * @return 结果 */ @Override - public int updateOrderInvoiceRecord(OrderInvoiceRecord orderInvoiceRecord) { - return orderInvoiceRecordMapper.updateOrderInvoiceRecord(orderInvoiceRecord); + @Transactional(rollbackFor = Exception.class) + public int updateOrderInvoiceRecord(OrderInvoiceRecord orderInvoiceRecord, Long operatorId, String operatorName) { + int rows = orderInvoiceRecordMapper.updateOrderInvoiceRecord(orderInvoiceRecord); + if (rows > 0 && "1".equals(orderInvoiceRecord.getStatus())) { + todoTaskService.completeByBusiness(TODO_BUSINESS_TYPE, String.valueOf(orderInvoiceRecord.getId()), + null, operatorId, operatorName); + } + return rows; } /** @@ -154,8 +189,18 @@ public class OrderInvoiceRecordServiceImpl implements OrderInvoiceRecordService * @return 结果 */ @Override - public int deleteOrderInvoiceRecordByIds(Integer[] ids) { - return orderInvoiceRecordMapper.deleteOrderInvoiceRecordByIds(ids); + @Transactional(rollbackFor = Exception.class) + public int deleteOrderInvoiceRecordByIds(Integer[] ids, String operatorName) { + int rows = orderInvoiceRecordMapper.deleteOrderInvoiceRecordByIds(ids); + if (rows > 0 && ids != null) { + for (Integer id : ids) { + if (id != null) { + todoTaskService.cancelByBusiness(TODO_BUSINESS_TYPE, String.valueOf(id), null, + operatorName, "开票申请已关闭"); + } + } + } + return rows; } /** @@ -165,7 +210,71 @@ public class OrderInvoiceRecordServiceImpl implements OrderInvoiceRecordService * @return 结果 */ @Override - public int deleteOrderInvoiceRecordById(Integer id) { - return orderInvoiceRecordMapper.deleteOrderInvoiceRecordById(id); + @Transactional(rollbackFor = Exception.class) + public int deleteOrderInvoiceRecordById(Integer id, String operatorName) { + int rows = orderInvoiceRecordMapper.deleteOrderInvoiceRecordById(id); + if (rows > 0) { + todoTaskService.cancelByBusiness(TODO_BUSINESS_TYPE, String.valueOf(id), null, + operatorName, "开票申请已关闭"); + } + return rows; + } + + private void createInvoiceReviewTodos(OrderInvoiceRecord invoiceRecord) { + if (invoiceRecord.getId() == null || StringUtils.isBlank(invoiceRecord.getMerchantId())) { + log.warn("开票申请缺少主键或运营商,无法创建待办,invoiceId={}, merchantId={}", + invoiceRecord.getId(), invoiceRecord.getMerchantId()); + return; + } + + PileMerchantInfo merchantInfo; + try { + merchantInfo = pileMerchantInfoService.selectPileMerchantInfoById(Long.parseLong(invoiceRecord.getMerchantId())); + } catch (NumberFormatException exception) { + log.warn("开票申请运营商 ID 非法,无法创建待办,invoiceId={}, merchantId={}", + invoiceRecord.getId(), invoiceRecord.getMerchantId()); + return; + } + + if (merchantInfo == null || StringUtils.isBlank(merchantInfo.getDeptId())) { + log.warn("开票申请运营商未配置部门,无法创建待办,invoiceId={}, merchantId={}", + invoiceRecord.getId(), invoiceRecord.getMerchantId()); + return; + } + + Long merchantDeptId; + try { + merchantDeptId = Long.parseLong(merchantInfo.getDeptId()); + } catch (NumberFormatException exception) { + log.warn("开票申请运营商部门 ID 非法,无法创建待办,invoiceId={}, deptId={}", + invoiceRecord.getId(), merchantInfo.getDeptId()); + return; + } + + List assigneeUserIds = todoTaskAssigneeService.findActiveUserIdsByDeptTree(merchantDeptId); + if (CollectionUtils.isEmpty(assigneeUserIds)) { + log.warn("开票申请运营商部门下没有正常用户,无法创建待办,invoiceId={}, deptId={}", + invoiceRecord.getId(), merchantInfo.getDeptId()); + return; + } + + for (Long assigneeUserId : assigneeUserIds) { + String idempotentKey = TODO_TASK_TYPE + ":" + TODO_BUSINESS_TYPE + ":" + + invoiceRecord.getId() + ":" + assigneeUserId; + todoTaskService.createTask(TodoTaskCreateCommand.builder() + .taskType(TODO_TASK_TYPE) + .title("司机开票申请") + .summary("有新的司机开票申请需要处理,申请单号:" + invoiceRecord.getId()) + .businessType(TODO_BUSINESS_TYPE) + .businessId(String.valueOf(invoiceRecord.getId())) + .routeName(TODO_ROUTE_NAME) + .routeParams("{\"params\":{\"id\":\"" + invoiceRecord.getId() + "\"}}") + .assigneeUserId(assigneeUserId) + .assigneeMerchantId(invoiceRecord.getMerchantId()) + .priority(TodoTaskConstants.PRIORITY_IMPORTANT) + .idempotentKey(idempotentKey) + .createBy("invoice-system") + .build()); + } } } diff --git a/jsowell-system/src/main/java/com/jsowell/system/mapper/SysTodoTaskMapper.java b/jsowell-system/src/main/java/com/jsowell/system/mapper/SysTodoTaskMapper.java index 0692476c3..b72fc1c08 100644 --- a/jsowell-system/src/main/java/com/jsowell/system/mapper/SysTodoTaskMapper.java +++ b/jsowell-system/src/main/java/com/jsowell/system/mapper/SysTodoTaskMapper.java @@ -56,4 +56,10 @@ public interface SysTodoTaskMapper { int cancelTodo(@Param("todoId") Long todoId, @Param("reason") String reason, @Param("updateBy") String updateBy); + + int cancelTodoByBusiness(@Param("businessType") String businessType, + @Param("businessId") String businessId, + @Param("assigneeUserId") Long assigneeUserId, + @Param("reason") String reason, + @Param("updateBy") String updateBy); } diff --git a/jsowell-system/src/main/java/com/jsowell/system/mapper/SysUserMapper.java b/jsowell-system/src/main/java/com/jsowell/system/mapper/SysUserMapper.java index 3c24176f4..89b2fb07d 100644 --- a/jsowell-system/src/main/java/com/jsowell/system/mapper/SysUserMapper.java +++ b/jsowell-system/src/main/java/com/jsowell/system/mapper/SysUserMapper.java @@ -19,6 +19,14 @@ public interface SysUserMapper { */ List selectUserList(SysUser sysUser); + /** + * 查询部门及下级部门的正常用户 ID。 + * + * @param deptId 部门 ID + * @return 用户 ID 集合 + */ + List selectActiveUserIdsByDeptTree(@Param("deptId") Long deptId); + /** * 根据条件分页查询已配用户角色列表 * diff --git a/jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskAssigneeService.java b/jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskAssigneeService.java new file mode 100644 index 000000000..ef8b85607 --- /dev/null +++ b/jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskAssigneeService.java @@ -0,0 +1,12 @@ +package com.jsowell.system.service; + +import java.util.List; + +/** + * 待办接收人解析服务。 + * + * @author jsowell + */ +public interface TodoTaskAssigneeService { + List findActiveUserIdsByDeptTree(Long deptId); +} diff --git a/jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskService.java b/jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskService.java index 8cb3329c9..4804c1fe4 100644 --- a/jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskService.java +++ b/jsowell-system/src/main/java/com/jsowell/system/service/TodoTaskService.java @@ -36,4 +36,7 @@ public interface TodoTaskService { boolean cancelForAssignee(Long todoId, Long assigneeUserId, String operatorName, String reason); boolean cancel(Long todoId, Long operatorId, String operatorName, String reason); + + int cancelByBusiness(String businessType, String businessId, Long assigneeUserId, + String operatorName, String reason); } diff --git a/jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskAssigneeServiceImpl.java b/jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskAssigneeServiceImpl.java new file mode 100644 index 000000000..e7dfe6332 --- /dev/null +++ b/jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskAssigneeServiceImpl.java @@ -0,0 +1,28 @@ +package com.jsowell.system.service.impl; + +import com.jsowell.common.exception.ServiceException; +import com.jsowell.system.mapper.SysUserMapper; +import com.jsowell.system.service.TodoTaskAssigneeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 待办接收人解析服务实现。 + * + * @author jsowell + */ +@Service +public class TodoTaskAssigneeServiceImpl implements TodoTaskAssigneeService { + @Autowired + private SysUserMapper userMapper; + + @Override + public List findActiveUserIdsByDeptTree(Long deptId) { + if (deptId == null || deptId <= 0) { + throw new ServiceException("待办接收部门不能为空"); + } + return userMapper.selectActiveUserIdsByDeptTree(deptId); + } +} diff --git a/jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskServiceImpl.java b/jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskServiceImpl.java index 142b2f307..f2e98568e 100644 --- a/jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskServiceImpl.java +++ b/jsowell-system/src/main/java/com/jsowell/system/service/impl/TodoTaskServiceImpl.java @@ -191,6 +191,20 @@ public class TodoTaskServiceImpl implements TodoTaskService { throw new ServiceException("当前待办状态不允许取消"); } + @Override + @Transactional(rollbackFor = Exception.class) + public int cancelByBusiness(String businessType, String businessId, Long assigneeUserId, + String operatorName, String reason) { + String safeBusinessType = normalizeRequired(businessType, "业务类型", 64); + String safeBusinessId = normalizeRequired(businessId, "业务主键", 64); + if (assigneeUserId != null) { + validateUserId(assigneeUserId); + } + String safeReason = normalizeRequired(reason, "取消原因", 500); + return todoTaskMapper.cancelTodoByBusiness(safeBusinessType, safeBusinessId, assigneeUserId, + safeReason, normalizeOperator(operatorName, null)); + } + private SysTodoTask buildTask(TodoTaskCreateCommand command) { if (command.getAssigneeUserId() == null || command.getAssigneeUserId() <= 0) { throw new ServiceException("待办接收用户不能为空"); diff --git a/jsowell-system/src/main/resources/mapper/system/SysTodoTaskMapper.xml b/jsowell-system/src/main/resources/mapper/system/SysTodoTaskMapper.xml index 35aed1509..6d2fbd578 100644 --- a/jsowell-system/src/main/resources/mapper/system/SysTodoTaskMapper.xml +++ b/jsowell-system/src/main/resources/mapper/system/SysTodoTaskMapper.xml @@ -212,4 +212,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and del_flag = '0' + + update sys_todo_task + set task_status = 'CANCELLED', + cancel_reason = #{reason}, + active_idempotent_key = null, + update_by = #{updateBy}, + update_time = sysdate() + where business_type = #{businessType} + and business_id = #{businessId} + + and assignee_user_id = #{assigneeUserId} + + and task_status in ('PENDING', 'PROCESSING') + and del_flag = '0' + + diff --git a/jsowell-system/src/main/resources/mapper/system/SysUserMapper.xml b/jsowell-system/src/main/resources/mapper/system/SysUserMapper.xml index 250ea8b5c..85c2a3105 100644 --- a/jsowell-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/jsowell-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -90,6 +90,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ${params.dataScope} + +