35 KiB
Inbound Cargo Space Height 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: Persist the WCS height level and enforce height-compatible, normal-space-first inbound allocation for warehouses 01 and 02, including PUT_OCCUPIED reallocation.
Architecture: Add height to the WCS request contract and persist it in the existing mydo_job.height column. Pass the value into the normal SQL Server lock query, apply the_height >= requested height, and sort exact-height candidates before oversized candidates. Reuse the persisted job height in the existing transactional PUT_OCCUPIED path. Apply the 01/02 cargo-space master-data update through an idempotent, assertion-guarded SQL script.
Tech Stack: Java 17, Spring Boot, MyBatis XML, SQL Server 2016, JUnit 5, Mockito, AssertJ, Maven, PowerShell System.Data.SqlClient.
File Map
ruoyi-admin/src/main/java/com/ruoyi/api/domain/WcsInboundLocationRequest.java: WCS requestheightproperty.ruoyi-admin/src/main/java/com/ruoyi/api/controller/ExternalApiController.java: request validation, idempotency, lock invocation, and height persistence.ruoyi-admin/src/main/java/com/ruoyi/pda/service/PdaService.java: height-aware lock contract.ruoyi-admin/src/main/java/com/ruoyi/pda/service/impl/PdaServiceImpl.java: mapper parameter propagation and stale-lock compatibility check.ruoyi-admin/src/main/resources/mapper/pda/PdaMapper.xml: normal allocation capacity filter and exact-height priority.ruoyi-admin/src/main/java/com/ruoyi/in/job/service/impl/MydoJobServiceImpl.java: reject invalid legacy heights beforePUT_OCCUPIEDchanges the old location.ruoyi-admin/src/main/resources/mapper/bas/BasCargoSpaceMapper.xml: height-compatiblePUT_OCCUPIEDcandidate filter and ordering.ruoyi-admin/src/test/java/com/ruoyi/api/domain/WcsInboundLocationRequestTest.java: DTO contract test.ruoyi-admin/src/test/java/com/ruoyi/api/controller/ExternalApiControllerWcsCodeTest.java: validation, persistence, and retry behavior.ruoyi-admin/src/test/java/com/ruoyi/pda/service/impl/PdaServiceImplTest.java: height propagation and stale-lock behavior.ruoyi-admin/src/test/java/com/ruoyi/pda/mapper/PdaMapperSqlTest.java: normal allocation SQL assertions.ruoyi-admin/src/test/java/com/ruoyi/in/job/service/impl/MydoJobServiceImplTest.java: invalid-height and inherited-height reallocation tests.ruoyi-admin/src/test/java/com/ruoyi/bas/cargospace/mapper/BasCargoSpaceMapperSqlTest.java: reallocation SQL assertions.ruoyi-admin/src/test/java/com/ruoyi/bas/cargospace/mapper/CargoSpaceHeightMigrationSqlTest.java: migration contract test.sql/20260710_cargo_space_height.sql: guarded, repeatable 01/02 master-data update.
The worktree already contains unrelated edits in PdaServiceImpl.java, PdaMapper.xml, PdaServiceImplTest.java, MydoJobServiceImpl.java, and other files. Preserve those edits and do not stage or commit an entire overlapping file. The implementation stays uncommitted for review unless the task-specific hunks can be isolated without staging user-owned changes.
Task 1: Add the Height Contracts
Files:
-
Create:
ruoyi-admin/src/test/java/com/ruoyi/api/domain/WcsInboundLocationRequestTest.java -
Modify:
ruoyi-admin/src/main/java/com/ruoyi/api/domain/WcsInboundLocationRequest.java -
Modify:
ruoyi-admin/src/test/java/com/ruoyi/pda/service/impl/PdaServiceImplTest.java -
Modify:
ruoyi-admin/src/main/java/com/ruoyi/pda/service/PdaService.java -
Modify:
ruoyi-admin/src/main/java/com/ruoyi/pda/service/impl/PdaServiceImpl.java -
Step 1: Write the failing DTO contract test
package com.ruoyi.api.domain;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
class WcsInboundLocationRequestTest {
@Test
void carriesHeightLevelAsBigDecimal() throws Exception {
WcsInboundLocationRequest request = new WcsInboundLocationRequest();
Method setter = WcsInboundLocationRequest.class.getMethod("setHeight", BigDecimal.class);
Method getter = WcsInboundLocationRequest.class.getMethod("getHeight");
setter.invoke(request, BigDecimal.valueOf(2));
assertThat(getter.invoke(request)).isEqualTo(BigDecimal.valueOf(2));
}
}
- Step 2: Run the DTO test and verify RED
Run:
mvn -pl ruoyi-admin -Dtest=WcsInboundLocationRequestTest test
Expected: FAIL with NoSuchMethodException: ...setHeight(java.math.BigDecimal).
- Step 3: Add the DTO field and accessors
Add import java.math.BigDecimal;, then add this field after warehouseId and these accessors before the closing brace:
/**
* 货物高度等级:1=普通,2=高货。
*/
private BigDecimal height;
public BigDecimal getHeight() {
return height;
}
public void setHeight(BigDecimal height) {
this.height = height;
}
- Step 4: Run the DTO test and verify GREEN
Run:
mvn -pl ruoyi-admin -Dtest=WcsInboundLocationRequestTest test
Expected: PASS, 1 test and 0 failures.
- Step 5: Write the failing service-contract test
Add import com.ruoyi.pda.service.PdaService;, import java.lang.reflect.Method;, and import java.util.Map;, then add this test to PdaServiceImplTest:
@Test
void inboundCargoSpaceLockContractAcceptsHeightLevel() throws Exception {
Method method = PdaService.class.getMethod(
"lockInboundCargoSpace",
String.class,
String.class,
String.class,
BigDecimal.class);
assertThat(method.getReturnType()).isEqualTo(Map.class);
}
- Step 6: Run the service-contract test and verify RED
Run:
mvn -pl ruoyi-admin '-Dtest=PdaServiceImplTest#inboundCargoSpaceLockContractAcceptsHeightLevel' test
Expected: FAIL with NoSuchMethodException for the four-argument method.
- Step 7: Change the service contract without changing allocation behavior yet
Add import java.math.BigDecimal; to PdaService.java and change the declaration to:
Map<String, Object> lockInboundCargoSpace(
String trayCode,
String warehouseId,
String roadwayId,
BigDecimal height);
Change the matching implementation signature to:
@Override
public Map<String, Object> lockInboundCargoSpace(
String trayCode,
String warehouseId,
String roadwayId,
BigDecimal height) {
Change the controller invocation so the project continues to compile with the new contract; Task 2 adds validation and persistence around this call:
cargoSpace = pdaService.lockInboundCargoSpace(
job.getTrayCode(),
job.getWarehouseId(),
job.getRoadwayId(),
request == null ? null : request.getHeight());
- Step 8: Run both contract tests and verify GREEN
Run:
mvn -pl ruoyi-admin '-Dtest=WcsInboundLocationRequestTest,PdaServiceImplTest#inboundCargoSpaceLockContractAcceptsHeightLevel' test
Expected: PASS, 2 tests and 0 failures.
Task 2: Validate and Persist WCS Height
Files:
-
Modify:
ruoyi-admin/src/test/java/com/ruoyi/api/controller/ExternalApiControllerWcsCodeTest.java -
Modify:
ruoyi-admin/src/main/java/com/ruoyi/api/controller/ExternalApiController.java -
Step 1: Add controller tests for invalid height, first allocation, and retries
Add imports for WcsInboundLocationRequest, ArgumentCaptor, BigDecimal, verifyNoInteractions, and the following tests:
@Test
void inboundLocationRejectsMissingHeightBeforeLookingUpJob() {
WcsInboundLocationRequest request = new WcsInboundLocationRequest();
request.setJobId("RK001");
AjaxResult result = controller.inboundLocation(request);
assertWcsError(result, "height必须为1或2");
verifyNoInteractions(mydoJobService, pdaService);
}
@Test
void inboundLocationRejectsUnsupportedHeightBeforeLookingUpJob() {
WcsInboundLocationRequest request = new WcsInboundLocationRequest();
request.setJobId("RK001");
request.setHeight(BigDecimal.valueOf(3));
AjaxResult result = controller.inboundLocation(request);
assertWcsError(result, "height必须为1或2");
verifyNoInteractions(mydoJobService, pdaService);
}
@Test
void inboundLocationPersistsHeightAfterLockingFirstCargoSpace() {
WcsInboundLocationRequest request = new WcsInboundLocationRequest();
request.setJobId("RK001");
request.setHeight(BigDecimal.ONE);
MydoJob job = new MydoJob();
job.setJobId("RK001");
job.setTrayCode("A001");
job.setWarehouseId("01");
job.setRoadwayId("R01");
when(mydoJobService.selectMydoJobByJobId("RK001")).thenReturn(job);
when(pdaService.lockInboundCargoSpace("A001", "01", "R01", BigDecimal.ONE))
.thenReturn(Map.of(
"roadway_id", "R01",
"cargo_space_id", "CS001",
"cargo_space_name", "01-01-01"));
when(mydoJobService.updateMydoJob(job)).thenReturn(1);
AjaxResult result = controller.inboundLocation(request);
assertWcsSuccess(result);
ArgumentCaptor<MydoJob> captor = ArgumentCaptor.forClass(MydoJob.class);
verify(mydoJobService).updateMydoJob(captor.capture());
assertThat(captor.getValue().getHeight()).isEqualByComparingTo("1");
assertThat(captor.getValue().getCargoSpaceId()).isEqualTo("CS001");
}
@Test
void inboundLocationReusesAssignedCargoSpaceForSameHeight() {
WcsInboundLocationRequest request = new WcsInboundLocationRequest();
request.setJobId("RK001");
request.setHeight(BigDecimal.ONE);
MydoJob job = new MydoJob();
job.setJobId("RK001");
job.setCargoSpaceId("CS001");
job.setHeight(new BigDecimal("1.00"));
when(mydoJobService.selectMydoJobByJobId("RK001")).thenReturn(job);
AjaxResult result = controller.inboundLocation(request);
assertWcsSuccess(result);
verifyNoInteractions(pdaService);
verify(mydoJobService, never()).updateMydoJob(job);
}
@Test
void inboundLocationRejectsDifferentHeightForAssignedCargoSpace() {
WcsInboundLocationRequest request = new WcsInboundLocationRequest();
request.setJobId("RK001");
request.setHeight(BigDecimal.valueOf(2));
MydoJob job = new MydoJob();
job.setJobId("RK001");
job.setCargoSpaceId("CS001");
job.setHeight(BigDecimal.ONE);
when(mydoJobService.selectMydoJobByJobId("RK001")).thenReturn(job);
AjaxResult result = controller.inboundLocation(request);
assertWcsError(result, "请求height与已分配作业不一致");
verifyNoInteractions(pdaService);
verify(mydoJobService, never()).updateMydoJob(job);
}
- Step 2: Run the controller tests and verify RED
Run:
mvn -pl ruoyi-admin -Dtest=ExternalApiControllerWcsCodeTest test
Expected: the new tests FAIL because height is not validated, passed to the service, persisted, or compared on retries.
- Step 3: Implement validation, persistence, and retry checks
Add import java.math.BigDecimal;. At the start of inboundLocation, validate the request:
BigDecimal requestedHeight = request == null ? null : request.getHeight();
if (!isSupportedHeight(requestedHeight)) {
return wcsError("height必须为1或2");
}
After resolving job and before locking, replace the existing cargo-space branch with:
if (job.getCargoSpaceId() != null && !job.getCargoSpaceId().trim().isEmpty()) {
if (!isSupportedHeight(job.getHeight())) {
return wcsError("已分配入库作业height无效");
}
if (job.getHeight().compareTo(requestedHeight) != 0) {
return wcsError("请求height与已分配作业不一致");
}
return wcsSuccess(job);
}
Map<String, Object> cargoSpace;
try {
cargoSpace = pdaService.lockInboundCargoSpace(
job.getTrayCode(),
job.getWarehouseId(),
job.getRoadwayId(),
requestedHeight);
} catch (RuntimeException e) {
return wcsError(e.getMessage());
}
job.setRoadwayId(String.valueOf(cargoSpace.get("roadway_id")));
job.setCargoSpaceId(String.valueOf(cargoSpace.get("cargo_space_id")));
job.setCargoSpaceName(String.valueOf(cargoSpace.get("cargo_space_name")));
job.setHeight(requestedHeight);
int updateRows = mydoJobService.updateMydoJob(job);
if (updateRows <= 0) {
pdaService.releaseInboundCargoSpace(job.getTrayCode(), job.getWarehouseId());
return wcsError("入库货位回写作业失败");
}
Add this helper near the existing private response helpers:
private boolean isSupportedHeight(BigDecimal height) {
return height != null
&& (height.compareTo(BigDecimal.ONE) == 0
|| height.compareTo(BigDecimal.valueOf(2)) == 0);
}
- Step 4: Run the controller tests and verify GREEN
Run:
mvn -pl ruoyi-admin -Dtest=ExternalApiControllerWcsCodeTest test
Expected: PASS, all controller tests and 0 failures.
Task 3: Enforce Height in Normal Cargo-Space Locking
Files:
-
Modify:
ruoyi-admin/src/test/java/com/ruoyi/pda/service/impl/PdaServiceImplTest.java -
Modify:
ruoyi-admin/src/test/java/com/ruoyi/pda/mapper/PdaMapperSqlTest.java -
Modify:
ruoyi-admin/src/main/java/com/ruoyi/pda/service/impl/PdaServiceImpl.java -
Modify:
ruoyi-admin/src/main/resources/mapper/pda/PdaMapper.xml -
Step 1: Add failing service tests
Add import static org.mockito.Mockito.verifyNoInteractions; if it is not already present, then add these tests to PdaServiceImplTest:
@Test
void lockInboundCargoSpacePassesHeightAndReusesCompatibleLock() {
HashMap<String, Object> rule = new HashMap<>();
rule.put("allowed_cargo_type", "A");
when(pdaMapper.selectMatchedTrayRule(any(HashMap.class))).thenReturn(rule);
HashMap<String, Object> locked = new HashMap<>();
locked.put("cargo_space_id", "CS-HIGH");
locked.put("the_height", 2.0D);
when(pdaMapper.selectLockedCargoSpaceByTrayCode(any(HashMap.class))).thenReturn(locked);
Map<String, Object> result = service.lockInboundCargoSpace(
"A001", "01", "R01", BigDecimal.ONE);
assertThat(result).isSameAs(locked);
ArgumentCaptor<HashMap<String, Object>> captor = ArgumentCaptor.forClass(HashMap.class);
verify(pdaMapper).selectLockedCargoSpaceByTrayCode(captor.capture());
assertThat(captor.getValue()).containsEntry("v_height", BigDecimal.ONE);
verify(pdaMapper, never()).lockInboundCargoSpace(any(HashMap.class));
}
@Test
void lockInboundCargoSpaceRejectsIncompatibleExistingLock() {
HashMap<String, Object> rule = new HashMap<>();
rule.put("allowed_cargo_type", "A");
when(pdaMapper.selectMatchedTrayRule(any(HashMap.class))).thenReturn(rule);
HashMap<String, Object> locked = new HashMap<>();
locked.put("cargo_space_id", "CS-NORMAL");
locked.put("the_height", 1.0D);
when(pdaMapper.selectLockedCargoSpaceByTrayCode(any(HashMap.class))).thenReturn(locked);
assertThatThrownBy(() -> service.lockInboundCargoSpace(
"A001", "01", "R01", BigDecimal.valueOf(2)))
.isInstanceOf(RuntimeException.class)
.hasMessageContaining("已锁定货位高度不满足");
verify(pdaMapper, never()).lockInboundCargoSpace(any(HashMap.class));
}
@Test
void lockInboundCargoSpaceRejectsUnsupportedHeight() {
assertThatThrownBy(() -> service.lockInboundCargoSpace(
"A001", "01", "R01", BigDecimal.ZERO))
.isInstanceOf(RuntimeException.class)
.hasMessage("height必须为1或2");
verifyNoInteractions(pdaMapper);
}
- Step 2: Add the failing normal-allocation SQL test
Replace the existing inboundCargoSpaceLockChoosesPlatoonWithMostEmptySpacesFirst assertion body with:
assertThat(lockSql)
.contains("and bcs.the_height >= #{v_height}")
.contains("case when bcs.the_height = #{v_height} then 0 else 1 end as height_priority")
.contains("count(1) over (partition by bcs.the_platoon) as platoon_empty_count")
.contains("order by height_priority asc, platoon_empty_count desc");
String lockedSql = section(
xml,
"<select id=\"selectLockedCargoSpaceByTrayCode\"",
"</select>");
assertThat(lockedSql).contains("the_height");
- Step 3: Run service and mapper tests and verify RED
Run:
mvn -pl ruoyi-admin '-Dtest=PdaServiceImplTest#lockInboundCargoSpace*,PdaMapperSqlTest#inboundCargoSpaceLockChoosesPlatoonWithMostEmptySpacesFirst' test
Expected: FAIL because v_height, compatibility validation, capacity filtering, priority ordering, and locked-row height are absent.
- Step 4: Implement service validation and mapper propagation
At the start of lockInboundCargoSpace, before the roadway validation, add:
if (!isSupportedCargoHeight(height)) {
throw new RuntimeException("height必须为1或2");
}
Add the mapper parameter:
lockMap.put("v_height", height);
Replace the existing locked-row return block with:
if (locked != null && !locked.isEmpty()) {
if (toBigDecimal(locked.get("the_height")).compareTo(height) < 0) {
throw new RuntimeException("已锁定货位高度不满足本次请求");
}
return locked;
}
Add this helper beside the existing toBigDecimal helper:
private boolean isSupportedCargoHeight(BigDecimal height) {
return height != null
&& (height.compareTo(BigDecimal.ONE) == 0
|| height.compareTo(BigDecimal.valueOf(2)) == 0);
}
- Step 5: Implement SQL capacity filtering and exact-height priority
In the lock subquery, add the_height and height_priority:
select bcs.cargo_space_id,
bcs.the_platoon,
bcs.the_column,
bcs.the_floor,
bcs.the_height,
case when bcs.the_height = #{v_height} then 0 else 1 end as height_priority,
count(1) over (partition by bcs.the_platoon) as platoon_empty_count
Add this candidate condition after the roadway condition:
and bcs.the_height >= #{v_height}
Change the candidate ordering to:
order by height_priority asc, platoon_empty_count desc,
the_platoon asc, the_floor asc, the_column asc, cargo_space_id asc
Return the locked row's capacity:
select top 1 cargo_space_id, cargo_space_name, roadway_id, the_height
- Step 6: Run service and mapper tests and verify GREEN
Run:
mvn -pl ruoyi-admin '-Dtest=PdaServiceImplTest#lockInboundCargoSpace*,PdaMapperSqlTest#inboundCargoSpaceLockChoosesPlatoonWithMostEmptySpacesFirst' test
Expected: PASS, all selected tests and 0 failures.
Task 4: Preserve Height During PUT_OCCUPIED Reallocation
Files:
-
Modify:
ruoyi-admin/src/test/java/com/ruoyi/in/job/service/impl/MydoJobServiceImplTest.java -
Modify:
ruoyi-admin/src/test/java/com/ruoyi/bas/cargospace/mapper/BasCargoSpaceMapperSqlTest.java -
Modify:
ruoyi-admin/src/main/java/com/ruoyi/in/job/service/impl/MydoJobServiceImpl.java -
Modify:
ruoyi-admin/src/main/resources/mapper/bas/BasCargoSpaceMapper.xml -
Step 1: Add the failing invalid-height reallocation test
Add import java.math.BigDecimal; and this test:
@Test
void handleWcsPutOccupiedExceptionRejectsUnsupportedJobHeightBeforeChangingLocation() {
MydoJob job = new MydoJob();
job.setJobId("RK002");
job.setInOrOut(10L);
job.setTrayCode("TRAY002");
job.setWarehouseId("01");
job.setRoadwayId("R01");
job.setCargoSpaceId("OLD-CS");
job.setHeight(BigDecimal.ZERO);
when(mydoJobMapper.selectMydoJobByJobId("RK002")).thenReturn(job);
AjaxResult result = service.handleWcsPutOccupiedException("RK002");
assertThat(result.isSuccess()).isFalse();
assertThat(result.get(AjaxResult.MSG_TAG)).isEqualTo("入库作业height必须为1或2");
verifyNoInteractions(basCargoSpaceMapper);
}
Set job.setHeight(BigDecimal.ONE); in the existing successful handleWcsPutOccupiedExceptionMarksOldLocationAndReturnsReassignedInboundJob fixture and add this verification after the call:
verify(basCargoSpaceMapper).selectAvailableInboundCargoSpaceInRoadway(
argThat(candidate -> candidate.getHeight().compareTo(BigDecimal.ONE) == 0));
- Step 2: Add the failing reallocation SQL test
Extend putOccupiedReallocationChoosesPlatoonWithMostAvailableCargoSpaces with:
assertThat(selectSql)
.contains("and bcs.the_height >= #{height}")
.contains("case when bcs.the_height = #{height} then 0 else 1 end as height_priority")
.contains("count(1) over (partition by bcs.the_platoon) as platoon_empty_count")
.contains("order by height_priority asc, platoon_empty_count desc");
- Step 3: Run the reallocation tests and verify RED
Run:
mvn -pl ruoyi-admin '-Dtest=MydoJobServiceImplTest#handleWcsPutOccupiedException*,BasCargoSpaceMapperSqlTest#putOccupiedReallocationChoosesPlatoonWithMostAvailableCargoSpaces' test
Expected: FAIL because invalid job height is not rejected and the reallocation SQL has no capacity or priority rule.
- Step 4: Validate persisted height before changing the old location
Add import java.math.BigDecimal;. After warehouse/roadway validation and before markInboundNeedCheck, add:
if (!isSupportedCargoHeight(job.getHeight())) {
return AjaxResult.error("入库作业height必须为1或2");
}
Add this private helper near rollbackError:
private boolean isSupportedCargoHeight(BigDecimal height) {
return height != null
&& (height.compareTo(BigDecimal.ONE) == 0
|| height.compareTo(BigDecimal.valueOf(2)) == 0);
}
- Step 5: Add the same capacity and ordering rules to reallocation SQL
Add this expression to the candidate projection before platoon_empty_count:
case when bcs.the_height = #{height} then 0 else 1 end as height_priority,
Add this predicate after the roadway predicate:
and bcs.the_height >= #{height}
Change the final ordering to:
order by height_priority asc, platoon_empty_count desc,
the_platoon asc, the_floor asc, the_column asc, cargo_space_id asc
- Step 6: Run the reallocation tests and verify GREEN
Run:
mvn -pl ruoyi-admin '-Dtest=MydoJobServiceImplTest#handleWcsPutOccupiedException*,BasCargoSpaceMapperSqlTest#putOccupiedReallocationChoosesPlatoonWithMostAvailableCargoSpaces' test
Expected: PASS, all selected tests and 0 failures.
Task 5: Create and Execute the Guarded Master-Data Migration
Files:
-
Create:
ruoyi-admin/src/test/java/com/ruoyi/bas/cargospace/mapper/CargoSpaceHeightMigrationSqlTest.java -
Create:
sql/20260710_cargo_space_height.sql -
Step 1: Write the failing migration contract test
package com.ruoyi.bas.cargospace.mapper;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
class CargoSpaceHeightMigrationSqlTest {
@Test
void assignsHeightLevelsOnlyToWarehouses01And02WithSafetyChecks() throws Exception {
String sql = Files.readString(resolveSql()).toLowerCase();
assertThat(sql)
.contains("set xact_abort on")
.contains("begin transaction")
.contains("when the_floor in (1, 5) then 2 else 1 end")
.contains("where warehouse_id in ('01', '02')")
.contains("@target_count <> 340")
.contains("@high_count <> 136")
.contains("@normal_count <> 204")
.contains("rollback transaction")
.contains("commit transaction");
}
private Path resolveSql() {
return Stream.of(
Path.of("sql/20260710_cargo_space_height.sql"),
Path.of("../sql/20260710_cargo_space_height.sql"))
.filter(Files::exists)
.findFirst()
.orElseThrow();
}
}
- Step 2: Run the migration contract test and verify RED
Run:
mvn -pl ruoyi-admin -Dtest=CargoSpaceHeightMigrationSqlTest test
Expected: FAIL with NoSuchElementException because the SQL file does not exist.
- Step 3: Create the guarded, repeatable SQL script
set nocount on;
set xact_abort on;
begin try
begin transaction;
declare @target_count int;
declare @high_count int;
declare @normal_count int;
declare @updated_rows int;
select @target_count = count(*)
from dbo.bas_cargo_space
where warehouse_id in ('01', '02');
select @high_count = count(*)
from dbo.bas_cargo_space
where warehouse_id in ('01', '02')
and the_floor in (1, 5);
select @normal_count = count(*)
from dbo.bas_cargo_space
where warehouse_id in ('01', '02')
and the_floor not in (1, 5);
if @target_count <> 340
begin
;throw 51001, 'Unexpected 01/02 cargo-space count; no changes committed.', 1;
end;
if @high_count <> 136
begin
;throw 51002, 'Unexpected high cargo-space count; no changes committed.', 1;
end;
if @normal_count <> 204
begin
;throw 51003, 'Unexpected normal cargo-space count; no changes committed.', 1;
end;
update dbo.bas_cargo_space
set the_height = case when the_floor in (1, 5) then 2 else 1 end
where warehouse_id in ('01', '02');
set @updated_rows = @@rowcount;
if @updated_rows <> @target_count
begin
;throw 51004, 'Not all 01/02 cargo spaces were updated; no changes committed.', 1;
end;
if exists (
select 1
from dbo.bas_cargo_space
where warehouse_id in ('01', '02')
and (
the_height is null
or the_height <> case when the_floor in (1, 5) then 2 else 1 end
)
)
begin
;throw 51005, 'Cargo-space height verification failed; no changes committed.', 1;
end;
commit transaction;
select warehouse_id,
the_floor,
the_height,
count(*) as cargo_space_count
from dbo.bas_cargo_space
where warehouse_id in ('01', '02')
group by warehouse_id, the_floor, the_height
order by warehouse_id, the_floor, the_height;
end try
begin catch
if @@trancount > 0
rollback transaction;
throw;
end catch;
- Step 4: Run the migration contract test and verify GREEN
Run:
mvn -pl ruoyi-admin -Dtest=CargoSpaceHeightMigrationSqlTest test
Expected: PASS, 1 test and 0 failures.
- Step 5: Re-run the read-only preflight immediately before writing
Run this read-only PowerShell block from the repository root. It extracts the configured master connection without printing credentials:
$config = Get-Content -Raw 'ruoyi-admin/src/main/resources/application-druid.yml'
$master = [regex]::Match($config, '(?ms)^[ \t]*master:[ \t]*\r?\n(?<body>.*?)(?=^[ \t]*slave:)').Groups['body'].Value
$url = [regex]::Match($master, '(?m)^[ \t]*url:[ \t]*(.+)$').Groups[1].Value.Trim()
$username = [regex]::Match($master, '(?m)^[ \t]*username:[ \t]*(.+)$').Groups[1].Value.Trim()
$password = [regex]::Match($master, '(?m)^[ \t]*password:[ \t]*(.+)$').Groups[1].Value.Trim()
$jdbc = [regex]::Match($url, 'jdbc:sqlserver://(?<host>[^:;]+):(?<port>\d+);databaseName=(?<db>[^;]+)')
$builder = [System.Data.SqlClient.SqlConnectionStringBuilder]::new()
$builder.DataSource = '{0},{1}' -f $jdbc.Groups['host'].Value, $jdbc.Groups['port'].Value
$builder.InitialCatalog = $jdbc.Groups['db'].Value
$builder.UserID = $username
$builder.Password = $password
$builder.Encrypt = $false
$builder.TrustServerCertificate = $true
$builder.ApplicationIntent = [System.Data.SqlClient.ApplicationIntent]::ReadOnly
$connection = [System.Data.SqlClient.SqlConnection]::new($builder.ConnectionString)
try {
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = @'
select warehouse_id,
the_floor,
the_height,
count(*) as cargo_space_count
from dbo.bas_cargo_space
where warehouse_id in ('01', '02')
group by warehouse_id, the_floor, the_height
order by warehouse_id, the_floor, the_height;
'@
$table = [System.Data.DataTable]::new()
$adapter = [System.Data.SqlClient.SqlDataAdapter]::new($command)
[void] $adapter.Fill($table)
$table | Format-Table -AutoSize
} finally {
$connection.Dispose()
}
Expected before first execution: 340 rows total, all currently NULL; 136 rows on floors 1/5 and 204 rows on floors 2/3/4.
- Step 6: Execute the migration against
FTC25009-WMSwithout printing credentials
Run this PowerShell block from the repository root:
$config = Get-Content -Raw 'ruoyi-admin/src/main/resources/application-druid.yml'
$master = [regex]::Match($config, '(?ms)^[ \t]*master:[ \t]*\r?\n(?<body>.*?)(?=^[ \t]*slave:)').Groups['body'].Value
$url = [regex]::Match($master, '(?m)^[ \t]*url:[ \t]*(.+)$').Groups[1].Value.Trim()
$username = [regex]::Match($master, '(?m)^[ \t]*username:[ \t]*(.+)$').Groups[1].Value.Trim()
$password = [regex]::Match($master, '(?m)^[ \t]*password:[ \t]*(.+)$').Groups[1].Value.Trim()
$jdbc = [regex]::Match($url, 'jdbc:sqlserver://(?<host>[^:;]+):(?<port>\d+);databaseName=(?<db>[^;]+)')
$builder = [System.Data.SqlClient.SqlConnectionStringBuilder]::new()
$builder.DataSource = '{0},{1}' -f $jdbc.Groups['host'].Value, $jdbc.Groups['port'].Value
$builder.InitialCatalog = $jdbc.Groups['db'].Value
$builder.UserID = $username
$builder.Password = $password
$builder.Encrypt = $false
$builder.TrustServerCertificate = $true
$connection = [System.Data.SqlClient.SqlConnection]::new($builder.ConnectionString)
try {
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandTimeout = 60
$command.CommandText = Get-Content -Raw 'sql/20260710_cargo_space_height.sql'
[void] $command.ExecuteNonQuery()
} finally {
$connection.Dispose()
}
Expected: no exception. Any count or invariant mismatch raises SQL error 51001-51005 and rolls back.
- Step 7: Query and record the live post-migration distribution
Run this read-only PowerShell block:
$config = Get-Content -Raw 'ruoyi-admin/src/main/resources/application-druid.yml'
$master = [regex]::Match($config, '(?ms)^[ \t]*master:[ \t]*\r?\n(?<body>.*?)(?=^[ \t]*slave:)').Groups['body'].Value
$url = [regex]::Match($master, '(?m)^[ \t]*url:[ \t]*(.+)$').Groups[1].Value.Trim()
$username = [regex]::Match($master, '(?m)^[ \t]*username:[ \t]*(.+)$').Groups[1].Value.Trim()
$password = [regex]::Match($master, '(?m)^[ \t]*password:[ \t]*(.+)$').Groups[1].Value.Trim()
$jdbc = [regex]::Match($url, 'jdbc:sqlserver://(?<host>[^:;]+):(?<port>\d+);databaseName=(?<db>[^;]+)')
$builder = [System.Data.SqlClient.SqlConnectionStringBuilder]::new()
$builder.DataSource = '{0},{1}' -f $jdbc.Groups['host'].Value, $jdbc.Groups['port'].Value
$builder.InitialCatalog = $jdbc.Groups['db'].Value
$builder.UserID = $username
$builder.Password = $password
$builder.Encrypt = $false
$builder.TrustServerCertificate = $true
$builder.ApplicationIntent = [System.Data.SqlClient.ApplicationIntent]::ReadOnly
$connection = [System.Data.SqlClient.SqlConnection]::new($builder.ConnectionString)
try {
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = @'
select warehouse_id,
the_floor,
the_height,
count(*) as cargo_space_count
from dbo.bas_cargo_space
where warehouse_id in ('01', '02')
group by warehouse_id, the_floor, the_height
order by warehouse_id, the_floor, the_height;
'@
$table = [System.Data.DataTable]::new()
$adapter = [System.Data.SqlClient.SqlDataAdapter]::new($command)
[void] $adapter.Fill($table)
$table | Format-Table -AutoSize
} finally {
$connection.Dispose()
}
Expected:
- Warehouse 01: floors 1 and 5 each have 48 rows at height 2; floors 2, 3, and 4 each have 48 rows at height 1.
- Warehouse 02: floors 1 and 5 each have 20 rows at height 2; floors 2, 3, and 4 each have 20 rows at height 1.
- Totals: 136 height-2 rows, 204 height-1 rows, 0 null/other rows.
Task 6: Full Verification and Completion Audit
Files:
-
Verify all files listed above.
-
Do not stage unrelated worktree changes.
-
Step 1: Run focused tests for every new rule
Run:
mvn -pl ruoyi-admin -Dtest=WcsInboundLocationRequestTest,ExternalApiControllerWcsCodeTest,PdaServiceImplTest,PdaMapperSqlTest,MydoJobServiceImplTest,BasCargoSpaceMapperSqlTest,CargoSpaceHeightMigrationSqlTest test
Expected: PASS, 0 failures and 0 errors.
- Step 2: Run the complete backend test suite
Run:
mvn -pl ruoyi-admin test
Expected: BUILD SUCCESS, 0 failures and 0 errors.
- Step 3: Compile/package all required backend modules
Run:
mvn -pl ruoyi-admin -am package -DskipTests
Expected: BUILD SUCCESS for the reactor.
- Step 4: Validate XML and diff hygiene
Run:
git diff --check
git status --short
git diff -- ruoyi-admin/src/main/java/com/ruoyi/api/domain/WcsInboundLocationRequest.java ruoyi-admin/src/main/java/com/ruoyi/api/controller/ExternalApiController.java ruoyi-admin/src/main/java/com/ruoyi/pda/service/PdaService.java ruoyi-admin/src/main/java/com/ruoyi/pda/service/impl/PdaServiceImpl.java ruoyi-admin/src/main/resources/mapper/pda/PdaMapper.xml ruoyi-admin/src/main/java/com/ruoyi/in/job/service/impl/MydoJobServiceImpl.java ruoyi-admin/src/main/resources/mapper/bas/BasCargoSpaceMapper.xml sql/20260710_cargo_space_height.sql
Expected: no whitespace errors. Review the diff to confirm existing inventory/PDA changes remain intact and only task-specific hunks were added.
- Step 5: Audit each requirement against authoritative evidence
Confirm all of the following:
- Live DB query proves 01/02 floors 1/5 are
the_height=2and all other 01/02 floors arethe_height=1. - Controller tests prove missing/invalid height rejection, persistence, same-height idempotency, and different-height conflict.
- Normal mapper SQL proves height 2 cannot select height 1 and height 1 ranks height 1 before height 2.
PUT_OCCUPIEDtests and SQL prove reallocation uses the persisted job height with the same rules.- Full test and package commands both have fresh successful output.
Do not claim completion or mark the goal complete until all five evidence items are present.