fix: auto materialize completed container plans

This commit is contained in:
2026-09-10 17:05:40 +08:00
parent 343c6e278a
commit d37e6cf147
7 changed files with 121 additions and 5 deletions
@@ -68,7 +68,7 @@ class InboundPlanControllerTest {
void platformManualPlanPassesVersionedIdempotentCommand() {
ManualInboundPlanRequest request = manualRequest();
controller.manual(platform(), "manual-key", request);
verify(service).convertManual(request, "manual-key", "7");
verify(containerConversion).convertManualAndAutoMaterialize(request, "manual-key", "7");
}
@Test
@@ -76,7 +76,7 @@ class InboundPlanControllerTest {
AuthenticatedUser customer = new AuthenticatedUser(8L, 100L, 100L, 9L, "customer", "MERCHANT", "WEB", Set.of());
assertThatThrownBy(() -> controller.manual(customer, "manual-key", manualRequest()))
.isInstanceOf(ResponseStatusException.class);
verify(service, never()).convertManual(org.mockito.ArgumentMatchers.any(),
verify(containerConversion, never()).convertManualAndAutoMaterialize(org.mockito.ArgumentMatchers.any(),
org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any());
}
@@ -2,6 +2,8 @@ package com.baiyue.lastleg.inboundplan.service;
import com.baiyue.lastleg.inboundplan.dto.ConvertContainerInboundPlanRequest;
import com.baiyue.lastleg.inboundplan.dto.ContainerInboundPlanPreviewResponse;
import com.baiyue.lastleg.inboundplan.dto.InboundPlanResponse;
import com.baiyue.lastleg.inboundplan.dto.ManualInboundPlanRequest;
import com.baiyue.lastleg.inboundplan.entity.ContainerPlanConversionCommand;
import com.baiyue.lastleg.inboundplan.mapper.ContainerPlanConversionCommandMapper;
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
@@ -18,6 +20,7 @@ import org.springframework.transaction.support.SimpleTransactionStatus;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.math.BigDecimal;
import java.util.List;
@@ -28,6 +31,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.doReturn;
@ExtendWith(MockitoExtension.class)
class ContainerInboundPlanConversionCoordinatorTest {
@@ -57,6 +61,43 @@ class ContainerInboundPlanConversionCoordinatorTest {
.thenReturn(new SimpleTransactionStatus());
}
@Test
void finalManualPlanAutomaticallyMaterializesSingleOrderContainer() {
ManualInboundPlanRequest request = manualRequest();
InboundPlanResponse logical = plan("IP-MANUAL", List.of());
InboundPlanResponse physical = plan("IP-MANUAL", List.of(new InboundPlanResponse.Pallet(
31L, "MSCU6639870-101-001", null, "ACY2", "Amazon ACY2", BigDecimal.ONE,
BigDecimal.ONE, 1, 51L, 61L, 71L, "WOOD", "LOCKED", LocalDateTime.now(), null, 0)));
ContainerInboundPlanConversionCoordinator spy = org.mockito.Mockito.spy(coordinator);
when(plans.convertManual(request, "manual-key", "7")).thenReturn(logical);
when(plans.sourceContainerBookingId("100", 11L)).thenReturn(BOOKING_ID);
when(plans.previewContainer(BOOKING_ID)).thenReturn(manualPreview(List.of(resourcePendingOrder())));
doReturn(List.of(physical)).when(spy).confirm(any(ConvertContainerInboundPlanRequest.class), any(String.class));
assertEquals(physical, spy.convertManualAndAutoMaterialize(request, "manual-key", "7"));
verify(spy).confirm(eq(new ConvertContainerInboundPlanRequest(BOOKING_ID, 7L, SNAPSHOT_HASH)),
any(String.class));
}
@Test
void earlierManualPlanWaitsUntilEveryOrderInContainerHasAPlan() {
ManualInboundPlanRequest request = manualRequest();
InboundPlanResponse logical = plan("IP-MANUAL", List.of());
var ready = new ContainerInboundPlanPreviewResponse.Order(
"200", "Customer 200", "12", "LM-12", "RK-2", "FIRST_LEG", "FBA",
null, "ONT8", "Amazon ONT8", "ONT8", 10, BigDecimal.ONE, 1,
null, null, null, 0, "READY", List.of());
when(plans.convertManual(request, "manual-key", "7")).thenReturn(logical);
when(plans.sourceContainerBookingId("100", 11L)).thenReturn(BOOKING_ID);
when(plans.previewContainer(BOOKING_ID)).thenReturn(manualPreview(List.of(resourcePendingOrder(), ready)));
assertEquals(logical, coordinator.convertManualAndAutoMaterialize(request, "manual-key", "7"));
verify(commands, never()).findByBooking(any(String.class));
verify(warehouse, never()).ensurePalletBatch(any());
}
@Test
void anotherIdempotencyKeyForCompletedCabinetReplaysWithoutCreatingAnotherBatch() throws Exception {
ConvertContainerInboundPlanRequest request = request();
@@ -366,6 +407,36 @@ class ContainerInboundPlanConversionCoordinatorTest {
fbaWarehouseCode, 10, BigDecimal.ONE, 1, null, null, null, 0, "READY", List.of());
}
private ManualInboundPlanRequest manualRequest() {
return new ManualInboundPlanRequest("100", 11L, 0, LocalDate.of(2026, 9, 10),
1, "WOOD", false, false, false, "自动生成");
}
private ContainerInboundPlanPreviewResponse.Order resourcePendingOrder() {
return new ContainerInboundPlanPreviewResponse.Order(
"100", "Customer 100", "11", "LM-11", "RK-1", "FIRST_LEG", "FBA",
null, "ACY2", "Amazon ACY2", "ACY2", 10, BigDecimal.ONE, 1,
1, "IP-MANUAL", "PENDING_ASSIGNMENT", 1, "RESOURCE_PENDING", List.of());
}
private ContainerInboundPlanPreviewResponse manualPreview(
List<ContainerInboundPlanPreviewResponse.Order> orders) {
int pending = (int) orders.stream().filter(order -> "RESOURCE_PENDING".equals(order.readiness())
|| "READY".equals(order.readiness())).count();
return new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01",
"Los Angeles", 7L, SNAPSHOT_HASH, orders.size(), orders.size(), 0, pending, pending,
0, orders.size(), 1, true,
List.of(new ContainerInboundPlanPreviewResponse.Group("FBA", null, "ACY2", "Amazon ACY2",
orders.size(), pending, pending, 0, orders.size(), 1, orders)));
}
private InboundPlanResponse plan(String planNo, List<InboundPlanResponse.Pallet> pallets) {
return new InboundPlanResponse(21L, planNo, "100", 11L, "MSCU6639870", "US-LAX-01",
"Los Angeles", "PENDING_ASSIGNMENT", 1, 1, BigDecimal.ONE, 10, 0,
List.of(), pallets, List.of(), 1, LocalDate.of(2026, 9, 10), "WOOD", false,
false, false, "MANUAL", "自动生成");
}
private ConvertContainerInboundPlanRequest request() {
return new ConvertContainerInboundPlanRequest(BOOKING_ID, 7L, SNAPSHOT_HASH);
}