diff --git a/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderAbnormalRecordController.java b/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderAbnormalRecordController.java index 751a2e8ad..c8d9b95ff 100644 --- a/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderAbnormalRecordController.java +++ b/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderAbnormalRecordController.java @@ -93,6 +93,6 @@ public class OrderAbnormalRecordController extends BaseController { @Log(title = "订单异常记录", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public AjaxResult remove(@PathVariable Integer[] ids) { - return toAjax(orderAbnormalRecordService.deleteOrderAbnormalRecordByIds(ids)); + return toAjax(orderAbnormalRecordService.deleteOrderAbnormalRecordByIds(ids, getUsername())); } } diff --git a/jsowell-admin/src/main/resources/application.yml b/jsowell-admin/src/main/resources/application.yml index 64dd72d7f..f282d40b5 100644 --- a/jsowell-admin/src/main/resources/application.yml +++ b/jsowell-admin/src/main/resources/application.yml @@ -219,4 +219,4 @@ sms: todo: operator-admin-role-id: 3 retention-days: 180 - route-whitelist: invoiceDetail,financeDetail,accountUserInfo + route-whitelist: invoiceDetail,financeDetail,accountUserInfo,AbnormalRecord diff --git a/jsowell-admin/src/test/java/com/jsowell/pile/service/AdapayClearingTodoServiceImplTest.java b/jsowell-admin/src/test/java/com/jsowell/pile/service/AdapayClearingTodoServiceImplTest.java new file mode 100644 index 000000000..535d18daf --- /dev/null +++ b/jsowell-admin/src/test/java/com/jsowell/pile/service/AdapayClearingTodoServiceImplTest.java @@ -0,0 +1,82 @@ +package com.jsowell.pile.service; + +import com.jsowell.pile.constant.AdapayClearingTodoConstants; +import com.jsowell.pile.domain.AdapayUnsplitRecord; +import com.jsowell.pile.domain.OrderBasicInfo; +import com.jsowell.pile.domain.PileMerchantInfo; +import com.jsowell.pile.service.impl.AdapayClearingTodoServiceImpl; +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.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class AdapayClearingTodoServiceImplTest { + @Test + void failedAndRecovered_shouldCreateThenCompleteTodo() { + Dependencies dependencies = new Dependencies(); + AdapayClearingTodoServiceImpl service = dependencies.createService(); + OrderBasicInfo orderInfo = OrderBasicInfo.builder().build(); + orderInfo.setMerchantId("100"); + when(dependencies.orderService.getOrderInfoByOrderCode("C100")).thenReturn(orderInfo); + PileMerchantInfo merchantInfo = new PileMerchantInfo(); + merchantInfo.setDeptId("200"); + when(dependencies.merchantService.selectPileMerchantInfoById(100L)).thenReturn(merchantInfo); + when(dependencies.assigneeService.findActiveOperatorAdminUserIdsByDeptTree(200L)) + .thenReturn(Collections.singletonList(8L)); + AdapayUnsplitRecord record = AdapayUnsplitRecord.builder() + .paymentId("payment-100") + .orderCode("C100") + .refundFlag("SUCCESS") + .splitFlag("FAILED") + .build(); + + service.syncUnsplitRecordTodo(record); + + ArgumentCaptor captor = ArgumentCaptor.forClass(TodoTaskCreateCommand.class); + verify(dependencies.todoTaskService).createTask(captor.capture()); + assertEquals(AdapayClearingTodoConstants.TASK_TYPE, captor.getValue().getTaskType()); + assertEquals(AdapayClearingTodoConstants.BUSINESS_TYPE, captor.getValue().getBusinessType()); + assertEquals("payment-100", captor.getValue().getBusinessId()); + assertEquals("financeDetail", captor.getValue().getRouteName()); + + record.setSplitFlag("SUCCESS"); + service.syncUnsplitRecordTodo(record); + verify(dependencies.todoTaskService).completeByBusinessAsSystem( + AdapayClearingTodoConstants.BUSINESS_TYPE, "payment-100", null, "adapay-clearing"); + } + + 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); + } + } + + private static class Dependencies { + private final OrderBasicInfoService orderService = mock(OrderBasicInfoService.class); + private final PileMerchantInfoService merchantService = mock(PileMerchantInfoService.class); + private final TodoTaskAssigneeService assigneeService = mock(TodoTaskAssigneeService.class); + private final TodoTaskService todoTaskService = mock(TodoTaskService.class); + + private AdapayClearingTodoServiceImpl createService() { + AdapayClearingTodoServiceImpl service = new AdapayClearingTodoServiceImpl(); + setField(service, "orderBasicInfoService", orderService); + setField(service, "pileMerchantInfoService", merchantService); + setField(service, "todoTaskAssigneeService", assigneeService); + setField(service, "todoTaskService", todoTaskService); + return service; + } + } +} diff --git a/jsowell-admin/src/test/java/com/jsowell/pile/service/AdapayUnsplitRecordTodoLinkTest.java b/jsowell-admin/src/test/java/com/jsowell/pile/service/AdapayUnsplitRecordTodoLinkTest.java new file mode 100644 index 000000000..8c03c95c0 --- /dev/null +++ b/jsowell-admin/src/test/java/com/jsowell/pile/service/AdapayUnsplitRecordTodoLinkTest.java @@ -0,0 +1,74 @@ +package com.jsowell.pile.service; + +import com.jsowell.pile.domain.AdapayUnsplitRecord; +import com.jsowell.pile.mapper.AdapayUnsplitRecordMapper; +import com.jsowell.web.controller.pile.AdapayUnsplitRecordServiceImpl; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class AdapayUnsplitRecordTodoLinkTest { + @Test + void flagUpdate_shouldSyncTodoWithPersistedRecord() { + AdapayUnsplitRecordMapper mapper = mock(AdapayUnsplitRecordMapper.class); + AdapayClearingTodoService todoService = mock(AdapayClearingTodoService.class); + AdapayUnsplitRecordServiceImpl service = newService(mapper, todoService); + AdapayUnsplitRecord updateRecord = AdapayUnsplitRecord.builder() + .id(1) + .paymentId("payment-100") + .splitFlag("FAILED") + .build(); + AdapayUnsplitRecord persistedRecord = AdapayUnsplitRecord.builder() + .id(1) + .paymentId("payment-100") + .orderCode("C100") + .refundFlag("SUCCESS") + .splitFlag("FAILED") + .build(); + when(mapper.insertOrUpdateSelective(updateRecord)).thenReturn(1); + when(mapper.selectByPrimaryKey(1)).thenReturn(persistedRecord); + + service.insertOrUpdateSelective(updateRecord); + + verify(todoService).syncUnsplitRecordTodo(persistedRecord); + } + + @Test + void amountOnlyUpdate_shouldNotSyncTodo() { + AdapayUnsplitRecordMapper mapper = mock(AdapayUnsplitRecordMapper.class); + AdapayClearingTodoService todoService = mock(AdapayClearingTodoService.class); + AdapayUnsplitRecordServiceImpl service = newService(mapper, todoService); + AdapayUnsplitRecord updateRecord = AdapayUnsplitRecord.builder() + .id(1) + .paymentId("payment-100") + .build(); + when(mapper.insertOrUpdateSelective(updateRecord)).thenReturn(1); + + service.insertOrUpdateSelective(updateRecord); + + verify(todoService, never()).syncUnsplitRecordTodo(updateRecord); + } + + private static AdapayUnsplitRecordServiceImpl newService(AdapayUnsplitRecordMapper mapper, + AdapayClearingTodoService todoService) { + AdapayUnsplitRecordServiceImpl service = new AdapayUnsplitRecordServiceImpl(); + setField(service, "adapayUnsplitRecordMapper", mapper); + setField(service, "adapayClearingTodoService", todoService); + 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-admin/src/test/java/com/jsowell/pile/service/OrderAbnormalRecordTodoTest.java b/jsowell-admin/src/test/java/com/jsowell/pile/service/OrderAbnormalRecordTodoTest.java new file mode 100644 index 000000000..1b0a3c16a --- /dev/null +++ b/jsowell-admin/src/test/java/com/jsowell/pile/service/OrderAbnormalRecordTodoTest.java @@ -0,0 +1,103 @@ +package com.jsowell.pile.service; + +import com.jsowell.pile.constant.OrderAbnormalTodoConstants; +import com.jsowell.pile.domain.OrderAbnormalRecord; +import com.jsowell.pile.domain.PileMerchantInfo; +import com.jsowell.pile.mapper.OrderAbnormalRecordMapper; +import com.jsowell.pile.service.impl.OrderAbnormalRecordServiceImpl; +import com.jsowell.pile.vo.base.PileInfoVO; +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 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 OrderAbnormalRecordTodoTest { + @Test + void insert_shouldCreateTodoForEachMerchantAdmin() { + Dependencies dependencies = new Dependencies(); + OrderAbnormalRecordServiceImpl service = dependencies.createService(); + OrderAbnormalRecord record = new OrderAbnormalRecord(); + record.setOrderCode("transaction-100"); + record.setPileSn("pile-100"); + record.setStopReasonMsg("平台未找到原订单"); + doAnswer(invocation -> { + OrderAbnormalRecord value = invocation.getArgument(0); + value.setId(55); + return 1; + }).when(dependencies.mapper).insertOrderAbnormalRecord(record); + when(dependencies.pileBasicInfoService.selectPileInfoBySn("pile-100")) + .thenReturn(PileInfoVO.builder().merchantId("100").build()); + PileMerchantInfo merchantInfo = new PileMerchantInfo(); + merchantInfo.setDeptId("200"); + when(dependencies.merchantService.selectPileMerchantInfoById(100L)).thenReturn(merchantInfo); + when(dependencies.assigneeService.findActiveOperatorAdminUserIdsByDeptTree(200L)) + .thenReturn(Arrays.asList(8L, 9L)); + + service.insertOrderAbnormalRecord(record); + + ArgumentCaptor captor = ArgumentCaptor.forClass(TodoTaskCreateCommand.class); + verify(dependencies.todoTaskService, times(2)).createTask(captor.capture()); + TodoTaskCreateCommand command = captor.getAllValues().get(0); + assertEquals(OrderAbnormalTodoConstants.TASK_TYPE, command.getTaskType()); + assertEquals(OrderAbnormalTodoConstants.BUSINESS_TYPE, command.getBusinessType()); + assertEquals("55", command.getBusinessId()); + assertEquals(OrderAbnormalTodoConstants.ROUTE_NAME, command.getRouteName()); + assertEquals("100", command.getAssigneeMerchantId()); + assertEquals(8L, command.getAssigneeUserId()); + } + + @Test + void delete_shouldCancelBusinessTodos() { + Dependencies dependencies = new Dependencies(); + OrderAbnormalRecordServiceImpl service = dependencies.createService(); + Integer[] ids = {55, 56}; + when(dependencies.mapper.deleteOrderAbnormalRecordByIds(ids)).thenReturn(2); + + service.deleteOrderAbnormalRecordByIds(ids, "admin"); + + verify(dependencies.todoTaskService).cancelByBusiness( + OrderAbnormalTodoConstants.BUSINESS_TYPE, "55", null, "admin", "订单异常记录已关闭"); + verify(dependencies.todoTaskService).cancelByBusiness( + OrderAbnormalTodoConstants.BUSINESS_TYPE, "56", null, "admin", "订单异常记录已关闭"); + } + + 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); + } + } + + private static class Dependencies { + private final OrderAbnormalRecordMapper mapper = mock(OrderAbnormalRecordMapper.class); + private final PileBasicInfoService pileBasicInfoService = mock(PileBasicInfoService.class); + private final PileMerchantInfoService merchantService = mock(PileMerchantInfoService.class); + private final TodoTaskAssigneeService assigneeService = mock(TodoTaskAssigneeService.class); + private final TodoTaskService todoTaskService = mock(TodoTaskService.class); + + private OrderAbnormalRecordServiceImpl createService() { + OrderAbnormalRecordServiceImpl service = new OrderAbnormalRecordServiceImpl(); + setField(service, "orderAbnormalRecordMapper", mapper); + setField(service, "pileBasicInfoService", pileBasicInfoService); + setField(service, "pileMerchantInfoService", merchantService); + setField(service, "todoTaskAssigneeService", assigneeService); + setField(service, "todoTaskService", todoTaskService); + return service; + } + } +} diff --git a/jsowell-admin/src/test/java/com/jsowell/system/service/TodoTaskServiceImplTest.java b/jsowell-admin/src/test/java/com/jsowell/system/service/TodoTaskServiceImplTest.java index 56108b059..e5448ffba 100644 --- a/jsowell-admin/src/test/java/com/jsowell/system/service/TodoTaskServiceImplTest.java +++ b/jsowell-admin/src/test/java/com/jsowell/system/service/TodoTaskServiceImplTest.java @@ -129,6 +129,18 @@ class TodoTaskServiceImplTest { verify(mapper, never()).insertTodoTask(any(SysTodoTask.class)); } + @Test + void createTask_shouldAllowOrderAbnormalRoute() { + SysTodoTaskMapper mapper = mock(SysTodoTaskMapper.class); + TodoTaskServiceImpl service = newService(mapper); + TodoTaskCreateCommand command = createCommand(); + command.setRouteName("AbnormalRecord"); + + service.createTask(command); + + verify(mapper).insertTodoTask(any(SysTodoTask.class)); + } + @Test void createTask_shouldRejectNestedRouteParams() { SysTodoTaskMapper mapper = mock(SysTodoTaskMapper.class); diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/constant/AdapayClearingTodoConstants.java b/jsowell-pile/src/main/java/com/jsowell/pile/constant/AdapayClearingTodoConstants.java new file mode 100644 index 000000000..6dda7b0eb --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/constant/AdapayClearingTodoConstants.java @@ -0,0 +1,15 @@ +package com.jsowell.pile.constant; + +/** + * 汇付清分异常待办常量。 + */ +public final class AdapayClearingTodoConstants { + public static final String TASK_TYPE = "CLEARING_EXCEPTION"; + public static final String BUSINESS_TYPE = "ADAPAY_UNSPLIT_RECORD"; + public static final String ROUTE_NAME = "financeDetail"; + public static final String HANDLE_FLAG_FAILED = "FAILED"; + public static final String HANDLE_FLAG_SUCCESS = "SUCCESS"; + + private AdapayClearingTodoConstants() { + } +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/constant/OrderAbnormalTodoConstants.java b/jsowell-pile/src/main/java/com/jsowell/pile/constant/OrderAbnormalTodoConstants.java new file mode 100644 index 000000000..e9fb2394f --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/constant/OrderAbnormalTodoConstants.java @@ -0,0 +1,13 @@ +package com.jsowell.pile.constant; + +/** + * 订单异常待办常量。 + */ +public final class OrderAbnormalTodoConstants { + public static final String TASK_TYPE = "ORDER_ABNORMAL"; + public static final String BUSINESS_TYPE = "ORDER_ABNORMAL_RECORD"; + public static final String ROUTE_NAME = "AbnormalRecord"; + + private OrderAbnormalTodoConstants() { + } +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/AdapayClearingTodoService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/AdapayClearingTodoService.java new file mode 100644 index 000000000..e9c5972be --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/AdapayClearingTodoService.java @@ -0,0 +1,10 @@ +package com.jsowell.pile.service; + +import com.jsowell.pile.domain.AdapayUnsplitRecord; + +/** + * 汇付清分状态与待办联动服务。 + */ +public interface AdapayClearingTodoService { + void syncUnsplitRecordTodo(AdapayUnsplitRecord record); +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/OrderAbnormalRecordService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/OrderAbnormalRecordService.java index f2c518c9d..deb92a7bd 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/OrderAbnormalRecordService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/OrderAbnormalRecordService.java @@ -49,7 +49,7 @@ public interface OrderAbnormalRecordService { * @param ids 需要删除的订单异常记录主键集合 * @return 结果 */ - public int deleteOrderAbnormalRecordByIds(Integer[] ids); + public int deleteOrderAbnormalRecordByIds(Integer[] ids, String operatorName); /** * 删除订单异常记录信息 @@ -57,5 +57,5 @@ public interface OrderAbnormalRecordService { * @param id 订单异常记录主键 * @return 结果 */ - public int deleteOrderAbnormalRecordById(Integer id); + public int deleteOrderAbnormalRecordById(Integer id, String operatorName); } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/AdapayClearingTodoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/AdapayClearingTodoServiceImpl.java new file mode 100644 index 000000000..5bc75df23 --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/AdapayClearingTodoServiceImpl.java @@ -0,0 +1,159 @@ +package com.jsowell.pile.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.jsowell.common.util.StringUtils; +import com.jsowell.pile.constant.AdapayClearingTodoConstants; +import com.jsowell.pile.domain.AdapayUnsplitRecord; +import com.jsowell.pile.domain.OrderBasicInfo; +import com.jsowell.pile.domain.PileMerchantInfo; +import com.jsowell.pile.service.AdapayClearingTodoService; +import com.jsowell.pile.service.OrderBasicInfoService; +import com.jsowell.pile.service.PileMerchantInfoService; +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.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 汇付清分失败状态与待办联动服务实现。 + */ +@Service +public class AdapayClearingTodoServiceImpl implements AdapayClearingTodoService { + private static final Logger log = LoggerFactory.getLogger(AdapayClearingTodoServiceImpl.class); + private static final String TODO_OPERATOR = "adapay-clearing"; + + @Autowired + private OrderBasicInfoService orderBasicInfoService; + + @Autowired + private PileMerchantInfoService pileMerchantInfoService; + + @Autowired + private TodoTaskAssigneeService todoTaskAssigneeService; + + @Autowired + private TodoTaskService todoTaskService; + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncUnsplitRecordTodo(AdapayUnsplitRecord record) { + if (record == null || StringUtils.isBlank(record.getPaymentId())) { + return; + } + boolean failed = AdapayClearingTodoConstants.HANDLE_FLAG_FAILED.equals(record.getRefundFlag()) + || AdapayClearingTodoConstants.HANDLE_FLAG_FAILED.equals(record.getSplitFlag()); + boolean succeeded = AdapayClearingTodoConstants.HANDLE_FLAG_SUCCESS.equals(record.getRefundFlag()) + && AdapayClearingTodoConstants.HANDLE_FLAG_SUCCESS.equals(record.getSplitFlag()); + if (!failed && !succeeded) { + return; + } + + String paymentId = record.getPaymentId().trim(); + if (succeeded) { + todoTaskService.completeByBusinessAsSystem(AdapayClearingTodoConstants.BUSINESS_TYPE, + paymentId, null, TODO_OPERATOR); + return; + } + createClearingExceptionTodos(record, paymentId); + } + + private void createClearingExceptionTodos(AdapayUnsplitRecord record, String paymentId) { + if (StringUtils.isBlank(record.getOrderCode())) { + log.warn("清分失败记录缺少订单号,无法创建待办,paymentId={}", paymentId); + return; + } + OrderBasicInfo orderInfo = orderBasicInfoService.getOrderInfoByOrderCode(record.getOrderCode()); + if (orderInfo == null || StringUtils.isBlank(orderInfo.getMerchantId())) { + log.warn("清分失败记录未找到订单运营商,无法创建待办,paymentId={}, orderCode={}", + paymentId, record.getOrderCode()); + return; + } + + PileMerchantInfo merchantInfo; + try { + merchantInfo = pileMerchantInfoService.selectPileMerchantInfoById( + Long.parseLong(orderInfo.getMerchantId())); + } catch (NumberFormatException exception) { + log.warn("清分失败记录运营商 ID 非法,无法创建待办,paymentId={}, merchantId={}", + paymentId, orderInfo.getMerchantId()); + return; + } + if (merchantInfo == null || StringUtils.isBlank(merchantInfo.getDeptId())) { + log.warn("清分失败记录运营商未配置部门,无法创建待办,paymentId={}, merchantId={}", + paymentId, orderInfo.getMerchantId()); + return; + } + + Long deptId; + try { + deptId = Long.parseLong(merchantInfo.getDeptId()); + } catch (NumberFormatException exception) { + log.warn("清分失败记录运营商部门 ID 非法,无法创建待办,paymentId={}, deptId={}", + paymentId, merchantInfo.getDeptId()); + return; + } + List assigneeUserIds = todoTaskAssigneeService.findActiveOperatorAdminUserIdsByDeptTree(deptId); + if (CollectionUtils.isEmpty(assigneeUserIds)) { + log.warn("清分失败记录运营商部门下没有正常管理员,无法创建待办,paymentId={}, deptId={}", + paymentId, deptId); + return; + } + + String summary = buildSummary(record); + String content = "退款处理状态:" + displayFlag(record.getRefundFlag()) + + ",分账处理状态:" + displayFlag(record.getSplitFlag()); + for (Long assigneeUserId : assigneeUserIds) { + todoTaskService.createTask(TodoTaskCreateCommand.builder() + .taskType(AdapayClearingTodoConstants.TASK_TYPE) + .title("清分异常待处理") + .summary(summary) + .content(content) + .businessType(AdapayClearingTodoConstants.BUSINESS_TYPE) + .businessId(paymentId) + .routeName(AdapayClearingTodoConstants.ROUTE_NAME) + .routeParams(buildRouteParams(record, orderInfo.getMerchantId())) + .assigneeUserId(assigneeUserId) + .assigneeMerchantId(orderInfo.getMerchantId()) + .priority(TodoTaskConstants.PRIORITY_IMPORTANT) + .idempotentKey(AdapayClearingTodoConstants.TASK_TYPE + ":" + + AdapayClearingTodoConstants.BUSINESS_TYPE + ":" + paymentId + ":" + assigneeUserId) + .createBy(TODO_OPERATOR) + .build()); + } + } + + private String buildSummary(AdapayUnsplitRecord record) { + String summary = "订单清分或退款处理失败,需要人工核对,订单号:" + record.getOrderCode().trim(); + if (AdapayClearingTodoConstants.HANDLE_FLAG_FAILED.equals(record.getRefundFlag())) { + summary += ",退款状态失败"; + } + if (AdapayClearingTodoConstants.HANDLE_FLAG_FAILED.equals(record.getSplitFlag())) { + summary += ",分账状态失败"; + } + return summary.length() <= 500 ? summary : summary.substring(0, 500); + } + + private String buildRouteParams(AdapayUnsplitRecord record, String merchantId) { + Map query = new HashMap<>(); + query.put("merchantId", merchantId); + query.put("orderCode", record.getOrderCode()); + query.put("paymentId", record.getPaymentId()); + Map routeParams = new HashMap<>(); + routeParams.put("query", query); + return JSON.toJSONString(routeParams); + } + + private String displayFlag(String flag) { + return StringUtils.isBlank(flag) ? "未确认" : flag; + } +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderAbnormalRecordServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderAbnormalRecordServiceImpl.java index 370cbce01..fe7fddaab 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderAbnormalRecordServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderAbnormalRecordServiceImpl.java @@ -1,12 +1,29 @@ package com.jsowell.pile.service.impl; +import com.alibaba.fastjson2.JSON; +import com.jsowell.common.util.StringUtils; +import com.jsowell.pile.constant.OrderAbnormalTodoConstants; import com.jsowell.pile.domain.OrderAbnormalRecord; +import com.jsowell.pile.domain.PileMerchantInfo; import com.jsowell.pile.mapper.OrderAbnormalRecordMapper; import com.jsowell.pile.service.OrderAbnormalRecordService; +import com.jsowell.pile.service.PileBasicInfoService; +import com.jsowell.pile.service.PileMerchantInfoService; +import com.jsowell.pile.vo.base.PileInfoVO; +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.util.HashMap; import java.util.List; +import java.util.Map; /** * 订单异常记录Service业务层处理 @@ -16,9 +33,25 @@ import java.util.List; */ @Service public class OrderAbnormalRecordServiceImpl implements OrderAbnormalRecordService { + private static final Logger log = LoggerFactory.getLogger(OrderAbnormalRecordServiceImpl.class); + private static final String TODO_OPERATOR = "order-abnormal-system"; + private static final String CANCEL_REASON = "订单异常记录已关闭"; + @Autowired private OrderAbnormalRecordMapper orderAbnormalRecordMapper; + @Autowired + private PileBasicInfoService pileBasicInfoService; + + @Autowired + private PileMerchantInfoService pileMerchantInfoService; + + @Autowired + private TodoTaskAssigneeService todoTaskAssigneeService; + + @Autowired + private TodoTaskService todoTaskService; + /** * 查询订单异常记录 * @@ -48,9 +81,13 @@ public class OrderAbnormalRecordServiceImpl implements OrderAbnormalRecordServic * @return 结果 */ @Override + @Transactional(rollbackFor = Exception.class) public int insertOrderAbnormalRecord(OrderAbnormalRecord orderAbnormalRecord) { - // orderAbnormalRecord.setCreateTime(DateUtils.getNowDate()); - return orderAbnormalRecordMapper.insertOrderAbnormalRecord(orderAbnormalRecord); + int rows = orderAbnormalRecordMapper.insertOrderAbnormalRecord(orderAbnormalRecord); + if (rows > 0) { + createOrderAbnormalTodo(orderAbnormalRecord); + } + return rows; } /** @@ -71,8 +108,15 @@ public class OrderAbnormalRecordServiceImpl implements OrderAbnormalRecordServic * @return 结果 */ @Override - public int deleteOrderAbnormalRecordByIds(Integer[] ids) { - return orderAbnormalRecordMapper.deleteOrderAbnormalRecordByIds(ids); + @Transactional(rollbackFor = Exception.class) + public int deleteOrderAbnormalRecordByIds(Integer[] ids, String operatorName) { + int rows = orderAbnormalRecordMapper.deleteOrderAbnormalRecordByIds(ids); + if (rows > 0 && ids != null) { + for (Integer id : ids) { + cancelOrderAbnormalTodo(id, operatorName); + } + } + return rows; } /** @@ -82,7 +126,123 @@ public class OrderAbnormalRecordServiceImpl implements OrderAbnormalRecordServic * @return 结果 */ @Override - public int deleteOrderAbnormalRecordById(Integer id) { - return orderAbnormalRecordMapper.deleteOrderAbnormalRecordById(id); + @Transactional(rollbackFor = Exception.class) + public int deleteOrderAbnormalRecordById(Integer id, String operatorName) { + int rows = orderAbnormalRecordMapper.deleteOrderAbnormalRecordById(id); + if (rows > 0) { + cancelOrderAbnormalTodo(id, operatorName); + } + return rows; + } + + private void createOrderAbnormalTodo(OrderAbnormalRecord record) { + if (record == null || record.getId() == null || StringUtils.isBlank(record.getPileSn())) { + log.warn("订单异常记录缺少主键或桩号,无法创建待办,recordId={}, pileSn={}", + record == null ? null : record.getId(), record == null ? null : record.getPileSn()); + return; + } + + PileInfoVO pileInfo = pileBasicInfoService.selectPileInfoBySn(record.getPileSn()); + if (pileInfo == null || StringUtils.isBlank(pileInfo.getMerchantId())) { + log.warn("订单异常记录未找到桩所属运营商,无法创建待办,recordId={}, pileSn={}", + record.getId(), record.getPileSn()); + return; + } + + Long merchantId; + try { + merchantId = Long.parseLong(pileInfo.getMerchantId()); + } catch (NumberFormatException exception) { + log.warn("订单异常记录运营商 ID 非法,无法创建待办,recordId={}, merchantId={}", + record.getId(), pileInfo.getMerchantId()); + return; + } + PileMerchantInfo merchantInfo = pileMerchantInfoService.selectPileMerchantInfoById(merchantId); + if (merchantInfo == null || StringUtils.isBlank(merchantInfo.getDeptId())) { + log.warn("订单异常记录运营商未配置部门,无法创建待办,recordId={}, merchantId={}", + record.getId(), pileInfo.getMerchantId()); + return; + } + + Long deptId; + try { + deptId = Long.parseLong(merchantInfo.getDeptId()); + } catch (NumberFormatException exception) { + log.warn("订单异常记录运营商部门 ID 非法,无法创建待办,recordId={}, deptId={}", + record.getId(), merchantInfo.getDeptId()); + return; + } + List assigneeUserIds = todoTaskAssigneeService.findActiveOperatorAdminUserIdsByDeptTree(deptId); + if (CollectionUtils.isEmpty(assigneeUserIds)) { + log.warn("订单异常记录运营商部门下没有正常管理员,无法创建待办,recordId={}, deptId={}", + record.getId(), deptId); + return; + } + + String businessId = String.valueOf(record.getId()); + String summary = buildSummary(record); + String content = StringUtils.isBlank(record.getStopReasonMsg()) + ? null : truncate(record.getStopReasonMsg(), 20000); + for (Long assigneeUserId : assigneeUserIds) { + todoTaskService.createTask(TodoTaskCreateCommand.builder() + .taskType(OrderAbnormalTodoConstants.TASK_TYPE) + .title("订单异常待处理") + .summary(summary) + .content(content) + .businessType(OrderAbnormalTodoConstants.BUSINESS_TYPE) + .businessId(businessId) + .routeName(OrderAbnormalTodoConstants.ROUTE_NAME) + .routeParams(buildRouteParams(record)) + .assigneeUserId(assigneeUserId) + .assigneeMerchantId(pileInfo.getMerchantId()) + .priority(TodoTaskConstants.PRIORITY_IMPORTANT) + .idempotentKey(OrderAbnormalTodoConstants.TASK_TYPE + ":" + + OrderAbnormalTodoConstants.BUSINESS_TYPE + ":" + businessId + ":" + assigneeUserId) + .createBy(TODO_OPERATOR) + .build()); + } + } + + private void cancelOrderAbnormalTodo(Integer id, String operatorName) { + if (id == null) { + return; + } + todoTaskService.cancelByBusiness(OrderAbnormalTodoConstants.BUSINESS_TYPE, String.valueOf(id), null, + normalizeOperator(operatorName), CANCEL_REASON); + } + + private String buildSummary(OrderAbnormalRecord record) { + String summary = "发现需要人工核对的异常充电订单"; + if (StringUtils.isNotBlank(record.getOrderCode())) { + summary += ",交易流水号:" + record.getOrderCode().trim(); + } + if (StringUtils.isNotBlank(record.getStopReasonMsg())) { + summary += ",停止原因:" + record.getStopReasonMsg().trim(); + } + return truncate(summary, 500); + } + + private String buildRouteParams(OrderAbnormalRecord record) { + Map query = new HashMap<>(); + if (StringUtils.isNotBlank(record.getOrderCode())) { + query.put("orderCode", record.getOrderCode().trim()); + } + if (StringUtils.isNotBlank(record.getPileSn())) { + query.put("pileSn", record.getPileSn().trim()); + } + Map routeParams = new HashMap<>(); + routeParams.put("query", query); + return JSON.toJSONString(routeParams); + } + + private String truncate(String value, int maxLength) { + if (value == null || value.length() <= maxLength) { + return value; + } + return value.substring(0, maxLength); + } + + private String normalizeOperator(String operatorName) { + return StringUtils.isBlank(operatorName) ? TODO_OPERATOR : operatorName.trim(); } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java index 164848e2a..661905b3b 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java @@ -3386,6 +3386,7 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService { public void saveAbnormalOrder(TransactionRecordsData data) { OrderAbnormalRecord record = new OrderAbnormalRecord(); BeanUtils.copyBeanProp(record, data); + record.setOrderCode(data.getTransactionCode()); orderAbnormalRecordService.insertOrderAbnormalRecord(record); // 保存到订单主表 diff --git a/jsowell-pile/src/main/java/com/jsowell/web/controller/pile/AdapayUnsplitRecordServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/web/controller/pile/AdapayUnsplitRecordServiceImpl.java index 3bd7a815a..78ce5fe32 100644 --- a/jsowell-pile/src/main/java/com/jsowell/web/controller/pile/AdapayUnsplitRecordServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/web/controller/pile/AdapayUnsplitRecordServiceImpl.java @@ -2,10 +2,13 @@ package com.jsowell.web.controller.pile; import com.jsowell.pile.domain.AdapayUnsplitRecord; import com.jsowell.pile.mapper.AdapayUnsplitRecordMapper; +import com.jsowell.pile.service.AdapayClearingTodoService; import com.jsowell.pile.service.AdapayUnsplitRecordService; import com.jsowell.pile.vo.AdapayUnsplitRecordVO; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.Collections; import java.util.List; @@ -15,6 +18,9 @@ public class AdapayUnsplitRecordServiceImpl implements AdapayUnsplitRecordServic @Autowired private AdapayUnsplitRecordMapper adapayUnsplitRecordMapper; + @Autowired + private AdapayClearingTodoService adapayClearingTodoService; + @Override public int deleteByPrimaryKey(Integer id) { return adapayUnsplitRecordMapper.deleteByPrimaryKey(id); @@ -31,8 +37,15 @@ public class AdapayUnsplitRecordServiceImpl implements AdapayUnsplitRecordServic } @Override + @Transactional(rollbackFor = Exception.class) public int insertOrUpdateSelective(AdapayUnsplitRecord record) { - return adapayUnsplitRecordMapper.insertOrUpdateSelective(record); + int rows = adapayUnsplitRecordMapper.insertOrUpdateSelective(record); + if (rows > 0 && record != null + && (record.getRefundFlag() != null || record.getSplitFlag() != null)) { + AdapayUnsplitRecord currentRecord = loadCurrentRecord(record); + adapayClearingTodoService.syncUnsplitRecordTodo(currentRecord); + } + return rows; } @Override @@ -98,4 +111,16 @@ public class AdapayUnsplitRecordServiceImpl implements AdapayUnsplitRecordServic return adapayUnsplitRecordMapper.queryFailedList(startTime, endTime); } + private AdapayUnsplitRecord loadCurrentRecord(AdapayUnsplitRecord record) { + if (record.getId() != null) { + return adapayUnsplitRecordMapper.selectByPrimaryKey(record.getId()); + } + if (record.getPaymentId() == null) { + return record; + } + List records = adapayUnsplitRecordMapper.selectByPaymentIds( + Collections.singletonList(record.getPaymentId())); + return CollectionUtils.isEmpty(records) ? record : records.get(0); + } + } diff --git a/jsowell-quartz/src/main/java/com/jsowell/quartz/service/impl/AdapayUnsplitRecordHandleServiceImpl.java b/jsowell-quartz/src/main/java/com/jsowell/quartz/service/impl/AdapayUnsplitRecordHandleServiceImpl.java index 07a3e9f58..59dda8e67 100644 --- a/jsowell-quartz/src/main/java/com/jsowell/quartz/service/impl/AdapayUnsplitRecordHandleServiceImpl.java +++ b/jsowell-quartz/src/main/java/com/jsowell/quartz/service/impl/AdapayUnsplitRecordHandleServiceImpl.java @@ -18,6 +18,7 @@ import com.jsowell.common.util.StringUtils; import com.jsowell.pile.domain.AdapayUnsplitRecord; import com.jsowell.pile.domain.OrderBasicInfo; import com.jsowell.pile.dto.ApplyRefundDTO; +import com.jsowell.pile.service.AdapayClearingTodoService; import com.jsowell.pile.service.AdapayUnsplitRecordService; import com.jsowell.pile.service.OrderBasicInfoService; import com.jsowell.pile.vo.AdapayUnsplitRecordVO; @@ -65,6 +66,9 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord @Autowired private AdapayUnsplitRecordService adapayUnsplitRecordService; + @Autowired + private AdapayClearingTodoService adapayClearingTodoService; + @Override public void processUnSettledOrder() { for (int i = 0; i < 35; i++) { @@ -377,9 +381,10 @@ public class AdapayUnsplitRecordHandleServiceImpl implements AdapayUnsplitRecord + "expectedSplitAmount:{}, confirmedSplitAmount:{}, remainingSplitAmount:{}, refundFlag:{}, splitFlag:{}", paymentId, orderCode, dueRefundAmount, refundResult.refundedAmount, expectedSplitAmount, splitResult.confirmedSplitAmount, remaining, refundFlag, splitFlag); - return true; + } else { + adapayClearingTodoService.syncUnsplitRecordTodo(record); } - return false; + return needUpdate; } private RefundAmountResult queryRefundAmountFromAdapay(String paymentId, String wechatAppId) { 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 96c4700ca..2f5ff84fb 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 @@ -33,9 +33,9 @@ public class TodoTaskServiceImpl implements TodoTaskService { private static final String SYSTEM_OPERATOR = "system"; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final Set DEFAULT_ROUTE_WHITELIST = new HashSet<>( - Arrays.asList("invoiceDetail", "financeDetail", "accountUserInfo")); + Arrays.asList("invoiceDetail", "financeDetail", "accountUserInfo", "AbnormalRecord")); - @Value("${todo.route-whitelist:invoiceDetail,financeDetail,accountUserInfo}") + @Value("${todo.route-whitelist:invoiceDetail,financeDetail,accountUserInfo,AbnormalRecord}") private String routeWhitelist; @Autowired