Files
FTC25009/docs/superpowers/plans/2026-07-14-dense-wcs-pull-and-routing.md

159 lines
9.4 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.
# Dense WCS Pull and Warehouse Routing 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:** Keep warehouse 03 inbound jobs pending for WCS pull, prevent duplicate active tray jobs, route push-mode jobs to each warehouse's configured WCS, and remove inbound-point selection from PDA tray binding.
**Architecture:** Warehouse 03 is the dense warehouse. PDA binding creates a status-2/phase-0 job without an entrance or outbound HTTP call; `POST /api/external/wcs/inbound/job` atomically claims that job by tray, writes the WCS-provided entrance and height, changes it to status-3/phase-1, and returns the stored job. Other warehouses keep push mode, but `WcsClient` resolves a warehouse-specific base URL from configuration. A SQL Server unique filtered index is the final concurrency-safe guarantee that `mydo_job.tray_code` is unique.
**Tech Stack:** Java 17, Spring Boot, MyBatis XML, SQL Server, JUnit 5/Mockito/AssertJ, Vue 2 uni-app.
---
## File map
- Modify `ruoyi-admin/src/main/java/com/ruoyi/api/config/ExternalProp.java`: support WCS base URLs keyed by warehouse ID.
- Modify `ruoyi-admin/src/main/java/com/ruoyi/api/client/BaseExternalClient.java`: resolve an API URL against a supplied base URL.
- Modify `ruoyi-admin/src/main/java/com/ruoyi/api/client/WcsClient.java`: route job push calls by `job.warehouseId`.
- Create `ruoyi-admin/src/main/java/com/ruoyi/api/domain/WcsDenseInboundJobRequest.java`: tray barcode, entrance number, and cargo height request.
- Modify `ruoyi-admin/src/main/java/com/ruoyi/api/controller/ExternalApiController.java`: expose the dense WCS job-pull endpoint.
- Modify `ruoyi-admin/src/main/java/com/ruoyi/in/job/mapper/MydoJobMapper.java` and `ruoyi-admin/src/main/resources/mapper/in/MydoJobMapper.xml`: atomic dense-job claim and tray-existence contracts.
- Modify `ruoyi-admin/src/main/java/com/ruoyi/in/job/service/IMydoJobService.java` and `ruoyi-admin/src/main/java/com/ruoyi/in/job/service/impl/MydoJobServiceImpl.java`: claim/validate the dense job and give duplicate trays a business error.
- Modify `ruoyi-admin/src/main/java/com/ruoyi/pda/domain/BindTrayRequest.java`, `ruoyi-admin/src/main/java/com/ruoyi/pda/domain/AddInJobRequest.java`, and `ruoyi-admin/src/main/java/com/ruoyi/pda/service/impl/PdaServiceImpl.java`: make entrance optional for binding and branch dense pull mode from ordinary push mode.
- Modify `pda/pages/warehouse/inbound/bindTray.vue` and `pda/api/warehouse/inbound.js`: remove inbound-point loading, selection, validation, and submission.
- Modify `ruoyi-admin/src/main/resources/application.yml`: document independently configurable warehouse WCS base URLs.
- Create `sql/20260714_mydo_job_tray_unique_and_wcs_pull.sql`: fail clearly on existing duplicates and add the unique index.
- Add/modify focused tests under `ruoyi-admin/src/test/java/com/ruoyi/api`, `ruoyi-admin/src/test/java/com/ruoyi/in/job`, and `ruoyi-admin/src/test/java/com/ruoyi/pda`.
### Task 1: Prove dense pending and station-free PDA binding
- [ ] Add service tests with a warehouse-03 request whose `stationPointId` is null. Assert the generated job is initialized with `status="2"`, `executionPhase="0"`, blank `entranceId`, blank roadway/location, and `wcsClient.sendPutawayTask` is never called.
- [ ] Add the ordinary-warehouse counterpart. Stub `selectAvailableInboundRoadway` and assert the job is still pushed, while no station is occupied and no entrance is written.
- [ ] Run `mvn -pl ruoyi-admin -am -Dtest=PdaServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test`; expect the new assertions to fail before implementation.
- [ ] Implement a station-free branch equivalent to:
```java
if (DENSE_WAREHOUSE_ID.equals(request.getWarehouseId())) {
job.setStatus("2");
job.setExecutionPhase("0");
return resultMsg + ";密集库作业已生成,等待WCS获取";
}
job.setRoadwayId(String.valueOf(selectAvailableInboundRoadway(
request.getTrayCode(), request.getWarehouseId()).get("roadway_id")));
return dispatchInboundJob(job, resultMsg);
```
- [ ] Remove station-point controls and `stationPointId` from the PDA payload, then re-run the focused test.
### Task 2: Add concurrency-safe tray uniqueness
- [ ] Add mapper/service tests asserting a nonblank tray already in `mydo_job` is rejected with `托盘已存在未完成作业` before insert/procedure execution.
- [ ] Add an idempotent SQL migration test requiring a duplicate preflight query and a unique filtered index on `mydo_job(tray_code)`.
- [ ] Implement `countMydoJobByTrayCode(String trayCode)` and call it at all application job-insert entry points touched by this flow.
- [ ] Create the migration with this invariant:
```sql
IF EXISTS (
SELECT tray_code FROM dbo.mydo_job
WHERE tray_code IS NOT NULL
GROUP BY tray_code HAVING COUNT_BIG(*) > 1
)
THROW 51000, N'mydo_job存在重复托盘条码,请清理后重试', 1;
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'dbo.mydo_job') AND name = N'UX_mydo_job_tray_code')
CREATE UNIQUE NONCLUSTERED INDEX UX_mydo_job_tray_code
ON dbo.mydo_job(tray_code) WHERE tray_code IS NOT NULL;
```
- [ ] Run focused mapper/service/migration tests and verify pass.
### Task 3: Route push-mode jobs by warehouse WCS address
- [ ] Add `WcsClientTest` cases with distinct `01` and `02` base URLs and assert each job posts to its own URL.
- [ ] Extend configuration with `warehouseBaseUrls` and deterministic fallback to the existing WCS `baseUrl` when a warehouse has no override.
- [ ] Resolve job URLs as:
```java
String url = config().getApiUrl(apiKey, job.getWarehouseId());
return post(url, body, interfaceName, this::parseWcsAck, MsgTypeEnum.JSON);
```
- [ ] Configure `01`, `02`, and `03` with independent environment-variable overrides in `application.yml`, without committing credentials.
- [ ] Run `mvn -pl ruoyi-admin -am -Dtest=WcsClientTest -Dsurefire.failIfNoSpecifiedTests=false test` and verify pass.
### Task 4: Add the dense WCS job-pull endpoint
- [ ] Add controller/service tests for missing tray, entrance, unsupported height, unknown tray, non-03 warehouse, non-inbound job, successful claim, and idempotent same-value retry.
- [ ] Define the request fields and aliases:
```java
private String trayCode;
private String entranceId;
private BigDecimal height;
```
- [ ] Add mapper CAS SQL that updates only warehouse 03 inbound jobs in pending state:
```sql
UPDATE mydo_job
SET entrance_id = #{entranceId}, height = #{height}, status = '3',
execution_phase = '1', start_time = COALESCE(start_time, CONVERT(varchar(19), GETDATE(), 120))
WHERE job_id = #{jobId} AND warehouse_id = '03' AND in_or_out < 20
AND status = '2' AND execution_phase = '0';
```
- [ ] Expose `POST /api/external/wcs/inbound/job`, invoke the transactional claim service, reload the persisted row, and return it in the established WCS `code/message/data` response.
- [ ] Run controller and service tests and verify pass.
### Task 5: Full verification and completion audit
- [ ] Run `mvn -pl ruoyi-admin -am test`; expect `BUILD SUCCESS`.
- [ ] Run the PDA build command available in `pda/package.json`; if no build script exists, run a static import/reference check proving the removed station API is no longer referenced by the bind page.
- [ ] Run `cd ruoyi-ui && npm run build:prod` if shared warehouse/config UI files were changed.
- [ ] Inspect the final diff and map each of the five numbered requirements to direct code plus test/migration evidence; do not mark complete if any item lacks evidence.
### Task 6: Correct point selection scope to warehouse 03 only
**Files:**
- Modify: `pda/pages/warehouse/inbound/bindTray.vue`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/pda/service/impl/PdaServiceImpl.java`
- Test: `ruoyi-admin/src/test/java/com/ruoyi/pda/service/impl/PdaServiceImplTest.java`
- [ ] **Step 1: Add differentiated service tests**
Assert warehouse `01`/`02` binding without `stationPointId` fails with `请选择入库点位`, while warehouse `03` succeeds without a station and never calls station-point services.
- [ ] **Step 2: Restore conditional PDA selection**
Use the exact dense-warehouse predicate and conditional submit rule:
```javascript
isDenseWarehouse() {
return this.warehouseId === '03'
}
if (!this.isDenseWarehouse && !this.stationPointId) {
this.$modal.msg('请选择入库点位')
return
}
```
Show/load/reset the station picker only when `warehouseId !== '03'`; submit `stationPointId` for ordinary warehouses and omit it for `03`.
- [ ] **Step 3: Restore backend enforcement**
In `bindTrayAndAddInJob`, reject a missing point for non-03 warehouses, pass the selected point into `AddInJobRequest`, and explicitly clear it for warehouse 03:
```java
if (!DENSE_WAREHOUSE_ID.equals(request.getWarehouseId())
&& !StringUtils.hasText(request.getStationPointId())) {
throw new RuntimeException("请选择入库点位");
}
addInJobRequest.setStationPointId(DENSE_WAREHOUSE_ID.equals(request.getWarehouseId())
? null : request.getStationPointId());
```
- [ ] **Step 4: Verify corrected behavior**
Run `mvn -pl ruoyi-admin -am -Dtest=PdaServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test`, compile the PDA SFC template/script, and run the full Maven test suite. Expected: warehouse 01/02 require points, warehouse 03 does not, and all tests pass.