feat(ft546): adapt existing WMS contract to WCS core
This commit is contained in:
@@ -5,6 +5,8 @@ import com.ruoyi.api.domain.wcs2wms.JobParam;
|
|||||||
import com.ruoyi.api.domain.wcs2wms.ReceiveJob;
|
import com.ruoyi.api.domain.wcs2wms.ReceiveJob;
|
||||||
import com.ruoyi.api.service.IApiService;
|
import com.ruoyi.api.service.IApiService;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.project.ft546.config.Ft546WcsProperties;
|
||||||
|
import com.ruoyi.project.ft546.wms.Ft546WmsAdapterService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -15,9 +17,18 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/wcs2Wms/api")
|
@RequestMapping("/wcs2Wms/api")
|
||||||
public class Wcs2WmsController {
|
public class Wcs2WmsController {
|
||||||
|
private final IApiService apiService;
|
||||||
|
private final Ft546WmsAdapterService standardAdapter;
|
||||||
|
private final Ft546WcsProperties properties;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IApiService apiService;
|
public Wcs2WmsController(IApiService apiService,
|
||||||
|
Ft546WmsAdapterService standardAdapter,
|
||||||
|
Ft546WcsProperties properties) {
|
||||||
|
this.apiService = apiService;
|
||||||
|
this.standardAdapter = standardAdapter;
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接收出库任务
|
* 接收出库任务
|
||||||
@@ -27,7 +38,10 @@ public class Wcs2WmsController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/receiveOutMydoJob")
|
@PostMapping("/receiveOutMydoJob")
|
||||||
public AjaxResult receiveOutMydoJob(@RequestBody ReceiveJob receiveOutJob) {
|
public AjaxResult receiveOutMydoJob(@RequestBody ReceiveJob receiveOutJob) {
|
||||||
return apiService.receiveOutMydoJob(receiveOutJob);
|
return isStandard()
|
||||||
|
? AjaxResult.success(
|
||||||
|
standardAdapter.receiveOutMydoJob(receiveOutJob))
|
||||||
|
: apiService.receiveOutMydoJob(receiveOutJob);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +52,10 @@ public class Wcs2WmsController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/receiveInMydoJob")
|
@PostMapping("/receiveInMydoJob")
|
||||||
public AjaxResult receiveInMydoJob(@RequestBody ReceiveJob receiveInJob) {
|
public AjaxResult receiveInMydoJob(@RequestBody ReceiveJob receiveInJob) {
|
||||||
return apiService.receiveInMydoJob(receiveInJob);
|
return isStandard()
|
||||||
|
? AjaxResult.success(
|
||||||
|
standardAdapter.receiveInMydoJob(receiveInJob))
|
||||||
|
: apiService.receiveInMydoJob(receiveInJob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -50,13 +67,23 @@ public class Wcs2WmsController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/cancelMydoJob")
|
@PostMapping("/cancelMydoJob")
|
||||||
public AjaxResult cancelMydoJob(@RequestBody JobParam jobParam) {
|
public AjaxResult cancelMydoJob(@RequestBody JobParam jobParam) {
|
||||||
return apiService.cancelMydoJob(jobParam);
|
return isStandard()
|
||||||
|
? AjaxResult.success(standardAdapter.cancelMydoJob(
|
||||||
|
jobParam == null ? null : jobParam.getTaskNo()))
|
||||||
|
: apiService.cancelMydoJob(jobParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return a read-only snapshot of all S-RGV vehicles. */
|
/** Return a read-only snapshot of all S-RGV vehicles. */
|
||||||
@PostMapping("/tidyVehicleSnapshot")
|
@PostMapping("/tidyVehicleSnapshot")
|
||||||
public AjaxResult tidyVehicleSnapshot() {
|
public AjaxResult tidyVehicleSnapshot() {
|
||||||
return apiService.tidyVehicleSnapshot();
|
return isStandard()
|
||||||
|
? AjaxResult.success(standardAdapter.tidyVehicleSnapshot())
|
||||||
|
: apiService.tidyVehicleSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isStandard() {
|
||||||
|
return properties.getRuntimeMode()
|
||||||
|
== Ft546WcsProperties.RuntimeMode.STANDARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.ruoyi.job.domain.MydoJob;
|
|||||||
import com.ruoyi.job.service.IMydoJobService;
|
import com.ruoyi.job.service.IMydoJobService;
|
||||||
import com.ruoyi.queue.agv.domain.AgvQueue;
|
import com.ruoyi.queue.agv.domain.AgvQueue;
|
||||||
import com.ruoyi.queue.agv.service.IAgvQueueService;
|
import com.ruoyi.queue.agv.service.IAgvQueueService;
|
||||||
|
import com.ruoyi.project.ft546.wms.Ft546WmsAdapterService;
|
||||||
import com.ruoyi.system.domain.AgvContainerTask;
|
import com.ruoyi.system.domain.AgvContainerTask;
|
||||||
import com.ruoyi.system.service.IAgvContainerTaskService;
|
import com.ruoyi.system.service.IAgvContainerTaskService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -60,6 +61,8 @@ public class ApiServiceImpl implements IApiService {
|
|||||||
private IAgvContainerTaskService agvContainerTaskService;
|
private IAgvContainerTaskService agvContainerTaskService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private DeviceSrgvMapper deviceSrgvMapper;
|
private DeviceSrgvMapper deviceSrgvMapper;
|
||||||
|
@Autowired
|
||||||
|
private Ft546WmsAdapterService ft546WmsAdapterService;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -520,36 +523,8 @@ public class ApiServiceImpl implements IApiService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AjaxResult tidyVehicleSnapshot() {
|
public AjaxResult tidyVehicleSnapshot() {
|
||||||
List<DeviceSrgv> vehicles = deviceSrgvMapper.selectDeviceSrgvList(new DeviceSrgv());
|
return AjaxResult.success(ft546WmsAdapterService
|
||||||
List<SrgvVehicleSnapshot> snapshots = new ArrayList<>();
|
.tidyVehicleSnapshot());
|
||||||
for (DeviceSrgv vehicle : vehicles) {
|
|
||||||
boolean online = "1".equals(vehicle.getAgvStatus());
|
|
||||||
boolean healthy = online && value(vehicle.getAlarm()) <= 1 && value(vehicle.getErrMsg()) == 0;
|
|
||||||
boolean idle = healthy && value(vehicle.getTaskNo()) == 0
|
|
||||||
&& value(vehicle.getLocked()) == 0 && value(vehicle.getChargeFlag()) == 0;
|
|
||||||
SrgvVehicleSnapshot snapshot = new SrgvVehicleSnapshot();
|
|
||||||
snapshot.setGuid(vehicle.getGuid());
|
|
||||||
snapshot.setDeviceId(vehicle.getDeviceId());
|
|
||||||
snapshot.setDeviceName(vehicle.getDeviceName());
|
|
||||||
snapshot.setAgvStatus(vehicle.getAgvStatus());
|
|
||||||
snapshot.setOnline(online);
|
|
||||||
snapshot.setHealthy(healthy);
|
|
||||||
snapshot.setIdle(idle);
|
|
||||||
snapshot.setTaskNo(vehicle.getTaskNo());
|
|
||||||
snapshot.setAlarm(vehicle.getAlarm());
|
|
||||||
snapshot.setErrMsg(vehicle.getErrMsg());
|
|
||||||
snapshot.setChargeFlag(vehicle.getChargeFlag());
|
|
||||||
snapshot.setLocked(vehicle.getLocked());
|
|
||||||
snapshot.setBattery(vehicle.getBattery());
|
|
||||||
snapshot.setCurrSite(vehicle.getCurrSite());
|
|
||||||
snapshot.setCurrFloor(vehicle.getCurrFloor());
|
|
||||||
snapshots.add(snapshot);
|
|
||||||
}
|
|
||||||
return AjaxResult.success(snapshots);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int value(Integer number) {
|
|
||||||
return number == null ? 0 : number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package com.ruoyi.project.ft546.wms;
|
||||||
|
|
||||||
|
import com.ruoyi.api.domain.wcs2wms.ReceiveJob;
|
||||||
|
import com.ruoyi.api.domain.wcs2wms.SrgvVehicleSnapshot;
|
||||||
|
import com.ruoyi.device.srgv.domain.DeviceSrgv;
|
||||||
|
import com.ruoyi.device.srgv.mapper.DeviceSrgvMapper;
|
||||||
|
import com.ruoyi.wcs.application.task.TransportOrderResult;
|
||||||
|
import com.ruoyi.wcs.port.in.TransportOrderUseCase;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class Ft546WmsAdapterService {
|
||||||
|
private final TransportOrderUseCase orderUseCase;
|
||||||
|
private final VehicleSnapshotProvider vehicleSnapshotProvider;
|
||||||
|
private final Ft546WmsOrderMapper mapper = new Ft546WmsOrderMapper();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public Ft546WmsAdapterService(TransportOrderUseCase orderUseCase,
|
||||||
|
DeviceSrgvMapper deviceMapper) {
|
||||||
|
this(orderUseCase, () -> snapshots(deviceMapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Ft546WmsAdapterService(TransportOrderUseCase orderUseCase,
|
||||||
|
VehicleSnapshotProvider snapshotProvider) {
|
||||||
|
this.orderUseCase = Objects.requireNonNull(orderUseCase,
|
||||||
|
"orderUseCase");
|
||||||
|
this.vehicleSnapshotProvider = Objects.requireNonNull(
|
||||||
|
snapshotProvider, "snapshotProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransportOrderResult receiveOutMydoJob(ReceiveJob job) {
|
||||||
|
return orderUseCase.create(mapper.outbound(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransportOrderResult receiveInMydoJob(ReceiveJob job) {
|
||||||
|
return orderUseCase.create(mapper.inbound(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransportOrderResult cancelMydoJob(String taskNo) {
|
||||||
|
return orderUseCase.cancel(requireText(taskNo, "taskNo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<?> tidyVehicleSnapshot() {
|
||||||
|
List<?> snapshots = Objects.requireNonNull(
|
||||||
|
vehicleSnapshotProvider.load(), "vehicle snapshots");
|
||||||
|
return Collections.unmodifiableList(new ArrayList<Object>(snapshots));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<SrgvVehicleSnapshot> snapshots(
|
||||||
|
DeviceSrgvMapper mapper) {
|
||||||
|
List<DeviceSrgv> vehicles = Objects.requireNonNull(mapper,
|
||||||
|
"deviceMapper").selectDeviceSrgvList(new DeviceSrgv());
|
||||||
|
List<SrgvVehicleSnapshot> snapshots =
|
||||||
|
new ArrayList<SrgvVehicleSnapshot>();
|
||||||
|
for (DeviceSrgv vehicle : vehicles) {
|
||||||
|
boolean online = "1".equals(vehicle.getAgvStatus());
|
||||||
|
boolean healthy = online && value(vehicle.getAlarm()) <= 1
|
||||||
|
&& value(vehicle.getErrMsg()) == 0;
|
||||||
|
boolean idle = healthy && value(vehicle.getTaskNo()) == 0
|
||||||
|
&& value(vehicle.getLocked()) == 0
|
||||||
|
&& value(vehicle.getChargeFlag()) == 0;
|
||||||
|
SrgvVehicleSnapshot snapshot = new SrgvVehicleSnapshot();
|
||||||
|
snapshot.setGuid(vehicle.getGuid());
|
||||||
|
snapshot.setDeviceId(vehicle.getDeviceId());
|
||||||
|
snapshot.setDeviceName(vehicle.getDeviceName());
|
||||||
|
snapshot.setAgvStatus(vehicle.getAgvStatus());
|
||||||
|
snapshot.setOnline(online);
|
||||||
|
snapshot.setHealthy(healthy);
|
||||||
|
snapshot.setIdle(idle);
|
||||||
|
snapshot.setTaskNo(vehicle.getTaskNo());
|
||||||
|
snapshot.setAlarm(vehicle.getAlarm());
|
||||||
|
snapshot.setErrMsg(vehicle.getErrMsg());
|
||||||
|
snapshot.setChargeFlag(vehicle.getChargeFlag());
|
||||||
|
snapshot.setLocked(vehicle.getLocked());
|
||||||
|
snapshot.setBattery(vehicle.getBattery());
|
||||||
|
snapshot.setCurrSite(vehicle.getCurrSite());
|
||||||
|
snapshot.setCurrFloor(vehicle.getCurrFloor());
|
||||||
|
snapshots.add(snapshot);
|
||||||
|
}
|
||||||
|
return snapshots;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int value(Integer number) {
|
||||||
|
return number == null ? 0 : number;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireText(String value, String field) {
|
||||||
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(field + " must not be blank");
|
||||||
|
}
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface VehicleSnapshotProvider {
|
||||||
|
List<?> load();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.ruoyi.project.ft546.wms;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.ruoyi.api.domain.RequestLog;
|
||||||
|
import com.ruoyi.api.service.IRequestLogService;
|
||||||
|
import com.ruoyi.wcs.port.out.WmsNotification;
|
||||||
|
import com.ruoyi.wcs.port.out.WmsNotificationGateway;
|
||||||
|
import com.ruoyi.wcs.port.out.WmsNotificationReceipt;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static com.ruoyi.common.Constant.JOB_FINISH_URL;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public final class Ft546WmsNotificationGateway
|
||||||
|
implements WmsNotificationGateway {
|
||||||
|
private static final java.util.Set<String> SUPPORTED_EVENTS =
|
||||||
|
new java.util.HashSet<String>(Arrays.asList("OrderCompleted",
|
||||||
|
"OrderFailed", "OrderCancelled"));
|
||||||
|
private final IRequestLogService requestLogService;
|
||||||
|
|
||||||
|
public Ft546WmsNotificationGateway(
|
||||||
|
IRequestLogService requestLogService) {
|
||||||
|
this.requestLogService = Objects.requireNonNull(requestLogService,
|
||||||
|
"requestLogService");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WmsNotificationReceipt notify(WmsNotification notification) {
|
||||||
|
Objects.requireNonNull(notification, "notification");
|
||||||
|
if (!SUPPORTED_EVENTS.contains(notification.getEventType())) {
|
||||||
|
return new WmsNotificationReceipt(
|
||||||
|
WmsNotificationReceipt.Status.PERMANENT_FAILURE,
|
||||||
|
"unsupported FT546 notification event");
|
||||||
|
}
|
||||||
|
Map<String, Object> payload = new LinkedHashMap<String, Object>();
|
||||||
|
payload.put("eventId", notification.getEventId());
|
||||||
|
payload.put("jobId", context(notification, "jobId",
|
||||||
|
notification.getOrderId()));
|
||||||
|
payload.put("taskNo", notification.getExternalRequestId());
|
||||||
|
payload.put("wmsType", context(notification, "inOrOut", "UNKNOWN"));
|
||||||
|
payload.put("eventType", notification.getEventType());
|
||||||
|
payload.put("resultCode", notification.getResultCode());
|
||||||
|
payload.put("resultMessage", notification.getResultMessage());
|
||||||
|
RequestLog result = requestLogService.getRequestLog(JOB_FINISH_URL,
|
||||||
|
"WCS标准任务结果回传", JSONUtil.toJsonStr(payload));
|
||||||
|
if (result != null && "1".equals(result.getIsSuccessful())) {
|
||||||
|
return new WmsNotificationReceipt(
|
||||||
|
WmsNotificationReceipt.Status.DELIVERED,
|
||||||
|
notification.getEventId());
|
||||||
|
}
|
||||||
|
return new WmsNotificationReceipt(
|
||||||
|
WmsNotificationReceipt.Status.RETRYABLE_FAILURE,
|
||||||
|
result == null ? "callback returned no result"
|
||||||
|
: result.getErrorDetails());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String context(WmsNotification notification, String key,
|
||||||
|
String fallback) {
|
||||||
|
String value = notification.getContext().get(key);
|
||||||
|
return value == null || value.trim().isEmpty()
|
||||||
|
? fallback : value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.ruoyi.project.ft546.wms;
|
||||||
|
|
||||||
|
import com.ruoyi.api.domain.wcs2wms.ReceiveJob;
|
||||||
|
import com.ruoyi.wcs.application.task.CreateTransportOrderCommand;
|
||||||
|
import com.ruoyi.wcs.domain.task.TransportOrderType;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class Ft546WmsOrderMapper {
|
||||||
|
public CreateTransportOrderCommand inbound(ReceiveJob job) {
|
||||||
|
ReceiveJob request = Objects.requireNonNull(job, "job");
|
||||||
|
return command(request, TransportOrderType.INBOUND,
|
||||||
|
firstText(request.getEntranceId(), request.getExitId()),
|
||||||
|
requireText(request.getCargoSpaceId(), "cargoSpaceId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreateTransportOrderCommand outbound(ReceiveJob job) {
|
||||||
|
ReceiveJob request = Objects.requireNonNull(job, "job");
|
||||||
|
return command(request, TransportOrderType.OUTBOUND,
|
||||||
|
requireText(request.getCargoSpaceId(), "cargoSpaceId"),
|
||||||
|
firstText(request.getExitId(),
|
||||||
|
request.getTargetCargoSpaceId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CreateTransportOrderCommand command(
|
||||||
|
ReceiveJob job, TransportOrderType type, String source,
|
||||||
|
String destination) {
|
||||||
|
String taskNo = requireText(job.getTaskNo(), "taskNo");
|
||||||
|
Map<String, String> attributes = new LinkedHashMap<String, String>();
|
||||||
|
put(attributes, "legacyTaskNo", taskNo);
|
||||||
|
put(attributes, "jobId", job.getJobId());
|
||||||
|
put(attributes, "inOrOut", job.getInOrOut());
|
||||||
|
put(attributes, "roadwayId", job.getRoadwayId());
|
||||||
|
put(attributes, "classId", job.getClassId());
|
||||||
|
put(attributes, "entranceId", job.getEntranceId());
|
||||||
|
put(attributes, "exitId", job.getExitId());
|
||||||
|
put(attributes, "cargoSpaceId", job.getCargoSpaceId());
|
||||||
|
put(attributes, "targetCargoSpaceId",
|
||||||
|
job.getTargetCargoSpaceId());
|
||||||
|
return new CreateTransportOrderCommand(taskNo, type,
|
||||||
|
textOrNull(job.getTrayCode()), source, destination,
|
||||||
|
priority(job.getPriority()), null, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int priority(Long value) {
|
||||||
|
if (value == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("priority is out of range");
|
||||||
|
}
|
||||||
|
return value.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void put(Map<String, String> target, String key,
|
||||||
|
String value) {
|
||||||
|
String text = textOrNull(value);
|
||||||
|
if (text != null) {
|
||||||
|
target.put(key, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String firstText(String first, String second) {
|
||||||
|
String value = textOrNull(first);
|
||||||
|
return value != null ? value : requireText(second, "location");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String textOrNull(String value) {
|
||||||
|
return value == null || value.trim().isEmpty()
|
||||||
|
? null : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireText(String value, String field) {
|
||||||
|
String text = textOrNull(value);
|
||||||
|
if (text == null) {
|
||||||
|
throw new IllegalArgumentException(field + " must not be blank");
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user