|
|
@@ -2,17 +2,20 @@ package com.ruoyi.app.order;
|
|
|
|
|
|
import com.ruoyi.app.order.dto.OrderCreatItem;
|
|
|
import com.ruoyi.app.order.dto.OrderCreateInput;
|
|
|
+import com.ruoyi.app.order.dto.SelfDeliveryOperateInput;
|
|
|
import com.ruoyi.app.pay.LinePayOrderGuard;
|
|
|
import com.ruoyi.app.pay.LinePayRefundService;
|
|
|
import com.ruoyi.common.exception.ServiceException;
|
|
|
import com.ruoyi.common.utils.MessageUtils;
|
|
|
import com.ruoyi.system.domain.InfoUser;
|
|
|
import com.ruoyi.system.domain.OrderParent;
|
|
|
+import com.ruoyi.system.domain.UserBilling;
|
|
|
import com.ruoyi.system.domain.PosOrder;
|
|
|
import com.ruoyi.system.domain.PosStore;
|
|
|
import com.ruoyi.system.service.IInfoUserService;
|
|
|
import com.ruoyi.system.service.IOperatingHoursService;
|
|
|
import com.ruoyi.system.service.IOrderParentService;
|
|
|
+import com.ruoyi.system.service.IUserBillingService;
|
|
|
import com.ruoyi.system.service.IPosOrderService;
|
|
|
import com.ruoyi.system.service.IPosStoreService;
|
|
|
import com.ruoyi.system.service.MerchantAccessContext;
|
|
|
@@ -25,13 +28,18 @@ import org.junit.jupiter.api.BeforeEach;
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
import org.mockito.MockedStatic;
|
|
|
import org.mockito.ArgumentCaptor;
|
|
|
+import org.redisson.api.RLock;
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
import org.springframework.test.util.ReflectionTestUtils;
|
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
import java.util.List;
|
|
|
import java.util.Set;
|
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
|
+import static org.mockito.ArgumentMatchers.anyLong;
|
|
|
import static org.mockito.ArgumentMatchers.anyString;
|
|
|
import static org.mockito.Mockito.mockStatic;
|
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
|
@@ -319,4 +327,214 @@ class PosOrderShOprateControllerTest {
|
|
|
authContext.when(AuthContext::requireUserId).thenReturn(userId);
|
|
|
ReflectionTestUtils.setField(controller, "merchantStoreAccessService", access);
|
|
|
}
|
|
|
+
|
|
|
+ // ===== 029 商家自配送 =====
|
|
|
+
|
|
|
+ private void wireSelfDeliveryLock(PosOrderShOprateController controller) {
|
|
|
+ RedissonClient redisson = mock(RedissonClient.class);
|
|
|
+ RLock lock = mock(RLock.class);
|
|
|
+ when(redisson.getLock(anyString())).thenReturn(lock);
|
|
|
+ try {
|
|
|
+ org.mockito.Mockito.when(lock.tryLock(anyLong(), anyLong(), any(TimeUnit.class))).thenReturn(true);
|
|
|
+ } catch (InterruptedException ignored) {
|
|
|
+ }
|
|
|
+ ReflectionTestUtils.setField(controller, "redissonClient", redisson);
|
|
|
+ }
|
|
|
+
|
|
|
+ private PosOrder selfDeliveryOrder(Long state, Long deliveryStatus) {
|
|
|
+ PosOrder order = new PosOrder();
|
|
|
+ order.setId(10L);
|
|
|
+ order.setDdId("DD-SD");
|
|
|
+ order.setType(0L);
|
|
|
+ order.setState(state);
|
|
|
+ order.setDeliveryStatus(deliveryStatus);
|
|
|
+ order.setPayStatus(1L);
|
|
|
+ order.setAfterSaleStatus(0L);
|
|
|
+ order.setSelfDelivery(1);
|
|
|
+ order.setShId(101L);
|
|
|
+ order.setUserId(7L);
|
|
|
+ return order;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static SelfDeliveryOperateInput operateInput(Long id) {
|
|
|
+ SelfDeliveryOperateInput input = new SelfDeliveryOperateInput();
|
|
|
+ input.setId(id);
|
|
|
+ return input;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void selfDeliveryCompleteOfCodOrderAttributesPaymentToMerchant() {
|
|
|
+ // T031:到付自配送单送达后,用户到付账单(type=3)归因到配送方 shId
|
|
|
+ PosOrderShOprateController controller = new PosOrderShOprateController();
|
|
|
+ IInfoUserService users = mock(IInfoUserService.class);
|
|
|
+ IPosOrderService orders = mock(IPosOrderService.class);
|
|
|
+ com.ruoyi.app.order.OrderService orderService = mock(com.ruoyi.app.order.OrderService.class);
|
|
|
+ IUserBillingService userBillingService = mock(IUserBillingService.class);
|
|
|
+ ReflectionTestUtils.setField(controller, "infoUserService", users);
|
|
|
+ ReflectionTestUtils.setField(controller, "posOrderService", orders);
|
|
|
+ ReflectionTestUtils.setField(controller, "orderService", orderService);
|
|
|
+ ReflectionTestUtils.setField(controller, "userBillingService", userBillingService);
|
|
|
+ ReflectionTestUtils.setField(controller, "linePayOrderGuard", mock(LinePayOrderGuard.class));
|
|
|
+ ReflectionTestUtils.setField(controller, "orderLogHelper", mock(OrderLogHelper.class));
|
|
|
+ wireMerchantSecurity(controller, 101L);
|
|
|
+ wireSelfDeliveryLock(controller);
|
|
|
+ when(users.getOne(any())).thenReturn(user(101L, "1", null));
|
|
|
+ when(users.getById(7L)).thenReturn(null);
|
|
|
+ when(orders.update(any(PosOrder.class), any())).thenReturn(true);
|
|
|
+
|
|
|
+ PosOrder codOrder = selfDeliveryOrder(2L, 0L);
|
|
|
+ codOrder.setCollectPayment("1");
|
|
|
+ when(orders.getById(10L)).thenReturn(codOrder);
|
|
|
+
|
|
|
+ UserBilling bill = new UserBilling();
|
|
|
+ when(userBillingService.getOne(any())).thenReturn(bill);
|
|
|
+
|
|
|
+ controller.selfDeliveryComplete(JwtUtil.token("101", "merchant"), operateInput(10L));
|
|
|
+
|
|
|
+ // 商品分成 + 自配送运费入账随送达触发
|
|
|
+ verify(orderService).setSanghuBilling(codOrder);
|
|
|
+ verify(orderService).setSelfDeliveryFreightBilling(codOrder);
|
|
|
+ // 到付账单归因:paymentId=配送方(101)、state=0、balancePay=1
|
|
|
+ ArgumentCaptor<UserBilling> captor = ArgumentCaptor.forClass(UserBilling.class);
|
|
|
+ verify(userBillingService).saveOrUpdate(captor.capture());
|
|
|
+ org.junit.jupiter.api.Assertions.assertEquals("101", captor.getValue().getPaymentId());
|
|
|
+ org.junit.jupiter.api.Assertions.assertEquals("0", captor.getValue().getState());
|
|
|
+ org.junit.jupiter.api.Assertions.assertEquals("1", captor.getValue().getBalancePay());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void selfDeliveryCompleteRejectsUnpaidOrder() {
|
|
|
+ // 核验修复:商家POS现金/转账外送单 payStatus=0,未确认收款不得确认送达(对齐骑手送达语义)
|
|
|
+ PosOrderShOprateController controller = new PosOrderShOprateController();
|
|
|
+ IInfoUserService users = mock(IInfoUserService.class);
|
|
|
+ IPosOrderService orders = mock(IPosOrderService.class);
|
|
|
+ com.ruoyi.app.order.OrderService orderService = mock(com.ruoyi.app.order.OrderService.class);
|
|
|
+ ReflectionTestUtils.setField(controller, "infoUserService", users);
|
|
|
+ ReflectionTestUtils.setField(controller, "posOrderService", orders);
|
|
|
+ ReflectionTestUtils.setField(controller, "orderService", orderService);
|
|
|
+ ReflectionTestUtils.setField(controller, "linePayOrderGuard", mock(LinePayOrderGuard.class));
|
|
|
+ ReflectionTestUtils.setField(controller, "orderLogHelper", mock(OrderLogHelper.class));
|
|
|
+ wireMerchantSecurity(controller, 101L);
|
|
|
+ wireSelfDeliveryLock(controller);
|
|
|
+ when(users.getOne(any())).thenReturn(user(101L, "1", null));
|
|
|
+
|
|
|
+ PosOrder unpaid = selfDeliveryOrder(2L, 0L);
|
|
|
+ unpaid.setPayStatus(0L);
|
|
|
+ when(orders.getById(10L)).thenReturn(unpaid);
|
|
|
+ assertThrows(ServiceException.class, () -> controller.selfDeliveryComplete(
|
|
|
+ JwtUtil.token("101", "merchant"), operateInput(10L)));
|
|
|
+ verify(orders, never()).update(any(PosOrder.class), any());
|
|
|
+ verify(orderService, never()).setSelfDeliveryFreightBilling(any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void selfDeliveryOrderCanBeAcceptedAndDispatchedWithoutRider() {
|
|
|
+ PosOrderShOprateController controller = new PosOrderShOprateController();
|
|
|
+ IInfoUserService users = mock(IInfoUserService.class);
|
|
|
+ IPosOrderService orders = mock(IPosOrderService.class);
|
|
|
+ ReflectionTestUtils.setField(controller, "infoUserService", users);
|
|
|
+ ReflectionTestUtils.setField(controller, "posOrderService", orders);
|
|
|
+ ReflectionTestUtils.setField(controller, "linePayOrderGuard", mock(LinePayOrderGuard.class));
|
|
|
+ ReflectionTestUtils.setField(controller, "orderLogHelper", mock(OrderLogHelper.class));
|
|
|
+ ReflectionTestUtils.setField(controller, "orderInvoiceService", mock(OrderInvoiceService.class));
|
|
|
+ wireMerchantSecurity(controller, 101L);
|
|
|
+ when(users.getOne(any())).thenReturn(user(101L, "1", null));
|
|
|
+ when(users.list(org.mockito.ArgumentMatchers
|
|
|
+ .<com.baomidou.mybatisplus.core.conditions.Wrapper<InfoUser>>any())).thenReturn(List.of());
|
|
|
+
|
|
|
+ // 接单:现状会被 requireRiderAssigned 拦截,selfDelivery=1 放行
|
|
|
+ when(orders.getOne(any())).thenReturn(selfDeliveryOrder(0L, 0L));
|
|
|
+ controller.acceptOrder(JwtUtil.token("101", "merchant"), 10L);
|
|
|
+ ArgumentCaptor<PosOrder> acceptCaptor = ArgumentCaptor.forClass(PosOrder.class);
|
|
|
+ verify(orders).saveOrUpdate(acceptCaptor.capture());
|
|
|
+ org.junit.jupiter.api.Assertions.assertEquals(1L, acceptCaptor.getValue().getState());
|
|
|
+
|
|
|
+ // 出餐
|
|
|
+ when(orders.getOne(any())).thenReturn(selfDeliveryOrder(1L, 0L));
|
|
|
+ controller.dispatchOrder(JwtUtil.token("101", "merchant"), 10L);
|
|
|
+ ArgumentCaptor<PosOrder> dispatchCaptor = ArgumentCaptor.forClass(PosOrder.class);
|
|
|
+ verify(orders, org.mockito.Mockito.times(2)).saveOrUpdate(dispatchCaptor.capture());
|
|
|
+ org.junit.jupiter.api.Assertions.assertEquals(2L, dispatchCaptor.getValue().getState());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void selfDeliveryStartRejectsInvalidStates() {
|
|
|
+ PosOrderShOprateController controller = new PosOrderShOprateController();
|
|
|
+ IInfoUserService users = mock(IInfoUserService.class);
|
|
|
+ IPosOrderService orders = mock(IPosOrderService.class);
|
|
|
+ ReflectionTestUtils.setField(controller, "infoUserService", users);
|
|
|
+ ReflectionTestUtils.setField(controller, "posOrderService", orders);
|
|
|
+ ReflectionTestUtils.setField(controller, "linePayOrderGuard", mock(LinePayOrderGuard.class));
|
|
|
+ ReflectionTestUtils.setField(controller, "orderLogHelper", mock(OrderLogHelper.class));
|
|
|
+ ReflectionTestUtils.setField(controller, "orderInvoiceService", mock(OrderInvoiceService.class));
|
|
|
+ wireMerchantSecurity(controller, 101L);
|
|
|
+ wireSelfDeliveryLock(controller);
|
|
|
+ when(users.getOne(any())).thenReturn(user(101L, "1", null));
|
|
|
+
|
|
|
+ // 未出餐(state=1)不可开始配送
|
|
|
+ when(orders.getById(10L)).thenReturn(selfDeliveryOrder(1L, 0L));
|
|
|
+ assertThrows(ServiceException.class, () -> controller.selfDeliveryStart(
|
|
|
+ JwtUtil.token("101", "merchant"), operateInput(10L)));
|
|
|
+
|
|
|
+ // 已开始配送(deliveryStatus=2)重复开始被拒
|
|
|
+ when(orders.getById(10L)).thenReturn(selfDeliveryOrder(2L, 2L));
|
|
|
+ assertThrows(ServiceException.class, () -> controller.selfDeliveryStart(
|
|
|
+ JwtUtil.token("101", "merchant"), operateInput(10L)));
|
|
|
+ verify(orders, never()).update(any(PosOrder.class), any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void selfDeliveryCompleteMovesOrderToDelivered() {
|
|
|
+ PosOrderShOprateController controller = new PosOrderShOprateController();
|
|
|
+ IInfoUserService users = mock(IInfoUserService.class);
|
|
|
+ IPosOrderService orders = mock(IPosOrderService.class);
|
|
|
+ ReflectionTestUtils.setField(controller, "infoUserService", users);
|
|
|
+ ReflectionTestUtils.setField(controller, "posOrderService", orders);
|
|
|
+ ReflectionTestUtils.setField(controller, "linePayOrderGuard", mock(LinePayOrderGuard.class));
|
|
|
+ ReflectionTestUtils.setField(controller, "orderLogHelper", mock(OrderLogHelper.class));
|
|
|
+ ReflectionTestUtils.setField(controller, "orderService", mock(com.ruoyi.app.order.OrderService.class));
|
|
|
+ wireMerchantSecurity(controller, 101L);
|
|
|
+ wireSelfDeliveryLock(controller);
|
|
|
+ when(users.getOne(any())).thenReturn(user(101L, "1", null));
|
|
|
+ // 送达推送:用户不存在则跳过(不触发 AsyncManager)
|
|
|
+ when(users.getById(7L)).thenReturn(null);
|
|
|
+ when(orders.update(any(PosOrder.class), any())).thenReturn(true);
|
|
|
+
|
|
|
+ // 允许跳过开始配送:出餐后(deliveryStatus=0)直接确认送达
|
|
|
+ when(orders.getById(10L)).thenReturn(selfDeliveryOrder(2L, 0L));
|
|
|
+ controller.selfDeliveryComplete(JwtUtil.token("101", "merchant"), operateInput(10L));
|
|
|
+ ArgumentCaptor<PosOrder> captor = ArgumentCaptor.forClass(PosOrder.class);
|
|
|
+ verify(orders).update(captor.capture(), any());
|
|
|
+ org.junit.jupiter.api.Assertions.assertEquals(3L, captor.getValue().getState());
|
|
|
+ org.junit.jupiter.api.Assertions.assertEquals(3L, captor.getValue().getDeliveryStatus());
|
|
|
+ org.junit.jupiter.api.Assertions.assertNotNull(captor.getValue().getSdTime());
|
|
|
+
|
|
|
+ // 从配送中(deliveryStatus=2)确认送达同样合法
|
|
|
+ when(orders.getById(10L)).thenReturn(selfDeliveryOrder(2L, 2L));
|
|
|
+ controller.selfDeliveryComplete(JwtUtil.token("101", "merchant"), operateInput(10L));
|
|
|
+ verify(orders, org.mockito.Mockito.times(2)).update(any(PosOrder.class), any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void selfDeliveryOperateRejectsNonSelfDeliveryOrder() {
|
|
|
+ PosOrderShOprateController controller = new PosOrderShOprateController();
|
|
|
+ IInfoUserService users = mock(IInfoUserService.class);
|
|
|
+ IPosOrderService orders = mock(IPosOrderService.class);
|
|
|
+ ReflectionTestUtils.setField(controller, "infoUserService", users);
|
|
|
+ ReflectionTestUtils.setField(controller, "posOrderService", orders);
|
|
|
+ ReflectionTestUtils.setField(controller, "linePayOrderGuard", mock(LinePayOrderGuard.class));
|
|
|
+ ReflectionTestUtils.setField(controller, "orderLogHelper", mock(OrderLogHelper.class));
|
|
|
+ wireMerchantSecurity(controller, 101L);
|
|
|
+ wireSelfDeliveryLock(controller);
|
|
|
+ when(users.getOne(any())).thenReturn(user(101L, "1", null));
|
|
|
+
|
|
|
+ // 普通骑手单(selfDelivery=0)不能走自配送操作
|
|
|
+ PosOrder riderOrder = selfDeliveryOrder(2L, 0L);
|
|
|
+ riderOrder.setSelfDelivery(0);
|
|
|
+ riderOrder.setQsId(88L);
|
|
|
+ when(orders.getById(10L)).thenReturn(riderOrder);
|
|
|
+ assertThrows(ServiceException.class, () -> controller.selfDeliveryComplete(
|
|
|
+ JwtUtil.token("101", "merchant"), operateInput(10L)));
|
|
|
+ verify(orders, never()).update(any(PosOrder.class), any());
|
|
|
+ }
|
|
|
}
|