feat: complete tail order controls and terminal drayage

This commit is contained in:
2026-09-07 23:58:18 +08:00
parent f018ca85a9
commit e32fe65fa8
53 changed files with 2566 additions and 87 deletions
@@ -3,6 +3,7 @@ package com.baiyue.lastleg.inboundplan.service;
import com.baiyue.lastleg.inboundplan.entity.InboundPlan;
import com.baiyue.lastleg.inboundplan.dto.InboundPlanRequests;
import com.baiyue.lastleg.inboundplan.dto.UpdateInboundPlanRequest;
import com.baiyue.lastleg.inboundplan.dto.ManualInboundPlanRequest;
import com.baiyue.lastleg.inboundplan.entity.InboundPlanLine;
import com.baiyue.lastleg.inboundplan.entity.InboundPlanPallet;
import com.baiyue.lastleg.inboundplan.entity.InboundPlanPalletAllocation;
@@ -15,6 +16,9 @@ import com.baiyue.lastleg.inboundplan.model.SourceOrderSnapshot;
import com.baiyue.lastleg.inventory.mapper.InventoryBalanceMapper;
import com.baiyue.lastleg.dropship.model.DropshipPalletSnapshot;
import com.baiyue.lastleg.dropship.service.DropshipOrderService;
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
import com.baiyue.lastleg.mapper.LastlegOrderCommandRecordMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -28,11 +32,13 @@ import org.springframework.test.util.ReflectionTestUtils;
import java.math.BigDecimal;
import java.util.List;
import java.time.LocalDateTime;
import java.time.LocalDate;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -47,6 +53,8 @@ class InboundPlanServiceTest {
@Mock InboundPlanOrderMapper orderMapper;
@Mock InventoryBalanceMapper inventoryBalanceMapper;
@Mock DropshipOrderService dropshipOrders;
@Mock WarehouseResourceGateway warehouseGateway;
@Mock LastlegOrderCommandRecordMapper commandMapper;
private InboundPlanService service;
@@ -60,6 +68,9 @@ class InboundPlanServiceTest {
ReflectionTestUtils.setField(service, "orderMapper", orderMapper);
ReflectionTestUtils.setField(service, "inventoryBalanceMapper", inventoryBalanceMapper);
ReflectionTestUtils.setField(service, "dropshipOrders", dropshipOrders);
ReflectionTestUtils.setField(service, "warehouseGateway", warehouseGateway);
ReflectionTestUtils.setField(service, "commandMapper", commandMapper);
ReflectionTestUtils.setField(service, "objectMapper", JsonMapper.builder().findAndAddModules().build());
ReflectionTestUtils.setField(service, "orderNoGenerator", new com.baiyue.lastleg.service.LastlegOrderNoGenerator());
org.mockito.Mockito.lenient().when(lineMapper.selectList(any())).thenReturn(List.of());
org.mockito.Mockito.lenient().when(palletMapper.selectList(any())).thenReturn(List.of());
@@ -112,6 +123,52 @@ class InboundPlanServiceTest {
assertEquals("洛杉矶仓", result.overseasWarehouseName());
}
@Test
void manuallyCreatesOneIndependentPlanWithFrozenMetadata() {
SourceOrderSnapshot source = order(2L, "RK-2", "1.0000", "WH-A");
when(orderMapper.lockOrder("100", 2L)).thenReturn(List.of(source));
when(warehouseGateway.requireActiveSharedWarehouse(193000000000200003L)).thenReturn(
new WarehouseResourceGateway.WarehouseReference(
193000000000200003L, "US-LAX-01", "洛杉矶仓", "ACTIVE"));
when(orderMapper.markPlannedManual(eq("100"), eq(2L), any(), eq(2), eq(true), eq(0), any()))
.thenReturn(1);
ManualInboundPlanRequest request = new ManualInboundPlanRequest("100", 2L, 0,
193000000000200003L, "US-LAX-01", "洛杉矶仓", LocalDate.of(2026, 9, 15),
"RK-2", 2, "STANDARD", true, true, true, "优先收货");
var result = service.convertManual(request, "manual-2", "900");
ArgumentCaptor<InboundPlan> plan = ArgumentCaptor.forClass(InboundPlan.class);
verify(planMapper).insert(plan.capture());
assertEquals(2L, plan.getValue().getSourceOrderId());
assertEquals(LocalDate.of(2026, 9, 15), plan.getValue().getPlannedArrivalDate());
assertEquals("STANDARD", plan.getValue().getPalletType());
assertEquals("MANUAL", plan.getValue().getCreationMode());
assertEquals(2, result.palletCount());
verify(palletMapper, times(2)).insert(any(InboundPlanPallet.class));
verify(orderMapper).markPlannedManual(eq("100"), eq(2L), any(), eq(2), eq(true), eq(0), any());
verify(commandMapper).insert(any(com.baiyue.lastleg.entity.LastlegOrderCommandRecord.class));
verify(orderMapper, never()).lockContainerOrders(any());
}
@Test
void refusesManualPlanForHeldOrderBeforeWriting() {
SourceOrderSnapshot held = new SourceOrderSnapshot(2L, "100", "MSCU6639870", "RK-2",
"PENDING_ASSIGNMENT", null, new BigDecimal("1.0000"), 10, 0,
"193000000000200003", "US-LAX-01", "洛杉矶仓", null,
null, "WH-A", "仓A", 1, 0, true, 5);
when(orderMapper.lockOrder("100", 2L)).thenReturn(List.of(held));
ResponseStatusException error = assertThrows(ResponseStatusException.class, () -> service.convertManual(
new ManualInboundPlanRequest("100", 2L, 5, 193000000000200003L, "US-LAX-01", "洛杉矶仓",
LocalDate.of(2026, 9, 15), "RK-2", 1, "STANDARD", false, false, false, null),
"manual-held", "900"));
assertEquals(409, error.getStatusCode().value());
verify(planMapper, never()).insert(any(InboundPlan.class));
verify(commandMapper, never()).insert(any(com.baiyue.lastleg.entity.LastlegOrderCommandRecord.class));
}
@Test
void convertsAConsistentDestinationAssignedOrder() {
SourceOrderSnapshot assigned = new SourceOrderSnapshot(2L, "100", "MSCU6639870", "RK-2",