feat: plan from published read model

This commit is contained in:
zgw
2026-09-03 11:54:31 +08:00
parent 5ed39162cc
commit 21cf0a5151
4 changed files with 108 additions and 2 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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");
}
}

View File

@@ -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);