Files
FT546-WCS/docs/superpowers/plans/2026-08-04-standard-wcs-core-foundation.md

535 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Standard WCS Core Foundation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 建立可被 `ruoyi-admin` 依赖的 `ruoyi-wcs` 模块,并交付订单、流程、步骤、幂等和核心持久化边界。
**Architecture:** `ruoyi-wcs` 只暴露入站用例、出站端口和查询 DTO;任务状态迁移保存在领域对象中,应用服务负责事务和幂等。Admin 只增加模块依赖,本计划不接 PLC,也不替换现有调度线程。
**Tech Stack:** Java 8, Spring Boot 2.5.15, Maven, JUnit 5, ArchUnit 1.2.1, MyBatis, SQL Server.
## Global Constraints
- 根 Reactor 新增且只新增 `ruoyi-wcs` 产品模块。
- `ruoyi-wcs` 只依赖 `ruoyi-common`、Spring 事务、MyBatis 和测试依赖;不依赖 `ruoyi-admin`、PLC 库或项目包。
- 所有公共接口使用 Java 8 类型,不使用 record、sealed class 或高版本 API。
- 订单、步骤和事件 ID 使用非空 `String`,时间使用 `java.time.Instant`。
- 外部请求以 `externalRequestId` 幂等;重复请求返回同一 `orderId`。
- 终态订单和步骤不可反向迁移。
- 本计划不修改 `BaseSRgvThread`、`PlcSrgvRead`、现有 WMS Controller 或旧业务表。
---
### Task 1: Add the ruoyi-wcs Maven module and architecture test harness
**Files:**
- Modify: `pom.xml`
- Modify: `ruoyi-admin/pom.xml`
- Create: `ruoyi-wcs/pom.xml`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/WcsModule.java`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/architecture/WcsModuleArchitectureTest.java`
**Interfaces:**
- Produces: Maven artifact `com.ruoyi:ruoyi-wcs:${ruoyi.version}`.
- Produces: marker type `com.ruoyi.wcs.WcsModule`.
- Enforces: no class under `com.ruoyi.wcs..` depends on `com.ruoyi.schedule..`, `com.ruoyi.api..`, `HslCommunication..`, or `org.apache.plc4x..`.
- [ ] **Step 1: Add the module skeleton and a deliberately failing boundary test**
Add `<module>ruoyi-wcs</module>` after `ruoyi-common`, add the managed dependency, add the Admin dependency, and create this test before `WcsModule` exists:
```java
@Test
void exposesAStableModuleMarker() throws Exception {
assertEquals("com.ruoyi.wcs.WcsModule",
Class.forName("com.ruoyi.wcs.WcsModule").getName());
}
```
Configure `spring-boot-starter-test` and `archunit-junit5:1.2.1` as test dependencies in `ruoyi-wcs/pom.xml`; configure Surefire `3.2.5` in the root build.
- [ ] **Step 2: Run the module test and observe RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=WcsModuleArchitectureTest test`
Expected: FAIL because `com.ruoyi.wcs.WcsModule` does not exist.
- [ ] **Step 3: Add the marker and dependency rules**
```java
package com.ruoyi.wcs;
public final class WcsModule {
private WcsModule() {
}
}
```
Add ArchUnit rules:
```java
@Test
void coreDoesNotDependOnProjectOrPlcPackages() {
noClasses().that().resideInAPackage("com.ruoyi.wcs..")
.should().dependOnClassesThat()
.resideInAnyPackage("com.ruoyi.schedule..", "com.ruoyi.api..",
"HslCommunication..", "org.apache.plc4x..")
.check(new ClassFileImporter().importPackages("com.ruoyi.wcs"));
}
```
- [ ] **Step 4: Run module and Admin reactor tests GREEN**
Run: `mvn -pl ruoyi-wcs,ruoyi-admin -am test`
Expected: Reactor succeeds and Surefire reports at least one `ruoyi-wcs` test.
- [ ] **Step 5: Commit**
```bash
git add pom.xml ruoyi-admin/pom.xml ruoyi-wcs
git commit -m "build: add standard WCS core module"
```
### Task 2: Define shared time, ID, event, and failure contracts
**Files:**
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/shared/WcsClock.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/shared/IdGenerator.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/shared/DomainEvent.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/shared/DomainRuleViolation.java`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/shared/SharedContractsTest.java`
**Interfaces:**
- Produces: `WcsClock.now(): Instant`.
- Produces: `IdGenerator.nextId(): String`.
- Produces: `DomainEvent.getEventId/getAggregateId/getSequence/getOccurredAt/getType`.
- [ ] **Step 1: Write failing immutable event tests**
```java
@Test
void rejectsBlankEventIdentity() {
assertThrows(IllegalArgumentException.class, () ->
new DomainEvent("", "ORDER-1", 1L, Instant.EPOCH, "OrderAccepted"));
}
@Test
void exposesDeterministicEventFields() {
DomainEvent event = new DomainEvent("E-1", "ORDER-1", 2L,
Instant.parse("2026-08-04T00:00:00Z"), "OrderPlanned");
assertEquals(2L, event.getSequence());
assertEquals("OrderPlanned", event.getType());
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=SharedContractsTest test`
Expected: compilation fails because the shared contracts do not exist.
- [ ] **Step 3: Implement the minimal contracts**
```java
public interface WcsClock {
Instant now();
}
public interface IdGenerator {
String nextId();
}
public final class DomainRuleViolation extends RuntimeException {
public DomainRuleViolation(String message) {
super(message);
}
}
```
Implement `DomainEvent` as a final immutable class with constructor validation and getters.
- [ ] **Step 4: Run GREEN**
Run: `mvn -pl ruoyi-wcs -am -Dtest=SharedContractsTest test`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/shared ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/shared
git commit -m "feat(wcs): add shared domain contracts"
```
### Task 3: Implement the TransportOrder state machine
**Files:**
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TransportOrderState.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TransportOrderType.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TransportOrder.java`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/task/TransportOrderTest.java`
**Interfaces:**
- Produces: `TransportOrder.accept(...)` factory.
- Produces: `plan(String workflowId)`, `reject(String code,String reason)`, `start()`, `suspend(String reason)`, `resumeToPlanned()`, `resumeToExecuting()`, `requestCancel()`, `cancel()`, `complete()`, `fail(String reason)`.
- Produces: `getUncommittedEvents(): List<DomainEvent>` and `clearUncommittedEvents()`.
- [ ] **Step 1: Write failing legal and illegal transition tests**
```java
@Test
void completesOnlyThroughPlannedAndExecuting() {
TransportOrder order = order();
order.plan("WF-1");
order.start();
order.complete();
assertEquals(TransportOrderState.COMPLETED, order.getState());
}
@Test
void terminalOrderCannotResume() {
TransportOrder order = order();
order.plan("WF-1");
order.cancel();
assertThrows(DomainRuleViolation.class, order::resumeToPlanned);
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=TransportOrderTest test`
- [ ] **Step 3: Implement explicit transition guards**
```java
public void start() {
requireState(TransportOrderState.PLANNED);
transitionTo(TransportOrderState.EXECUTING, "OrderStarted");
}
public void requestCancel() {
if (state != TransportOrderState.EXECUTING
&& state != TransportOrderState.SUSPENDED) {
throw new DomainRuleViolation("only executing or suspended order can request cancellation");
}
transitionTo(TransportOrderState.CANCEL_REQUESTED, "OrderCancelRequested");
}
```
Implement this exact transition set and increment event sequence exactly once per successful transition:
- `ACCEPTED -> PLANNED/REJECTED/SUSPENDED/CANCELLED/FAILED`.
- `PLANNED -> EXECUTING/SUSPENDED/CANCELLED/FAILED`.
- `EXECUTING -> COMPLETED/SUSPENDED/CANCEL_REQUESTED/FAILED`.
- `SUSPENDED -> PLANNED/EXECUTING/CANCEL_REQUESTED/FAILED`.
- `CANCEL_REQUESTED -> CANCELLED/FAILED` only after application-level command and resource convergence checks.
- `REJECTED/COMPLETED/CANCELLED/FAILED` are mutually exclusive terminal states and reject every further transition.
- [ ] **Step 4: Run state tests GREEN**
Run: `mvn -pl ruoyi-wcs -am -Dtest=TransportOrderTest test`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/task
git commit -m "feat(wcs): add transport order state machine"
```
### Task 4: Implement WorkflowInstance and TaskStep dependencies
**Files:**
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TaskStepState.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TaskStepType.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TaskStep.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/WorkflowInstance.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/WorkflowDefinition.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/WorkflowStepDefinition.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/WorkflowDefinitionProvider.java`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/task/WorkflowInstanceTest.java`
**Interfaces:**
- Produces: `WorkflowDefinitionProvider.definitionFor(TransportOrderType): WorkflowDefinition`.
- Produces: `WorkflowInstance.create(String workflowId, String orderId, WorkflowDefinition)`.
- Produces: `readySteps(): List<TaskStep>` and `onStepSucceeded(String stepId)`.
- `TaskStep` exposes `markReady`, `block(reasonCode)`, `unblock`, `assign(vehicleId,reservationIds)`, `start`, `suspend(reason)`, `resumeAssigned`, `resumeRunning`, `succeed`, `skip`, `cancel`, and `fail(reason)`.
- [ ] **Step 1: Write failing dependency and optional-step tests**
```java
@Test
void exposesOnlyStepsWhoseMandatoryDependenciesSucceeded() {
WorkflowInstance workflow = workflow("MOVE", "DROP");
assertEquals(Collections.singletonList("MOVE"), ids(workflow.readySteps()));
workflow.step("MOVE").assign("VEH-1");
workflow.step("MOVE").start();
workflow.step("MOVE").succeed();
workflow.onStepSucceeded("MOVE");
assertEquals(Collections.singletonList("DROP"), ids(workflow.readySteps()));
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=WorkflowInstanceTest test`
- [ ] **Step 3: Implement dependency evaluation and transition methods**
```java
public List<TaskStep> readySteps() {
List<TaskStep> result = new ArrayList<TaskStep>();
for (TaskStep step : steps.values()) {
if (step.isWaiting() && mandatoryDependenciesSucceeded(step)) {
step.markReady();
result.add(step);
}
}
return Collections.unmodifiableList(result);
}
```
Implement mandatory dependency failure as `FAILED`, optional branch exclusion as `SKIPPED`, and these exact transitions: `WAITING -> READY/BLOCKED/SKIPPED/CANCELLED/FAILED`; `BLOCKED -> READY/CANCELLED/FAILED`; `READY -> ASSIGNED/BLOCKED/CANCELLED/FAILED`; `ASSIGNED -> RUNNING/SUSPENDED/CANCELLED/FAILED`; `RUNNING -> SUCCEEDED/SUSPENDED/CANCELLED/FAILED`; `SUSPENDED -> ASSIGNED/RUNNING/CANCELLED/FAILED`. `SKIPPED/CANCELLED/FAILED/SUCCEEDED` are terminal.
- [ ] **Step 4: Run GREEN**
Run: `mvn -pl ruoyi-wcs -am -Dtest=WorkflowInstanceTest,TransportOrderTest test`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/WorkflowDefinitionProvider.java ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/task
git commit -m "feat(wcs): add workflow and task step model"
```
### Task 5: Add standard order use cases and idempotent application service
**Files:**
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/in/TransportOrderUseCase.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/task/CreateTransportOrderCommand.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/task/TransportOrderResult.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/task/TransportOrderApplicationService.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/TransportOrderRepository.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/DomainEventPublisher.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/WmsNotificationGateway.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/WmsNotification.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/WmsNotificationReceipt.java`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/application/task/TransportOrderApplicationServiceTest.java`
**Interfaces:**
- Produces: `create(CreateTransportOrderCommand)`, `cancel(String externalRequestId)`, `suspend(String orderId,String reason)`, `resume(String orderId)`.
- Consumes: `TransportOrderRepository.findByExternalRequestId/save`.
- Consumes: `WorkflowDefinitionProvider` and `DomainEventPublisher.publish(List<DomainEvent>)`.
- Produces outbound port `WmsNotificationGateway.notify(WmsNotification): WmsNotificationReceipt`; later plans deliver it through a core-owned Outbox.
- `WmsNotification` contains `eventId`, `externalRequestId`, `orderId`, `occurredAt`, `eventType`, `resultCode`, `resultMessage`, and typed context fields for `OrderAccepted`, `OrderStarted`, `OrderCompleted`, `OrderFailed`, `OrderCancelled`, and `OrderSuspended`.
- [ ] **Step 1: Write failing duplicate-request test**
```java
@Test
void returnsTheExistingOrderForTheSameExternalRequest() {
TransportOrderResult first = service.create(command("REQ-1"));
TransportOrderResult second = service.create(command("REQ-1"));
assertEquals(first.getOrderId(), second.getOrderId());
assertEquals(1, repository.count());
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=TransportOrderApplicationServiceTest test`
- [ ] **Step 3: Implement transactional orchestration**
```java
@Transactional
public TransportOrderResult create(CreateTransportOrderCommand command) {
TransportOrder existing = repository.findByExternalRequestId(command.getExternalRequestId());
if (existing != null) {
return TransportOrderResult.from(existing);
}
TransportOrder order = TransportOrder.accept(command, idGenerator.nextId(), clock.now());
WorkflowDefinition definition = workflowDefinitions.definitionFor(order.getType());
WorkflowInstance workflow = WorkflowInstance.create(idGenerator.nextId(), order.getOrderId(), definition);
order.plan(workflow.getWorkflowId());
repository.save(order, workflow);
eventPublisher.publish(order.getUncommittedEvents());
order.clearUncommittedEvents();
return TransportOrderResult.from(order);
}
```
Implement cancel/suspend/resume using domain methods; do not expose repository entities in results.
Map order domain events to immutable `WmsNotification` values without calling the gateway in the order transaction. `WmsNotificationReceipt` exposes `DELIVERED`, `RETRYABLE_FAILURE`, and `PERMANENT_FAILURE`; the notification `eventId` is always the external idempotency key.
- [ ] **Step 4: Run GREEN**
Run: `mvn -pl ruoyi-wcs -am -Dtest='TransportOrder*Test,WorkflowInstanceTest' test`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/task ruoyi-wcs/src/main/java/com/ruoyi/wcs/port ruoyi-wcs/src/test/java/com/ruoyi/wcs/application/task
git commit -m "feat(wcs): add idempotent transport order use cases"
```
### Task 6: Add the foundation SQL schema and MyBatis repository
**Files:**
- Create: `sql/wcs/V001__wcs_task_foundation.sql`
- Create: `sql/wcs/verify_V001__wcs_task_foundation.sql`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task/TransportOrderRow.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task/WorkflowRow.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task/TaskStepRow.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task/TransportOrderMapper.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task/MyBatisTransportOrderRepository.java`
- Create: `ruoyi-wcs/src/main/resources/mapper/wcs/task/TransportOrderMapper.xml`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/infrastructure/persistence/task/TransportOrderRowMapperTest.java`
**Interfaces:**
- Implements: `TransportOrderRepository` from Task 5.
- Produces: unique constraint `uk_wcs_transport_order_external_request`.
- Produces: optimistic `state_version` on order, workflow, and step rows.
- [ ] **Step 1: Write a failing domain-row round-trip test**
```java
@Test
void preservesOrderStateAndVersionAcrossPersistenceMapping() {
TransportOrder source = executingOrder("ORDER-1", 4L);
TransportOrderRow row = TransportOrderRow.fromDomain(source);
TransportOrder restored = row.toDomain();
assertEquals(source.getState(), restored.getState());
assertEquals(4L, restored.getStateVersion());
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=TransportOrderRowMapperTest test`
- [ ] **Step 3: Create idempotent SQL Server DDL**
The first table must include this exact invariant:
```sql
IF OBJECT_ID('dbo.wcs_transport_order', 'U') IS NULL
BEGIN
CREATE TABLE dbo.wcs_transport_order (
order_id varchar(64) NOT NULL PRIMARY KEY,
external_request_id varchar(128) NOT NULL,
order_type varchar(32) NOT NULL,
load_id varchar(128) NULL,
source_code varchar(128) NOT NULL,
destination_code varchar(128) NOT NULL,
priority int NOT NULL,
order_state varchar(32) NOT NULL,
workflow_id varchar(64) NULL,
state_version bigint NOT NULL,
created_time datetime2(3) NOT NULL,
updated_time datetime2(3) NOT NULL,
CONSTRAINT uk_wcs_transport_order_external_request UNIQUE (external_request_id)
);
END;
```
Add `wcs_workflow_instance`, `wcs_task_step`, `wcs_task_dependency`, and `wcs_domain_event` with named constraints and no `default02-default08` columns.
- [ ] **Step 4: Implement mapping and optimistic updates**
`TransportOrderMapper.updateState` must include `WHERE order_id = #{orderId} AND state_version = #{expectedVersion}` and increment `state_version`; repository throws `OptimisticLockingFailureException` when update count is 0.
- [ ] **Step 5: Run mapping and schema text tests GREEN**
Run: `mvn -pl ruoyi-wcs -am -Dtest=TransportOrderRowMapperTest test`
Run the read-only verification script against the isolated WCS test database and expect one row per required table and unique constraint.
- [ ] **Step 6: Commit**
```bash
git add sql/wcs ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task ruoyi-wcs/src/main/resources/mapper/wcs/task ruoyi-wcs/src/test/java/com/ruoyi/wcs/infrastructure/persistence/task
git commit -m "feat(wcs): persist transport orders and workflows"
```
### Task 7: Wire the core module into RuoYi without changing legacy behavior
**Files:**
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/WcsCoreConfiguration.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/wcs/WcsCoreWiringTest.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/wcs/TestPersistenceConfiguration.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/wcs/AdminWcsBoundaryArchitectureTest.java`
- Verify: `ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java`
- Verify: `ruoyi-admin/src/main/resources/application.yml`
**Interfaces:**
- Produces: Spring beans `TransportOrderUseCase`, `TransportOrderRepository`, `WcsClock`, and `IdGenerator`.
- Does not change: `ScheduleThread`, `BaseSRgvThread`, `Wcs2WmsController`, or PLC writes.
- Enforces: project/Admin packages may use `com.ruoyi.wcs.port.in..`, `com.ruoyi.wcs.port.out..`, and `com.ruoyi.wcs.query..`, but may not depend on `com.ruoyi.wcs.infrastructure.persistence..` or concrete Repository implementations.
- [ ] **Step 1: Write a failing Spring wiring test**
```java
@Test
void exposesTheTransportOrderUseCaseFromTheWcsModule() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(WcsCoreConfiguration.class, TestPersistenceConfiguration.class);
context.refresh();
assertNotNull(context.getBean(TransportOrderUseCase.class));
}
}
```
Create the test-only dependency configuration explicitly:
```java
@TestConfiguration
public class TestPersistenceConfiguration {
@Bean
public TransportOrderRepository transportOrderRepository() {
return Mockito.mock(TransportOrderRepository.class);
}
@Bean
public WorkflowDefinitionProvider workflowDefinitionProvider() {
return Mockito.mock(WorkflowDefinitionProvider.class);
}
@Bean
public DomainEventPublisher domainEventPublisher() {
return Mockito.mock(DomainEventPublisher.class);
}
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-admin -am -Dtest=WcsCoreWiringTest test`
- [ ] **Step 3: Add explicit core configuration**
```java
@Configuration
@ComponentScan(basePackages = "com.ruoyi.wcs")
public class WcsCoreConfiguration {
}
```
Do not alter `@MapperScan("com.ruoyi.**.mapper")`; add a test assertion that `TransportOrderMapper` is found under the existing wildcard scan.
Add an ArchUnit rule importing `com.ruoyi.project..` and `com.ruoyi.api..` that fails on dependencies to `com.ruoyi.wcs.infrastructure.persistence..`; allow only the public ports, application use cases, domain value types required by those ports, and query DTOs.
- [ ] **Step 4: Run the full foundation verification**
Run: `mvn -pl ruoyi-wcs,ruoyi-admin -am test`
Run: `git diff --check`
Expected: tests pass, no PLC or scheduler source file is modified, and the module boundary test passes.
- [ ] **Step 5: Commit**
```bash
git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/WcsCoreConfiguration.java ruoyi-admin/src/test/java/com/ruoyi/wcs
git commit -m "feat(wcs): wire core module into RuoYi"
```