feat(ft546): adapt existing WMS contract to WCS core

This commit is contained in:
zgw
2026-08-11 14:46:36 +08:00
parent e5f91de1a0
commit 3e704d0b10
6 changed files with 556 additions and 35 deletions

View File

@@ -0,0 +1,265 @@
package com.ruoyi.project.ft546.wms;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ruoyi.api.controller.Wcs2WmsController;
import com.ruoyi.api.domain.RequestLog;
import com.ruoyi.api.domain.wcs2wms.JobParam;
import com.ruoyi.api.domain.wcs2wms.ReceiveJob;
import com.ruoyi.api.service.IApiService;
import com.ruoyi.api.service.impl.RequestLogServiceImpl;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.project.ft546.config.Ft546WcsProperties;
import com.ruoyi.wcs.application.task.CreateTransportOrderCommand;
import com.ruoyi.wcs.application.task.TransportOrderResult;
import com.ruoyi.wcs.domain.task.TransportOrder;
import com.ruoyi.wcs.domain.task.TransportOrderType;
import com.ruoyi.wcs.port.in.TransportOrderUseCase;
import com.ruoyi.wcs.port.out.WmsNotification;
import com.ruoyi.wcs.port.out.WmsNotificationReceipt;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class Ft546WmsAdapterServiceTest {
private static final Instant NOW = Instant.parse("2026-08-11T06:00:00Z");
@Test
void mapsExistingInboundRequestToCanonicalOrder() {
Ft546WmsOrderMapper mapper = new Ft546WmsOrderMapper();
ReceiveJob job = job("TASK-1");
job.setEntranceId("PORT-1");
job.setCargoSpaceId("SPACE-9");
CreateTransportOrderCommand command = mapper.inbound(job);
assertEquals("TASK-1", command.getExternalRequestId());
assertEquals(TransportOrderType.INBOUND, command.getOrderType());
assertEquals("PORT-1", command.getSourceCode());
assertEquals("SPACE-9", command.getDestinationCode());
assertEquals("TRAY-1", command.getLoadId());
assertEquals("JOB-1", command.getAttributes().get("jobId"));
assertEquals("TASK-1",
command.getAttributes().get("legacyTaskNo"));
}
@Test
void mapsExistingOutboundRequestToCanonicalOrder() {
Ft546WmsOrderMapper mapper = new Ft546WmsOrderMapper();
ReceiveJob job = job("TASK-2");
job.setCargoSpaceId("SPACE-9");
job.setExitId("PORT-2");
CreateTransportOrderCommand command = mapper.outbound(job);
assertEquals(TransportOrderType.OUTBOUND, command.getOrderType());
assertEquals("SPACE-9", command.getSourceCode());
assertEquals("PORT-2", command.getDestinationCode());
}
@Test
void duplicateWmsRequestReturnsTheSameOrder() {
FakeTransportOrderUseCase useCase = new FakeTransportOrderUseCase();
Ft546WmsAdapterService service = new Ft546WmsAdapterService(useCase,
() -> Collections.singletonList("SNAPSHOT"));
ReceiveJob job = job("TASK-1");
job.setEntranceId("PORT-1");
job.setCargoSpaceId("SPACE-9");
TransportOrderResult first = service.receiveInMydoJob(job);
TransportOrderResult duplicate = service.receiveInMydoJob(job);
assertEquals(first.getOrderId(), duplicate.getOrderId());
assertEquals(2, useCase.createCommands.size());
assertEquals("TASK-1", useCase.createCommands.get(1)
.getExternalRequestId());
}
@Test
void controllerUsesStandardAdapterOnlyInStandardMode() {
IApiService legacy = mock(IApiService.class);
AjaxResult legacyResult = AjaxResult.success("legacy");
ReceiveJob job = job("TASK-1");
job.setEntranceId("PORT-1");
job.setCargoSpaceId("SPACE-9");
when(legacy.receiveInMydoJob(job)).thenReturn(legacyResult);
FakeTransportOrderUseCase useCase = new FakeTransportOrderUseCase();
Ft546WmsAdapterService standard = new Ft546WmsAdapterService(useCase,
() -> Collections.singletonList("SNAPSHOT"));
Ft546WcsProperties properties = new Ft546WcsProperties();
Wcs2WmsController controller = new Wcs2WmsController(legacy,
standard, properties);
assertSame(legacyResult, controller.receiveInMydoJob(job));
verify(legacy).receiveInMydoJob(job);
properties.setRuntimeMode(Ft546WcsProperties.RuntimeMode.STANDARD);
AjaxResult standardResult = controller.receiveInMydoJob(job);
assertTrue(standardResult.isSuccess());
assertTrue(standardResult.get(AjaxResult.DATA_TAG)
instanceof TransportOrderResult);
assertEquals(1, useCase.createCommands.size());
}
@Test
void controllerDelegatesCancelAndSnapshotWithoutLegacyWritesInStandard() {
IApiService legacy = mock(IApiService.class);
FakeTransportOrderUseCase useCase = new FakeTransportOrderUseCase();
Ft546WmsAdapterService standard = new Ft546WmsAdapterService(useCase,
() -> Arrays.asList("S1", "S2"));
Ft546WcsProperties properties = new Ft546WcsProperties();
properties.setRuntimeMode(Ft546WcsProperties.RuntimeMode.STANDARD);
Wcs2WmsController controller = new Wcs2WmsController(legacy,
standard, properties);
JobParam cancel = new JobParam("JOB-1", "TRAY-1", "TASK-1");
AjaxResult cancelResult = controller.cancelMydoJob(cancel);
AjaxResult snapshotResult = controller.tidyVehicleSnapshot();
assertTrue(cancelResult.isSuccess());
assertEquals(Arrays.asList("S1", "S2"),
snapshotResult.get(AjaxResult.DATA_TAG));
assertEquals(Collections.singletonList("TASK-1"),
useCase.cancelledExternalIds);
verify(legacy, never()).cancelMydoJob(any(JobParam.class));
verify(legacy, never()).tidyVehicleSnapshot();
}
@Test
void notificationUsesEventIdAsLegacyCallbackIdempotencyKey() {
FakeRequestLogService requestLogs = new FakeRequestLogService();
Ft546WmsNotificationGateway gateway =
new Ft546WmsNotificationGateway(requestLogs);
Map<String, String> context = new LinkedHashMap<String, String>();
context.put("jobId", "JOB-1");
context.put("inOrOut", "10");
WmsNotification notification = new WmsNotification("EVENT-1",
"TASK-1", "ORDER-1", NOW, "OrderCompleted", "OK",
"completed", context);
WmsNotificationReceipt receipt = gateway.notify(notification);
assertEquals(WmsNotificationReceipt.Status.DELIVERED,
receipt.getStatus());
JSONObject payload = JSONUtil.parseObj(requestLogs.requestBody);
assertEquals("EVENT-1", payload.getStr("eventId"));
assertEquals("JOB-1", payload.getStr("jobId"));
assertEquals("TASK-1", payload.getStr("taskNo"));
assertEquals("10", payload.getStr("wmsType"));
assertEquals("OrderCompleted", payload.getStr("eventType"));
}
@Test
void notificationTransportFailureRemainsRetryable() {
FakeRequestLogService requestLogs = new FakeRequestLogService();
requestLogs.success = false;
Ft546WmsNotificationGateway gateway =
new Ft546WmsNotificationGateway(requestLogs);
WmsNotification notification = new WmsNotification("EVENT-2",
"TASK-2", "ORDER-2", NOW, "OrderFailed", "FAILED",
"fault", Collections.<String, String>emptyMap());
WmsNotificationReceipt receipt = gateway.notify(notification);
assertEquals(WmsNotificationReceipt.Status.RETRYABLE_FAILURE,
receipt.getStatus());
}
private static ReceiveJob job(String taskNo) {
ReceiveJob job = new ReceiveJob();
job.setTaskNo(taskNo);
job.setJobId("JOB-1");
job.setTrayCode("TRAY-1");
job.setInOrOut("10");
job.setRoadwayId("R1");
job.setClassId("7");
job.setPriority(5L);
return job;
}
private static final class FakeTransportOrderUseCase
implements TransportOrderUseCase {
private final Map<String, TransportOrderResult> orders =
new LinkedHashMap<String, TransportOrderResult>();
private final List<CreateTransportOrderCommand> createCommands =
new ArrayList<CreateTransportOrderCommand>();
private final List<String> cancelledExternalIds =
new ArrayList<String>();
@Override
public TransportOrderResult create(CreateTransportOrderCommand command) {
createCommands.add(command);
TransportOrderResult existing = orders.get(
command.getExternalRequestId());
if (existing != null) {
return existing;
}
TransportOrder order = TransportOrder.accept(
"ORDER-" + command.getExternalRequestId(),
command.getExternalRequestId(), command.getOrderType(),
command.getLoadId(), command.getSourceCode(),
command.getDestinationCode(), command.getPriority(), NOW);
order.plan("WORKFLOW-" + command.getExternalRequestId());
TransportOrderResult result = TransportOrderResult.from(order);
orders.put(command.getExternalRequestId(), result);
return result;
}
@Override
public TransportOrderResult cancel(String externalRequestId) {
cancelledExternalIds.add(externalRequestId);
TransportOrderResult existing = orders.get(externalRequestId);
if (existing != null) {
return existing;
}
CreateTransportOrderCommand command =
new CreateTransportOrderCommand(externalRequestId,
TransportOrderType.INBOUND, null, "UNKNOWN-SOURCE",
"UNKNOWN-TARGET", 0, null,
Collections.<String, String>emptyMap());
return create(command);
}
@Override
public TransportOrderResult suspend(String orderId, String reason) {
throw new UnsupportedOperationException();
}
@Override
public TransportOrderResult resume(String orderId) {
throw new UnsupportedOperationException();
}
}
private static final class FakeRequestLogService
extends RequestLogServiceImpl {
private boolean success = true;
private String requestBody;
@Override
public RequestLog getRequestLog(String url, String endpoint,
String body) {
this.requestBody = body;
RequestLog log = new RequestLog();
log.setIsSuccessful(success ? "1" : "0");
log.setErrorDetails(success ? null : "injected failure");
return log;
}
}
}