fix(lastleg): enforce container warehouse plan facts
This commit is contained in:
+10
@@ -1,5 +1,6 @@
|
||||
package com.baiyue.lastleg.inboundplan;
|
||||
|
||||
import com.baiyue.lastleg.inboundplan.dto.ManualInboundPlanRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -16,6 +17,15 @@ class TailPlanCommandOpenApiContractTest {
|
||||
String api = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
assertTrue(api.contains("/api/platform/v1/last-leg/inbound-plans/manual:"));
|
||||
assertFalse(api.contains("/api/platform/v1/last-leg/inbound-plans/convert:"));
|
||||
assertFalse(api.contains("plannedWarehouseId"));
|
||||
assertFalse(api.contains("plannedWarehouseCode"));
|
||||
assertFalse(api.contains("plannedWarehouseName"));
|
||||
int manualSchemaStart = api.indexOf(" ManualInboundPlanRequest:");
|
||||
int manualSchemaEnd = api.indexOf("\n PageMetadata:", manualSchemaStart);
|
||||
assertTrue(manualSchemaStart >= 0 && manualSchemaEnd > manualSchemaStart);
|
||||
assertFalse(api.substring(manualSchemaStart, manualSchemaEnd).contains("inboundCode"));
|
||||
assertFalse(java.util.Arrays.stream(ManualInboundPlanRequest.class.getRecordComponents())
|
||||
.anyMatch(component -> "inboundCode".equals(component.getName())));
|
||||
assertTrue(api.contains("/api/platform/v1/last-leg/orders/{id}/hold:"));
|
||||
assertFalse(api.contains("/api/platform/v1/last-leg/orders/{id}/detained:"));
|
||||
assertTrue(api.contains("/api/platform/v1/last-leg/orders/{id}/manual-nodes:"));
|
||||
|
||||
+1
-2
@@ -101,7 +101,6 @@ class InboundPlanControllerTest {
|
||||
|
||||
private static ManualInboundPlanRequest manualRequest() {
|
||||
return new ManualInboundPlanRequest("193000000000200003", 9223372036854775807L, 0,
|
||||
193000000000200003L, "US-LAX-01", "洛杉矶仓", LocalDate.of(2026, 9, 8),
|
||||
"FBA-001", 2, "STANDARD", false, true, true, "加急入仓");
|
||||
LocalDate.of(2026, 9, 8), 2, "STANDARD", false, true, true, "加急入仓");
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -34,4 +34,17 @@ class InboundPlanOrderMapperContractTest {
|
||||
assertTrue(sql.contains("customer_id=#{customerId} AND id=#{orderId}"));
|
||||
assertTrue(!sql.contains("destination_warehouse"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void manualPlanLocksEveryOrderInTheSelectedStableContainer() throws Exception {
|
||||
Select select = InboundPlanOrderMapper.class
|
||||
.getMethod("lockContainerWarehouseFacts", String.class, Long.class)
|
||||
.getAnnotation(Select.class);
|
||||
String sql = String.join(" ", select.value());
|
||||
|
||||
assertTrue(sql.contains("c.source_container_booking_id=target.source_container_booking_id"));
|
||||
assertTrue(sql.contains("target.customer_id=#{customerId} AND target.id=#{orderId}"));
|
||||
assertTrue(sql.contains("c.overseas_warehouse_id AS overseasWarehouseId"));
|
||||
assertTrue(sql.contains("ORDER BY c.customer_id,c.id FOR UPDATE"));
|
||||
}
|
||||
}
|
||||
|
||||
+71
-4
@@ -78,6 +78,12 @@ class InboundPlanServiceTest {
|
||||
org.mockito.Mockito.lenient().when(orderMapper.findReceivingWarehouse(any(), any())).thenReturn(
|
||||
new InboundPlanOrderMapper.ReceivingWarehouseSnapshot(
|
||||
"193000000000200003", "US-LAX-01", "洛杉矶仓"));
|
||||
org.mockito.Mockito.lenient().when(warehouseGateway.requireActiveSharedWarehouse(193000000000200003L)).thenReturn(
|
||||
new WarehouseResourceGateway.WarehouseReference(
|
||||
193000000000200003L, "US-LAX-01", "洛杉矶仓", "ACTIVE"));
|
||||
org.mockito.Mockito.lenient().when(orderMapper.lockContainerWarehouseFacts(any(), any())).thenReturn(List.of(
|
||||
new InboundPlanOrderMapper.ContainerWarehouseRef(
|
||||
"100", 2L, "BOOKING-1", "193000000000200003")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -133,8 +139,7 @@ class InboundPlanServiceTest {
|
||||
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, "优先收货");
|
||||
LocalDate.of(2026, 9, 15), 2, "STANDARD", true, true, true, "优先收货");
|
||||
|
||||
var result = service.convertManual(request, "manual-2", "900");
|
||||
|
||||
@@ -149,6 +154,46 @@ class InboundPlanServiceTest {
|
||||
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());
|
||||
verify(orderMapper).lockContainerWarehouseFacts("100", 2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void manuallyCreatesPlanWhenStableWarehouseIdExistsWithoutDisplaySnapshots() {
|
||||
SourceOrderSnapshot source = new SourceOrderSnapshot(2L, "100", "MSCU6639870", "RK-2",
|
||||
"PENDING_ASSIGNMENT", null, BigDecimal.ONE, 10, 0,
|
||||
"193000000000200003", null, null, null,
|
||||
"WH-A", "WH-A", "仓A", 1, 0, false, 0);
|
||||
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(3), eq(false), eq(0), any()))
|
||||
.thenReturn(1);
|
||||
|
||||
var result = service.convertManual(new ManualInboundPlanRequest("100", 2L, 0,
|
||||
LocalDate.of(2026, 9, 15), 3, "STANDARD", true, false, false, null),
|
||||
"manual-id-only", "900");
|
||||
|
||||
assertEquals("US-LAX-01", result.overseasWarehouseCode());
|
||||
assertEquals("洛杉矶仓", result.overseasWarehouseName());
|
||||
verify(warehouseGateway).requireActiveSharedWarehouse(193000000000200003L);
|
||||
verify(planMapper).insert(any(InboundPlan.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void manualPlanRejectsMixedContainerWarehousesBeforeLockingOrderOrWriting() {
|
||||
when(orderMapper.lockContainerWarehouseFacts("100", 2L)).thenReturn(List.of(
|
||||
new InboundPlanOrderMapper.ContainerWarehouseRef("100", 2L, "BOOKING-1", "193000000000200003"),
|
||||
new InboundPlanOrderMapper.ContainerWarehouseRef("200", 3L, "BOOKING-1", "193000000000200004")));
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class, () -> service.convertManual(
|
||||
new ManualInboundPlanRequest("100", 2L, 0, LocalDate.of(2026, 9, 15),
|
||||
1, "STANDARD", false, false, false, null), "mixed-warehouse", "900"));
|
||||
|
||||
assertEquals(409, error.getStatusCode().value());
|
||||
verify(orderMapper, never()).lockOrder(any(), any());
|
||||
verify(planMapper, never()).insert(any(InboundPlan.class));
|
||||
verify(commandMapper, never()).insert(any(com.baiyue.lastleg.entity.LastlegOrderCommandRecord.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -160,8 +205,8 @@ class InboundPlanServiceTest {
|
||||
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),
|
||||
new ManualInboundPlanRequest("100", 2L, 5, LocalDate.of(2026, 9, 15),
|
||||
1, "STANDARD", false, false, false, null),
|
||||
"manual-held", "900"));
|
||||
|
||||
assertEquals(409, error.getStatusCode().value());
|
||||
@@ -217,6 +262,8 @@ class InboundPlanServiceTest {
|
||||
when(planMapper.selectOne(any())).thenReturn(plan);
|
||||
when(orderMapper.findReceivingWarehouse("100", 1L)).thenReturn(
|
||||
new InboundPlanOrderMapper.ReceivingWarehouseSnapshot("88", "US-NY-01", "纽约仓"));
|
||||
when(warehouseGateway.requireActiveSharedWarehouse(88L)).thenReturn(
|
||||
new WarehouseResourceGateway.WarehouseReference(88L, "US-NY-01", "纽约仓", "ACTIVE"));
|
||||
|
||||
var result = service.detailByPlanNo("100", "LIP-1");
|
||||
|
||||
@@ -440,6 +487,26 @@ class InboundPlanServiceTest {
|
||||
verify(inventoryBalanceMapper, never()).bindInboundPlan(any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void containerConversionRejectsMixedWarehouseSnapshotsBeforeCreatingAnyPlan() {
|
||||
when(orderMapper.lockContainerOrders("BOOKING-1")).thenReturn(List.of(
|
||||
new InboundPlanOrderMapper.ContainerOrderRef("100", 1L, "PENDING_ASSIGNMENT", "MSCU6639870", "STANDARD"),
|
||||
new InboundPlanOrderMapper.ContainerOrderRef("200", 2L, "PENDING_ASSIGNMENT", "MSCU6639870", "STANDARD")));
|
||||
when(orderMapper.lockOrder("100", 1L)).thenReturn(List.of(order("100", 1L, "RK-1", "WH-A")));
|
||||
SourceOrderSnapshot differentWarehouse = new SourceOrderSnapshot(2L, "200", "MSCU6639870", "RK-2",
|
||||
"PENDING_ASSIGNMENT", null, BigDecimal.ONE, 10, 0,
|
||||
"193000000000200004", null, null, null, "WH-B", "WH-B", "仓B", 1, 0, false, 0);
|
||||
when(orderMapper.lockOrder("200", 2L)).thenReturn(List.of(differentWarehouse));
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class,
|
||||
() -> service.convertContainer("BOOKING-1"));
|
||||
|
||||
assertEquals(409, error.getStatusCode().value());
|
||||
verify(planMapper, never()).insert(any(InboundPlan.class));
|
||||
verify(orderMapper, never()).markPlanned(any(), any(), any(), org.mockito.ArgumentMatchers.anyInt(), any());
|
||||
verify(inventoryBalanceMapper, never()).bindInboundPlan(any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeContainerReplayReturnsStableExistingPlansWithoutWriting() {
|
||||
var refs = List.of(
|
||||
|
||||
Reference in New Issue
Block a user