535 lines
22 KiB
Markdown
535 lines
22 KiB
Markdown
# 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"
|
||
```
|