feat(ft546): add WCS operations console
This commit is contained in:
@@ -6,8 +6,13 @@ import com.ruoyi.wcs.application.device.DeviceExecutionService;
|
||||
import com.ruoyi.wcs.application.device.SnapshotCollector;
|
||||
import com.ruoyi.wcs.application.dispatch.DispatchCoordinator;
|
||||
import com.ruoyi.wcs.application.recovery.RecoveryGate;
|
||||
import com.ruoyi.wcs.application.recovery.RecoveryApplicationService;
|
||||
import com.ruoyi.wcs.domain.shared.IdGenerator;
|
||||
import com.ruoyi.wcs.domain.shared.WcsClock;
|
||||
import com.ruoyi.wcs.port.out.DispatchSnapshotProvider;
|
||||
import com.ruoyi.wcs.port.out.RecoveryCaseRepository;
|
||||
import com.ruoyi.wcs.query.WcsOperationsQueryService;
|
||||
import com.ruoyi.wcs.query.WcsOperationsReadRepository;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -37,4 +42,18 @@ public class Ft546AdapterConfiguration {
|
||||
WcsRuntimeCoordinator coordinator) {
|
||||
return new Ft546WcsScheduler(coordinator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WcsOperationsQueryService wcsOperationsQueryService(
|
||||
WcsOperationsReadRepository repository) {
|
||||
return new WcsOperationsQueryService(repository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RecoveryApplicationService recoveryApplicationService(
|
||||
RecoveryCaseRepository repository, IdGenerator idGenerator,
|
||||
WcsClock clock) {
|
||||
return new RecoveryApplicationService(repository, idGenerator,
|
||||
clock::now);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.ruoyi.project.ft546.web;
|
||||
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.wcs.query.OperationsPage;
|
||||
import com.ruoyi.wcs.query.WcsOperationsQueryService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/wcs/devices")
|
||||
@ConditionalOnProperty(prefix = "ft546.wcs", name = "runtime-mode",
|
||||
havingValue = "STANDARD")
|
||||
public class WcsDeviceController extends BaseController {
|
||||
private final WcsOperationsQueryService queries;
|
||||
|
||||
public WcsDeviceController(WcsOperationsQueryService queries) {
|
||||
this.queries = Objects.requireNonNull(queries, "queries");
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('wcs:device:list')")
|
||||
@GetMapping
|
||||
public TableDataInfo list(@RequestParam(required = false) String state,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
OperationsPage<?> page = queries.devices(state,
|
||||
offset(pageNum, pageSize), pageSize);
|
||||
TableDataInfo result = new TableDataInfo(page.getRows(), page.getTotal());
|
||||
result.setCode(HttpStatus.SUCCESS);
|
||||
result.setMsg("查询成功");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int offset(int pageNum, int pageSize) {
|
||||
if (pageNum <= 0 || pageSize <= 0) {
|
||||
throw new IllegalArgumentException("invalid page request");
|
||||
}
|
||||
return Math.multiplyExact(pageNum - 1, pageSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.ruoyi.project.ft546.web;
|
||||
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.wcs.application.task.TransportOrderResult;
|
||||
import com.ruoyi.wcs.port.in.TransportOrderUseCase;
|
||||
import com.ruoyi.wcs.query.OperationsPage;
|
||||
import com.ruoyi.wcs.query.OrderExecutionView;
|
||||
import com.ruoyi.wcs.query.WcsOperationsQueryService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/wcs/orders")
|
||||
@ConditionalOnProperty(prefix = "ft546.wcs", name = "runtime-mode",
|
||||
havingValue = "STANDARD")
|
||||
public class WcsOrderController extends BaseController {
|
||||
private final WcsOperationsQueryService queries;
|
||||
private final TransportOrderUseCase orders;
|
||||
|
||||
public WcsOrderController(WcsOperationsQueryService queries,
|
||||
TransportOrderUseCase orders) {
|
||||
this.queries = Objects.requireNonNull(queries, "queries");
|
||||
this.orders = Objects.requireNonNull(orders, "orders");
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('wcs:order:list')")
|
||||
@GetMapping
|
||||
public TableDataInfo list(
|
||||
@RequestParam(required = false) String state,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
return table(queries.orders(state, offset(pageNum, pageSize), pageSize));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('wcs:order:control')")
|
||||
@PostMapping("/{id}/suspend")
|
||||
public AjaxResult suspend(@PathVariable("id") String orderId,
|
||||
@RequestBody OrderControlRequest request) {
|
||||
TransportOrderResult result = orders.suspend(orderId,
|
||||
requireText(request == null ? null : request.getReason(),
|
||||
"reason"));
|
||||
return success(result);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('wcs:order:control')")
|
||||
@PostMapping("/{id}/resume")
|
||||
public AjaxResult resume(@PathVariable("id") String orderId) {
|
||||
return success(orders.resume(orderId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('wcs:order:control')")
|
||||
@PostMapping("/{id}/cancel")
|
||||
public AjaxResult cancel(@PathVariable("id") String orderId) {
|
||||
OrderExecutionView view = queries.order(orderId);
|
||||
return success(orders.cancel(requireText(view.getExternalRequestId(),
|
||||
"externalRequestId")));
|
||||
}
|
||||
|
||||
private static TableDataInfo table(OperationsPage<?> page) {
|
||||
TableDataInfo result = new TableDataInfo(page.getRows(), page.getTotal());
|
||||
result.setCode(HttpStatus.SUCCESS);
|
||||
result.setMsg("查询成功");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int offset(int pageNum, int pageSize) {
|
||||
if (pageNum <= 0 || pageSize <= 0) {
|
||||
throw new IllegalArgumentException("invalid page request");
|
||||
}
|
||||
return Math.multiplyExact(pageNum - 1, pageSize);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public static final class OrderControlRequest {
|
||||
private String reason;
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.ruoyi.project.ft546.web;
|
||||
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.wcs.domain.recovery.RecoveryEvidence;
|
||||
import com.ruoyi.wcs.domain.shared.WcsClock;
|
||||
import com.ruoyi.wcs.port.in.RecoveryUseCase;
|
||||
import com.ruoyi.wcs.query.OperationsPage;
|
||||
import com.ruoyi.wcs.query.WcsOperationsQueryService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/wcs/recovery/cases")
|
||||
@ConditionalOnProperty(prefix = "ft546.wcs", name = "runtime-mode",
|
||||
havingValue = "STANDARD")
|
||||
public class WcsRecoveryController extends BaseController {
|
||||
private final WcsOperationsQueryService queries;
|
||||
private final RecoveryUseCase recovery;
|
||||
private final WcsClock clock;
|
||||
|
||||
public WcsRecoveryController(WcsOperationsQueryService queries,
|
||||
RecoveryUseCase recovery, WcsClock clock) {
|
||||
this.queries = Objects.requireNonNull(queries, "queries");
|
||||
this.recovery = Objects.requireNonNull(recovery, "recovery");
|
||||
this.clock = Objects.requireNonNull(clock, "clock");
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('wcs:recovery:list')")
|
||||
@GetMapping
|
||||
public TableDataInfo list(
|
||||
@RequestParam(defaultValue = "OPEN") String state,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
OperationsPage<?> page = queries.recoveryCasePage(state,
|
||||
offset(pageNum, pageSize), pageSize);
|
||||
TableDataInfo result = new TableDataInfo(page.getRows(), page.getTotal());
|
||||
result.setCode(HttpStatus.SUCCESS);
|
||||
result.setMsg("查询成功");
|
||||
return result;
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('wcs:recovery:confirm')")
|
||||
@PostMapping("/{id}/confirm")
|
||||
public AjaxResult confirm(@PathVariable("id") String caseId,
|
||||
@RequestBody RecoveryConfirmRequest request) {
|
||||
if (request == null || request.getLoaded() == null) {
|
||||
throw new IllegalArgumentException("loaded must be specified");
|
||||
}
|
||||
RecoveryEvidence evidence = new RecoveryEvidence(
|
||||
request.getCurrentSite(),
|
||||
request.getLoaded() ? "LOADED" : "EMPTY",
|
||||
request.getTaskNumber(), request.getDeviceMode(), clock.now(),
|
||||
"OPERATOR_UI", request.getReason());
|
||||
recovery.confirmFacts(caseId, getUsername(), evidence);
|
||||
return success();
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('wcs:recovery:confirm')")
|
||||
@PostMapping("/{id}/abort")
|
||||
public AjaxResult abort(@PathVariable("id") String caseId,
|
||||
@RequestBody RecoveryAbortRequest request) {
|
||||
recovery.abort(caseId, getUsername(),
|
||||
requireText(request == null ? null : request.getReason(),
|
||||
"reason"));
|
||||
return success();
|
||||
}
|
||||
|
||||
private static int offset(int pageNum, int pageSize) {
|
||||
if (pageNum <= 0 || pageSize <= 0) {
|
||||
throw new IllegalArgumentException("invalid page request");
|
||||
}
|
||||
return Math.multiplyExact(pageNum - 1, pageSize);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public static final class RecoveryConfirmRequest {
|
||||
private String reason;
|
||||
private String currentSite;
|
||||
private Boolean loaded;
|
||||
private String taskNumber;
|
||||
private String deviceMode;
|
||||
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
public String getCurrentSite() { return currentSite; }
|
||||
public void setCurrentSite(String currentSite) {
|
||||
this.currentSite = currentSite;
|
||||
}
|
||||
public Boolean getLoaded() { return loaded; }
|
||||
public void setLoaded(Boolean loaded) { this.loaded = loaded; }
|
||||
public String getTaskNumber() { return taskNumber; }
|
||||
public void setTaskNumber(String taskNumber) {
|
||||
this.taskNumber = taskNumber;
|
||||
}
|
||||
public String getDeviceMode() { return deviceMode; }
|
||||
public void setDeviceMode(String deviceMode) {
|
||||
this.deviceMode = deviceMode;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RecoveryAbortRequest {
|
||||
private String reason;
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.ruoyi.project.ft546.web;
|
||||
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.wcs.query.OperationsPage;
|
||||
import com.ruoyi.wcs.query.WcsOperationsQueryService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/wcs/traffic/resources")
|
||||
@ConditionalOnProperty(prefix = "ft546.wcs", name = "runtime-mode",
|
||||
havingValue = "STANDARD")
|
||||
public class WcsTrafficController extends BaseController {
|
||||
private final WcsOperationsQueryService queries;
|
||||
|
||||
public WcsTrafficController(WcsOperationsQueryService queries) {
|
||||
this.queries = Objects.requireNonNull(queries, "queries");
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('wcs:traffic:list')")
|
||||
@GetMapping
|
||||
public TableDataInfo list(@RequestParam(required = false) String state,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
OperationsPage<?> page = queries.resources(state,
|
||||
offset(pageNum, pageSize), pageSize);
|
||||
TableDataInfo result = new TableDataInfo(page.getRows(), page.getTotal());
|
||||
result.setCode(HttpStatus.SUCCESS);
|
||||
result.setMsg("查询成功");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int offset(int pageNum, int pageSize) {
|
||||
if (pageNum <= 0 || pageSize <= 0) {
|
||||
throw new IllegalArgumentException("invalid page request");
|
||||
}
|
||||
return Math.multiplyExact(pageNum - 1, pageSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
package com.ruoyi.project.ft546.web;
|
||||
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.wcs.application.task.CreateTransportOrderCommand;
|
||||
import com.ruoyi.wcs.application.task.TransportOrderResult;
|
||||
import com.ruoyi.wcs.domain.recovery.RecoveryEvidence;
|
||||
import com.ruoyi.wcs.port.in.RecoveryUseCase;
|
||||
import com.ruoyi.wcs.port.in.TransportOrderUseCase;
|
||||
import com.ruoyi.wcs.query.DeviceOperationsView;
|
||||
import com.ruoyi.wcs.query.OrderExecutionView;
|
||||
import com.ruoyi.wcs.query.RecoveryCaseView;
|
||||
import com.ruoyi.wcs.query.ResourceOperationsView;
|
||||
import com.ruoyi.wcs.query.WcsOperationsQueryService;
|
||||
import com.ruoyi.wcs.query.WcsOperationsReadRepository;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
class WcsOperationsControllerTest {
|
||||
private RecordingOrderUseCase orders;
|
||||
private RecordingRecoveryUseCase recovery;
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
orders = new RecordingOrderUseCase();
|
||||
recovery = new RecordingRecoveryUseCase();
|
||||
WcsOperationsQueryService queries = new WcsOperationsQueryService(
|
||||
new OperationsReadRepository());
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(
|
||||
new WcsOrderController(queries, orders),
|
||||
new WcsDeviceController(queries),
|
||||
new WcsTrafficController(queries),
|
||||
new WcsRecoveryController(queries, recovery,
|
||||
() -> Instant.parse("2026-08-11T08:00:00Z")))
|
||||
.build();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearSecurityContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsPagedOrdersWithCanonicalAndExternalIdentifiers()
|
||||
throws Exception {
|
||||
mockMvc.perform(get("/wcs/orders")
|
||||
.param("state", "EXECUTING")
|
||||
.param("pageNum", "1")
|
||||
.param("pageSize", "20"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.total").value(1))
|
||||
.andExpect(jsonPath("$.rows[0].orderId").value("ORDER-1"))
|
||||
.andExpect(jsonPath("$.rows[0].externalRequestId")
|
||||
.value("TASK-1"))
|
||||
.andExpect(jsonPath("$.rows[0].orderState")
|
||||
.value("EXECUTING"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsResourceCapacityProtectionAndHolderEvidence()
|
||||
throws Exception {
|
||||
mockMvc.perform(get("/wcs/traffic/resources")
|
||||
.param("state", "OCCUPIED")
|
||||
.param("pageNum", "1")
|
||||
.param("pageSize", "20"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.rows[0].resourceType").value("NODE"))
|
||||
.andExpect(jsonPath("$.rows[0].capacity").value(2))
|
||||
.andExpect(jsonPath("$.rows[0].protectedCount").value(1))
|
||||
.andExpect(jsonPath("$.rows[0].holder").value("VEH-1"))
|
||||
.andExpect(jsonPath("$.rows[0].reservationState")
|
||||
.value("OCCUPIED"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderControlsUseTheCorrectCoreIdentifiers() throws Exception {
|
||||
mockMvc.perform(post("/wcs/orders/ORDER-1/suspend")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"reason\":\"现场暂停\"}"))
|
||||
.andExpect(status().isOk());
|
||||
mockMvc.perform(post("/wcs/orders/ORDER-1/resume"))
|
||||
.andExpect(status().isOk());
|
||||
mockMvc.perform(post("/wcs/orders/ORDER-1/cancel"))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
assertEquals("ORDER-1", orders.suspendedOrderId);
|
||||
assertEquals("现场暂停", orders.suspendReason);
|
||||
assertEquals("ORDER-1", orders.resumedOrderId);
|
||||
assertEquals("TASK-1", orders.cancelledExternalRequestId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmsRecoveryWithAuthenticatedOperatorAndCompleteEvidence()
|
||||
throws Exception {
|
||||
authenticate("operator-a");
|
||||
|
||||
mockMvc.perform(post("/wcs/recovery/cases/RC-1/confirm")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"reason\":\"现场复核\","
|
||||
+ "\"currentSite\":\"070501\","
|
||||
+ "\"loaded\":false,"
|
||||
+ "\"taskNumber\":\"30001\","
|
||||
+ "\"deviceMode\":\"AUTO\"}"))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
assertEquals("RC-1", recovery.confirmedCaseId);
|
||||
assertEquals("operator-a", recovery.operatorId);
|
||||
assertEquals("070501", recovery.evidence.getObservedSite());
|
||||
assertEquals("EMPTY", recovery.evidence.getLoadState());
|
||||
assertEquals("30001", recovery.evidence.getTaskNumber());
|
||||
assertEquals("AUTO", recovery.evidence.getDeviceMode());
|
||||
assertEquals("现场复核", recovery.evidence.getOperatorReason());
|
||||
assertEquals(Instant.parse("2026-08-11T08:00:00Z"),
|
||||
recovery.evidence.getEvidenceTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void declaresRecoveryConfirmationPermission() {
|
||||
Method confirm = null;
|
||||
for (Method method : WcsRecoveryController.class.getMethods()) {
|
||||
if ("confirm".equals(method.getName())) {
|
||||
confirm = method;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertNotNull(confirm);
|
||||
PreAuthorize permission = confirm.getAnnotation(PreAuthorize.class);
|
||||
assertNotNull(permission);
|
||||
assertEquals("@ss.hasPermi('wcs:recovery:confirm')",
|
||||
permission.value());
|
||||
}
|
||||
|
||||
private static void authenticate(String username) {
|
||||
SysUser user = new SysUser();
|
||||
user.setUserName(username);
|
||||
LoginUser loginUser = new LoginUser(user,
|
||||
Collections.singleton("wcs:recovery:confirm"));
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(loginUser, null,
|
||||
Collections.emptyList()));
|
||||
}
|
||||
|
||||
private static final class RecordingOrderUseCase
|
||||
implements TransportOrderUseCase {
|
||||
private String cancelledExternalRequestId;
|
||||
private String suspendedOrderId;
|
||||
private String suspendReason;
|
||||
private String resumedOrderId;
|
||||
|
||||
@Override
|
||||
public TransportOrderResult create(CreateTransportOrderCommand command) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOrderResult cancel(String externalRequestId) {
|
||||
cancelledExternalRequestId = externalRequestId;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOrderResult suspend(String orderId, String reason) {
|
||||
suspendedOrderId = orderId;
|
||||
suspendReason = reason;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOrderResult resume(String orderId) {
|
||||
resumedOrderId = orderId;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RecordingRecoveryUseCase
|
||||
implements RecoveryUseCase {
|
||||
private String confirmedCaseId;
|
||||
private String operatorId;
|
||||
private RecoveryEvidence evidence;
|
||||
|
||||
@Override
|
||||
public void confirmFacts(String caseId, String operator,
|
||||
RecoveryEvidence recoveryEvidence) {
|
||||
confirmedCaseId = caseId;
|
||||
operatorId = operator;
|
||||
evidence = recoveryEvidence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort(String caseId, String operator, String reason) {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class OperationsReadRepository
|
||||
implements WcsOperationsReadRepository {
|
||||
@Override
|
||||
public OrderExecutionView findOrder(String orderId) {
|
||||
return "ORDER-1".equals(orderId)
|
||||
? new OrderExecutionView("ORDER-1", "TASK-1",
|
||||
"EXECUTING", "WF-1", "STEP-1", "VEH-1", "CMD-1",
|
||||
"SENT", null, Instant.EPOCH) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderExecutionView findCommand(String commandKey) {
|
||||
return findOrder("ORDER-1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceOperationsView findDevice(String deviceId) {
|
||||
return new DeviceOperationsView("VEH-1", "SHUTTLE", "CMD-1",
|
||||
"SENT", "070501", true, null, null, Instant.EPOCH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceOperationsView findResource(String resourceKey) {
|
||||
return new ResourceOperationsView("NODE:070501", "NODE", 2, 1,
|
||||
"VEH-1", "OCCUPIED", "ORDER-1", "STEP-1",
|
||||
"VEH-1", 11L, null, Instant.EPOCH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecoveryCaseView findRecoveryCase(String caseId) {
|
||||
return recovery(caseId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OrderExecutionView> findOrders(String state, int offset,
|
||||
int limit) {
|
||||
return Collections.singletonList(findOrder("ORDER-1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countOrders(String state) {
|
||||
return 1L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceOperationsView> findDevices(String state,
|
||||
int offset, int limit) {
|
||||
return Collections.singletonList(findDevice("VEH-1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countDevices(String state) {
|
||||
return 1L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourceOperationsView> findResources(String state,
|
||||
int offset,
|
||||
int limit) {
|
||||
return Collections.singletonList(findResource("NODE:070501"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countResources(String state) {
|
||||
return 1L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RecoveryCaseView> findRecoveryCases(String state,
|
||||
int offset,
|
||||
int limit) {
|
||||
return Arrays.asList(recovery("RC-1"), recovery("RC-2"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countRecoveryCases(String state) {
|
||||
return 2L;
|
||||
}
|
||||
|
||||
private static RecoveryCaseView recovery(String caseId) {
|
||||
return new RecoveryCaseView(caseId, "VEH-1", "CMD-1", "OPEN",
|
||||
"TASK_NUMBER_MISMATCH", Instant.EPOCH, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user