package com.baiyue.lastleg.service; import com.baiyue.lastleg.dto.LastlegOrderRequests; import com.baiyue.lastleg.entity.LastlegOrder; import com.baiyue.lastleg.entity.LastlegOrderDetail; import com.baiyue.lastleg.entity.LastlegOrderStatusHistory; import com.baiyue.lastleg.inboundplan.entity.InboundPlanCarton; import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper; import com.baiyue.lastleg.inventory.command.service.InventoryCommandService; import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper; import com.baiyue.lastleg.mapper.LastlegOrderMapper; import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; import com.baiyue.lastleg.mapper.LastlegOrderFeeMapper; import com.baiyue.lastleg.mapper.LastlegOrderOperationLogMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.web.server.ResponseStatusException; import org.springframework.test.util.ReflectionTestUtils; import java.util.List; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class LastlegOrderServiceTest { @Mock LastlegOrderMapper orders; @Mock LastlegOrderDetailMapper details; @Mock LastlegOrderStatusHistoryMapper histories; @Mock LastlegOrderFeeMapper fees; @Mock LastlegOrderOperationLogMapper operations; @Mock InboundPlanCartonMapper cartons; @Mock InventoryCommandService inventory; LastlegOrderService service; @BeforeEach void setUp() { service = new LastlegOrderService(); ReflectionTestUtils.setField(service, "orderMapper", orders); ReflectionTestUtils.setField(service, "detailMapper", details); ReflectionTestUtils.setField(service, "historyMapper", histories); ReflectionTestUtils.setField(service, "feeMapper", fees); ReflectionTestUtils.setField(service, "operationLogMapper", operations); ReflectionTestUtils.setField(service, "cartonMapper", cartons); ReflectionTestUtils.setField(service, "inventory", inventory); } @Test void detailDoesNotExposeDropshipCartonsOrInternalEventIds() { LastlegOrder order = order(11L, "100", "PENDING_ASSIGNMENT", null); order.setOrderType("ONE_CLICK_DROPSHIP"); when(orders.selectOne(any())).thenReturn(order); when(details.selectList(any())).thenReturn(List.of()); when(histories.selectList(any())).thenReturn(List.of()); when(fees.selectList(any())).thenReturn(List.of()); when(operations.selectList(any())).thenReturn(List.of()); var response = service.detail("100", 11L); assertEquals(11L, response.order().id()); assertEquals("100", response.order().customerId()); assertEquals("ONE_CLICK_DROPSHIP", response.order().orderType()); assertEquals("一件代发", response.order().orderTypeName()); assertTrue(java.util.Arrays.stream(response.getClass().getRecordComponents()) .noneMatch(component -> "dropshipPallets".equals(component.getName()))); assertTrue(java.util.Arrays.stream(com.baiyue.lastleg.dto.LastlegOrderDetailResponse.History.class.getRecordComponents()) .noneMatch(component -> "eventId".equals(component.getName()))); } @Test void transferUpdatesOrderAndEveryCargoSnapshot() { LastlegOrder order = order(11L, "100", "PENDING_ASSIGNMENT", null); when(orders.selectList(any())).thenReturn(List.of(order)); var request = new LastlegOrderRequests.TransferDestination( List.of(11L), "wh-2", "LAX-02", "Los Angeles 02", null); assertEquals(1, service.transferDestination("100", request)); assertEquals("wh-2", order.getTransferWarehouseId()); assertEquals("LAX-02", order.getTransferWarehouseCode()); assertEquals("DESTINATION_ASSIGNED", order.getCurrentStatus()); ArgumentCaptor cargo = ArgumentCaptor.forClass(LastlegOrderDetail.class); verify(details).update(cargo.capture(), any()); assertEquals("LAX-02", cargo.getValue().getDestinationWarehouseCode()); ArgumentCaptor carton = ArgumentCaptor.forClass(InboundPlanCarton.class); verify(cartons).update(carton.capture(), any()); assertEquals("wh-2", carton.getValue().getDestinationWarehouseId()); assertEquals("LAX-02", carton.getValue().getDestinationWarehouseCode()); ArgumentCaptor history = ArgumentCaptor.forClass(LastlegOrderStatusHistory.class); verify(histories).insert(history.capture()); assertEquals("100", history.getValue().getCustomerId()); assertEquals("PENDING_ASSIGNMENT", history.getValue().getFromStatus()); assertEquals("DESTINATION_ASSIGNED", history.getValue().getToStatus()); } @Test void createFeePersistsFeeAndOperationForOwnedOrder() { when(orders.selectOne(any())).thenReturn(order(11L, "100", "PENDING_ASSIGNMENT", null)); var request = new LastlegOrderRequests.CreateFee( "清关费", "USD", new BigDecimal("420.00"), "PENDING_REVIEW", "报关代理"); var fee = service.createFee("100", 11L, request, "platform_admin"); assertEquals("100", fee.getCustomerId()); assertEquals("清关费", fee.getFeeItem()); assertEquals(new BigDecimal("420.00"), fee.getAmount()); verify(fees).insert(any(com.baiyue.lastleg.entity.LastlegOrderFee.class)); verify(operations).insert(any(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class)); } @Test void palletMergeMarkIsPersistedAndAuditedForPdaReminder() { LastlegOrder order = order(11L, "100", "INBOUND_PLANNED", "IB-1"); when(orders.selectOne(any())).thenReturn(order); when(orders.lockById("100", 11L)).thenReturn(order); when(orders.updatePalletMergeFlag(any(), anyLong(), any(), any())).thenReturn(1); service.setPalletMergeRequired("100", 11L, new LastlegOrderRequests.SetPalletMergeRequired(true), "platform_admin"); verify(orders).updatePalletMergeFlag(org.mockito.ArgumentMatchers.eq("100"), org.mockito.ArgumentMatchers.eq(11L), org.mockito.ArgumentMatchers.eq("REQUIRED"), any()); ArgumentCaptor operation = ArgumentCaptor.forClass(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class); verify(operations).insert(operation.capture()); assertEquals("标记合托", operation.getValue().getOperationNode()); assertTrue(operation.getValue().getResult().contains("PDA")); } @Test void completedPhysicalMergeCannotBeUnmarked() { LastlegOrder order = order(11L, "100", "INBOUND_PLANNED", "IB-1"); order.setPalletMergeFlag("MERGED"); when(orders.selectOne(any())).thenReturn(order); when(orders.lockById("100", 11L)).thenReturn(order); assertThrows(ResponseStatusException.class, () -> service.setPalletMergeRequired("100", 11L, new LastlegOrderRequests.SetPalletMergeRequired(false), "platform_admin")); verify(orders, never()).updatePalletMergeFlag(any(), anyLong(), any(), any()); } @Test void transferRejectsAnOrderAlreadyBoundToInboundPlan() { LastlegOrder order = order(11L, "100", "DESTINATION_ASSIGNED", "IB-1"); when(orders.selectList(any())).thenReturn(List.of(order)); var request = new LastlegOrderRequests.TransferDestination( List.of(11L), "wh-2", "LAX-02", "Los Angeles 02", null); assertThrows(ResponseStatusException.class, () -> service.transferDestination("100", request)); verify(orders, never()).updateById(any(LastlegOrder.class)); verify(details, never()).update(any(), any()); verify(cartons, never()).update(any(), any()); } @Test void batchRejectsMissingOrCrossCustomerIdsBeforeWriting() { when(orders.selectList(any())).thenReturn(List.of(order(11L, "100", "PENDING_ASSIGNMENT", null))); var request = new LastlegOrderRequests.TransferDestination( List.of(11L, 22L), "wh-2", "LAX-02", "Los Angeles 02", null); ResponseStatusException error = assertThrows(ResponseStatusException.class, () -> service.transferDestination("100", request)); assertTrue(error.getStatusCode().is4xxClientError()); verify(orders, never()).updateById(any(LastlegOrder.class)); } @Test void pageUsesPaginationAndPlatformFiltersWithoutCustomerScope() { var query = new LastlegOrderRequests.Query(3, 25, "LL-", "FIRST_LEG", "IN-", null, "", "PENDING_ASSIGNMENT", null, "LAX", null, null, null, null, null); var resultPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page(3, 25); resultPage.setTotal(0); when(orders.selectPage(any(), any())).thenReturn(resultPage); service.page(null, query); ArgumentCaptor> page = ArgumentCaptor.forClass(com.baomidou.mybatisplus.extension.plugins.pagination.Page.class); verify(orders).selectPage(page.capture(), any()); assertEquals(3, page.getValue().getCurrent()); assertEquals(25, page.getValue().getSize()); } @Test void pageReturnsConfirmedPalletMergeRelatedOrderNumbers() { LastlegOrder order = order(11L, "100", "INBOUND_COMPLETED", "IB-1"); order.setPalletMergeFlag("MERGED"); var resultPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page(1, 10); resultPage.setRecords(List.of(order)); resultPage.setTotal(1); when(orders.selectPage(any(), any())).thenReturn(resultPage); when(orders.findPalletMergeRelations(List.of(11L))).thenReturn(List.of( new LastlegOrderMapper.PalletMergeRelationRow(11L, "LMO-RELATED-2", "PMG-1"), new LastlegOrderMapper.PalletMergeRelationRow(11L, "LMO-RELATED-3", "PMG-1"))); var query = new LastlegOrderRequests.Query(1, 10, null, null, null, null, null, null, null, null, null, null, null, null, null); var result = service.page(null, query); assertEquals("PMG-1", result.records().get(0).palletMergeBatchId()); assertEquals(List.of("LMO-RELATED-2", "LMO-RELATED-3"), result.records().get(0).palletMergeOrderNos()); } @Test void holdingBeforeInboundPersistsTheOrderMarkerAndOperationRemark() { LastlegOrder order = order(11L, "100", "PENDING_INBOUND", null); when(orders.selectOne(any())).thenReturn(order); when(orders.lockById("100", 11L)).thenReturn(order); service.setDetained("100", 11L, new LastlegOrderRequests.SetDetained(true, "等待质检"), "operator"); assertTrue(order.getDetained()); verify(orders).updateById(order); ArgumentCaptor operation = ArgumentCaptor.forClass(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class); verify(operations).insert(operation.capture()); assertEquals("暂扣", operation.getValue().getOperationNode()); assertEquals("等待质检", operation.getValue().getResult()); verify(inventory, never()).releaseInboundOrderHold(any(), any(), anyLong(), any(), any()); } @Test void releasingAfterInboundMovesAllOrderHeldInventoryToAvailable() { LastlegOrder order = order(11L, "100", "INBOUND_COMPLETED", "IP-1"); order.setOrderNo("LM-1"); order.setDetained(true); when(orders.selectOne(any())).thenReturn(order); when(orders.lockById("100", 11L)).thenReturn(order); service.setDetained("100", 11L, new LastlegOrderRequests.SetDetained(false, "质检通过"), "operator"); verify(inventory).releaseInboundOrderHold("100", "operator", 11L, "LM-1", "质检通过"); assertEquals(false, order.getDetained()); } private static LastlegOrder order(long id, String customerId, String status, String inboundOrderNo) { LastlegOrder order = new LastlegOrder(); order.setId(id); order.setCustomerId(customerId); order.setCurrentStatus(status); order.setInboundOrderNo(inboundOrderNo); return order; } }