diff --git a/fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/SchedulingFacade.java b/fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/SchedulingFacade.java index a684c9f..4fa2f06 100644 --- a/fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/SchedulingFacade.java +++ b/fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/SchedulingFacade.java @@ -39,6 +39,11 @@ public final class SchedulingFacade { return applicationService.planInbound(warehouseId, request); } + public InboundPlanningResult planInboundFromPublishedSnapshot(String warehouseId, InboundRequest request) { + requireAlgorithmMode(); + return applicationService.planInboundFromPublishedSnapshot(warehouseId, request); + } + public InboundReservationOutcome reserveInbound(String reservationId, String warehouseId, String projectNo, InboundPlanningResult plan) { requireActiveMode(); @@ -50,6 +55,11 @@ public final class SchedulingFacade { return applicationService.planOutbound(warehouseId, request); } + public OutboundPlan planOutboundFromPublishedSnapshot(String warehouseId, OutboundRequest request) { + requireAlgorithmMode(); + return applicationService.planOutboundFromPublishedSnapshot(warehouseId, request); + } + public TidyPlan planTidy(String warehouseId, Instant now) { requireAlgorithmMode(); return applicationService.planTidy(warehouseId, now); diff --git a/fortucky-algorithm/src/main/java/com/ruoyi/algorithm/service/WarehouseApplicationService.java b/fortucky-algorithm/src/main/java/com/ruoyi/algorithm/service/WarehouseApplicationService.java index 3c0564f..655f216 100644 --- a/fortucky-algorithm/src/main/java/com/ruoyi/algorithm/service/WarehouseApplicationService.java +++ b/fortucky-algorithm/src/main/java/com/ruoyi/algorithm/service/WarehouseApplicationService.java @@ -76,7 +76,21 @@ public final class WarehouseApplicationService { } public InboundPlanningResult planInbound(String warehouseId, InboundRequest request) { - repository.refreshChangedLanes(warehouseId); + return planInbound(warehouseId, request, true); + } + + public InboundPlanningResult planInboundFromPublishedSnapshot(String warehouseId, InboundRequest request) { + return planInbound(warehouseId, request, false); + } + + private InboundPlanningResult planInbound(String warehouseId, InboundRequest request, boolean refresh) { + if (refresh) { + repository.refreshChangedLanes(warehouseId); + } + return planInboundFromCurrentReadModel(warehouseId, request); + } + + private InboundPlanningResult planInboundFromCurrentReadModel(String warehouseId, InboundRequest request) { RepositoryPolicy policy = repository.loadPolicy(warehouseId); WarehouseSummary summary = repository.loadSummary(warehouseId); InboundRequest effectiveRequest = request; @@ -115,7 +129,21 @@ public final class WarehouseApplicationService { } public OutboundPlan planOutbound(String warehouseId, OutboundRequest request) { - repository.refreshChangedLanes(warehouseId); + return planOutbound(warehouseId, request, true); + } + + public OutboundPlan planOutboundFromPublishedSnapshot(String warehouseId, OutboundRequest request) { + return planOutbound(warehouseId, request, false); + } + + private OutboundPlan planOutbound(String warehouseId, OutboundRequest request, boolean refresh) { + if (refresh) { + repository.refreshChangedLanes(warehouseId); + } + return planOutboundFromCurrentReadModel(warehouseId, request); + } + + private OutboundPlan planOutboundFromCurrentReadModel(String warehouseId, OutboundRequest request) { RepositoryPolicy policy = repository.loadPolicy(warehouseId); WarehouseSummary summary = repository.loadSummary(warehouseId); OutboundRequest effectiveRequest = request.fifoToleranceMinutes() == null && policy.fifoToleranceMinutes() > 0 diff --git a/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/integration/SchedulingFacadePublishedSnapshotTest.java b/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/integration/SchedulingFacadePublishedSnapshotTest.java new file mode 100644 index 0000000..1bb7fc9 --- /dev/null +++ b/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/integration/SchedulingFacadePublishedSnapshotTest.java @@ -0,0 +1,43 @@ +package com.ruoyi.algorithm.integration; + +import com.ruoyi.algorithm.inbound.BatchLaneMode; +import com.ruoyi.algorithm.inbound.InboundRequest; +import com.ruoyi.algorithm.outbound.OutboundRequest; +import com.ruoyi.algorithm.service.WarehouseApplicationService; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class SchedulingFacadePublishedSnapshotTest { + + @Test + void shadowDelegatesPublishedInboundPlanning() { + WarehouseApplicationService applicationService = mock(WarehouseApplicationService.class); + AlgorithmProperties properties = new AlgorithmProperties(); + properties.setMode(SchedulingMode.SHADOW); + SchedulingFacade facade = new SchedulingFacade(applicationService, properties); + InboundRequest request = new InboundRequest("I-STABLE", "T", "P", "SKU", "L", 2, + 1, null, Set.of(), BatchLaneMode.OPEN); + + facade.planInboundFromPublishedSnapshot("W1", request); + + verify(applicationService).planInboundFromPublishedSnapshot("W1", request); + } + + @Test + void legacyRejectsPublishedOutboundPlanning() { + WarehouseApplicationService applicationService = mock(WarehouseApplicationService.class); + AlgorithmProperties properties = new AlgorithmProperties(); + properties.setMode(SchedulingMode.LEGACY); + SchedulingFacade facade = new SchedulingFacade(applicationService, properties); + OutboundRequest outboundRequest = new OutboundRequest("O-STABLE", null, "P", null, + "OFF", null, 1, true); + + assertThatThrownBy(() -> facade.planOutboundFromPublishedSnapshot("W1", outboundRequest)) + .hasMessageContaining("disabled in LEGACY mode"); + } +} diff --git a/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/service/WarehouseApplicationServiceTest.java b/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/service/WarehouseApplicationServiceTest.java index 916c5ff..05356f7 100644 --- a/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/service/WarehouseApplicationServiceTest.java +++ b/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/service/WarehouseApplicationServiceTest.java @@ -95,6 +95,31 @@ class WarehouseApplicationServiceTest { order.verify(repository).loadPolicy("W1"); } + @Test + void publishedInboundReadsSnapshotWithoutRefreshingChangedLanes() { + when(repository.pageCandidateLanes("W1", null, 100)).thenReturn(new LanePage(List.of(), null)); + + service.planInboundFromPublishedSnapshot("W1", inbound("I-STABLE")); + + verify(repository, never()).refreshChangedLanes(any()); + verify(repository).loadPolicy("W1"); + } + + @Test + void publishedOutboundReadsSnapshotWithoutRefreshingChangedLanes() { + OutboundRequest outboundRequest = new OutboundRequest("O-STABLE", null, "P", null, + "OFF", null, 1, true); + when(repository.searchOutboundStock("W1", + com.ruoyi.algorithm.repository.OutboundStockSearch.project("P", null))).thenReturn(List.of()); + when(repository.loadLaneSnapshots("W1", List.of())).thenReturn(List.of()); + when(repository.loadRelocationBuffers("W1", Set.of())).thenReturn(List.of()); + + service.planOutboundFromPublishedSnapshot("W1", outboundRequest); + + verify(repository, never()).refreshChangedLanes(any()); + verify(repository).searchOutboundStock(eq("W1"), any()); + } + @Test void inboundDelegatesToPlannerLogsReadyAndNeverReservesDuringPlanning() { VersionToken version = VersionToken.numeric(7);