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

9.4 KiB
Raw Blame History

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:
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:
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:
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:
private String trayCode;
private String entranceId;
private BigDecimal height;
  • Add mapper CAS SQL that updates only warehouse 03 inbound jobs in pending state:
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:

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:

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.