159 lines
9.4 KiB
Markdown
159 lines
9.4 KiB
Markdown
# 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.
|