feat: enforce dense lane outermost order

This commit is contained in:
zgw
2026-07-16 10:14:19 +08:00
parent 9158a5fa54
commit 66830dc649
9 changed files with 612 additions and 2 deletions

View File

@@ -0,0 +1,51 @@
package com.ruoyi.out.dense.domain;
/**
* 密集库通道货位及其几何信息。
*/
public class DenseLanePosition
{
private String cargoSpaceId;
private String cargoSpaceName;
private String warehouseId;
private String groupCoding;
private String cargoArea;
private Long thePlatoon;
private Long theColumn;
private Long theFloor;
private String status;
private String locked;
private String trayCode;
private String directionType;
private Long minColumn;
private Long maxColumn;
public String getCargoSpaceId() { return cargoSpaceId; }
public void setCargoSpaceId(String cargoSpaceId) { this.cargoSpaceId = cargoSpaceId; }
public String getCargoSpaceName() { return cargoSpaceName; }
public void setCargoSpaceName(String cargoSpaceName) { this.cargoSpaceName = cargoSpaceName; }
public String getWarehouseId() { return warehouseId; }
public void setWarehouseId(String warehouseId) { this.warehouseId = warehouseId; }
public String getGroupCoding() { return groupCoding; }
public void setGroupCoding(String groupCoding) { this.groupCoding = groupCoding; }
public String getCargoArea() { return cargoArea; }
public void setCargoArea(String cargoArea) { this.cargoArea = cargoArea; }
public Long getThePlatoon() { return thePlatoon; }
public void setThePlatoon(Long thePlatoon) { this.thePlatoon = thePlatoon; }
public Long getTheColumn() { return theColumn; }
public void setTheColumn(Long theColumn) { this.theColumn = theColumn; }
public Long getTheFloor() { return theFloor; }
public void setTheFloor(Long theFloor) { this.theFloor = theFloor; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getLocked() { return locked; }
public void setLocked(String locked) { this.locked = locked; }
public String getTrayCode() { return trayCode; }
public void setTrayCode(String trayCode) { this.trayCode = trayCode; }
public String getDirectionType() { return directionType; }
public void setDirectionType(String directionType) { this.directionType = directionType; }
public Long getMinColumn() { return minColumn; }
public void setMinColumn(Long minColumn) { this.minColumn = minColumn; }
public Long getMaxColumn() { return maxColumn; }
public void setMaxColumn(Long maxColumn) { this.maxColumn = maxColumn; }
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.out.dense.domain;
import com.ruoyi.bas.cargospace.domain.BasCargoSpace;
/**
* 密集库移库源托盘与候选目标货位。
*/
public class DenseRelocationCandidate
{
private DenseLanePosition source;
private BasCargoSpace target;
public DenseLanePosition getSource() { return source; }
public void setSource(DenseLanePosition source) { this.source = source; }
public BasCargoSpace getTarget() { return target; }
public void setTarget(BasCargoSpace target) { this.target = target; }
}

View File

@@ -0,0 +1,42 @@
package com.ruoyi.out.dense.domain;
/**
* 同层异通道移库目标查询条件。
*/
public class RelocationTargetQuery
{
private String warehouseId;
private Long theFloor;
private String sourceGroupCoding;
private String targetGroupCoding;
private String trayCode;
private String cargoSpaceType;
private Long height;
private String storageMode;
private String projectNo;
private String productId;
private String productName;
public String getWarehouseId() { return warehouseId; }
public void setWarehouseId(String warehouseId) { this.warehouseId = warehouseId; }
public Long getTheFloor() { return theFloor; }
public void setTheFloor(Long theFloor) { this.theFloor = theFloor; }
public String getSourceGroupCoding() { return sourceGroupCoding; }
public void setSourceGroupCoding(String sourceGroupCoding) { this.sourceGroupCoding = sourceGroupCoding; }
public String getTargetGroupCoding() { return targetGroupCoding; }
public void setTargetGroupCoding(String targetGroupCoding) { this.targetGroupCoding = targetGroupCoding; }
public String getTrayCode() { return trayCode; }
public void setTrayCode(String trayCode) { this.trayCode = trayCode; }
public String getCargoSpaceType() { return cargoSpaceType; }
public void setCargoSpaceType(String cargoSpaceType) { this.cargoSpaceType = cargoSpaceType; }
public Long getHeight() { return height; }
public void setHeight(Long height) { this.height = height; }
public String getStorageMode() { return storageMode; }
public void setStorageMode(String storageMode) { this.storageMode = storageMode; }
public String getProjectNo() { return projectNo; }
public void setProjectNo(String projectNo) { this.projectNo = projectNo; }
public String getProductId() { return productId; }
public void setProductId(String productId) { this.productId = productId; }
public String getProductName() { return productName; }
public void setProductName(String productName) { this.productName = productName; }
}

View File

@@ -1,5 +1,9 @@
package com.ruoyi.out.dense.mapper;
import java.util.List;
import com.ruoyi.bas.cargospace.domain.BasCargoSpace;
import com.ruoyi.out.dense.domain.DenseLanePosition;
import com.ruoyi.out.dense.domain.RelocationTargetQuery;
import org.apache.ibatis.annotations.Param;
/**
@@ -8,4 +12,12 @@ import org.apache.ibatis.annotations.Param;
public interface DenseLaneMapper
{
Integer isCurrentOutermost(@Param("cargoSpaceId") String cargoSpaceId);
DenseLanePosition selectPosition(@Param("cargoSpaceId") String cargoSpaceId);
List<DenseLanePosition> selectOccupiedLanePositions(DenseLanePosition source);
List<DenseLanePosition> selectBlockersForUpdate(@Param("cargoSpaceId") String cargoSpaceId);
BasCargoSpace selectRelocationTargetForUpdate(RelocationTargetQuery query);
}

View File

@@ -1,7 +1,11 @@
package com.ruoyi.out.dense.service;
import java.util.List;
import com.ruoyi.bas.cargospace.domain.BasCargoSpace;
import com.ruoyi.in.job.domain.MydoJob;
import com.ruoyi.out.dense.domain.DenseLanePosition;
import com.ruoyi.out.dense.domain.DispatchEligibility;
import com.ruoyi.out.dense.domain.RelocationTargetQuery;
/**
* 密集库通道服务。
@@ -9,4 +13,10 @@ import com.ruoyi.out.dense.domain.DispatchEligibility;
public interface IDenseLaneService
{
DispatchEligibility checkDispatchEligibility(MydoJob job);
List<DenseLanePosition> orderOutermostFirst(List<DenseLanePosition> positions);
List<DenseLanePosition> selectBlockersForUpdate(String cargoSpaceId);
BasCargoSpace selectRelocationTargetForUpdate(RelocationTargetQuery query);
}

View File

@@ -1,8 +1,15 @@
package com.ruoyi.out.dense.service.impl;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;
import java.util.List;
import com.ruoyi.bas.cargospace.domain.BasCargoSpace;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.in.job.domain.MydoJob;
import com.ruoyi.out.dense.domain.DenseLanePosition;
import com.ruoyi.out.dense.domain.DispatchEligibility;
import com.ruoyi.out.dense.domain.RelocationTargetQuery;
import com.ruoyi.out.dense.mapper.DenseLaneMapper;
import com.ruoyi.out.dense.service.IDenseLaneService;
import org.springframework.stereotype.Service;
@@ -32,10 +39,66 @@ public class DenseLaneServiceImpl implements IDenseLaneService
if (!StringUtils.hasText(job.getCargoSpaceId())) {
return DispatchEligibility.blocked("03仓出库作业未分配源货位");
}
Integer outermost = denseLaneMapper.isCurrentOutermost(job.getCargoSpaceId());
if (Integer.valueOf(1).equals(outermost)) {
DenseLanePosition source = denseLaneMapper.selectPosition(job.getCargoSpaceId());
if (source == null) {
return DispatchEligibility.blocked("出库作业源货位不存在");
}
if (!StringUtils.hasText(source.getGroupCoding())) {
return DispatchEligibility.allowed();
}
List<DenseLanePosition> ordered = orderOutermostFirst(
denseLaneMapper.selectOccupiedLanePositions(source));
if (ordered.isEmpty() || job.getCargoSpaceId().equals(ordered.get(0).getCargoSpaceId())) {
return DispatchEligibility.allowed();
}
return DispatchEligibility.blocked("当前货位不是密集库通道最外侧货位");
}
@Override
public List<DenseLanePosition> orderOutermostFirst(List<DenseLanePosition> positions)
{
if (positions == null || positions.isEmpty()) {
return Collections.emptyList();
}
List<DenseLanePosition> ordered = new ArrayList<>(positions);
String direction = StringUtils.defaultIfEmpty(ordered.get(0).getDirectionType(), "1");
Comparator<DenseLanePosition> comparator;
if ("2".equals(direction)) {
comparator = Comparator.comparing(DenseLanePosition::getTheColumn,
Comparator.nullsLast(Long::compareTo));
} else if ("3".equals(direction)) {
comparator = Comparator
.comparingLong(this::distanceToNearestBoundary)
.thenComparing(DenseLanePosition::getTheColumn,
Comparator.nullsLast(Long::compareTo));
} else {
comparator = Comparator.comparing(DenseLanePosition::getTheColumn,
Comparator.nullsLast(Long::compareTo)).reversed();
}
ordered.sort(comparator);
return ordered;
}
@Override
public List<DenseLanePosition> selectBlockersForUpdate(String cargoSpaceId)
{
List<DenseLanePosition> blockers = denseLaneMapper.selectBlockersForUpdate(cargoSpaceId);
return blockers == null ? Collections.emptyList() : blockers;
}
@Override
public BasCargoSpace selectRelocationTargetForUpdate(RelocationTargetQuery query)
{
return denseLaneMapper.selectRelocationTargetForUpdate(query);
}
private long distanceToNearestBoundary(DenseLanePosition position)
{
if (position.getTheColumn() == null || position.getMinColumn() == null
|| position.getMaxColumn() == null) {
return Long.MAX_VALUE;
}
return Math.min(position.getTheColumn() - position.getMinColumn(),
position.getMaxColumn() - position.getTheColumn());
}
}

View File

@@ -4,6 +4,221 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.out.dense.mapper.DenseLaneMapper">
<resultMap id="DenseLanePositionResult" type="com.ruoyi.out.dense.domain.DenseLanePosition">
<id property="cargoSpaceId" column="cargo_space_id" />
<result property="cargoSpaceName" column="cargo_space_name" />
<result property="warehouseId" column="warehouse_id" />
<result property="groupCoding" column="group_coding" />
<result property="cargoArea" column="this_cargo_area" />
<result property="thePlatoon" column="the_platoon" />
<result property="theColumn" column="the_column" />
<result property="theFloor" column="the_floor" />
<result property="status" column="status" />
<result property="locked" column="locked" />
<result property="trayCode" column="tray_code" />
<result property="directionType" column="direction_type" />
<result property="minColumn" column="min_column" />
<result property="maxColumn" column="max_column" />
</resultMap>
<select id="selectPosition" resultMap="DenseLanePositionResult">
select source_space.cargo_space_id,
source_space.cargo_space_name,
source_space.warehouse_id,
source_space.group_coding,
source_space.this_cargo_area,
source_space.the_platoon,
source_space.the_column,
source_space.the_floor,
source_space.status,
source_space.locked,
source_space.tray_code,
isnull(area_config.direction_type, '1') as direction_type,
boundary.min_column,
boundary.max_column
from bas_cargo_space source_space
left join bas_cargo_area_config area_config
on area_config.warehouse_id = source_space.warehouse_id
and area_config.cargo_area_code = source_space.this_cargo_area
and area_config.is_active = '1'
outer apply
(
select min(the_column) as min_column,
max(the_column) as max_column
from bas_cargo_space lane_space
where lane_space.warehouse_id = source_space.warehouse_id
and lane_space.group_coding = source_space.group_coding
and lane_space.the_floor = source_space.the_floor
) boundary
where source_space.cargo_space_id = #{cargoSpaceId}
</select>
<select id="selectOccupiedLanePositions" resultMap="DenseLanePositionResult">
select occupied.cargo_space_id,
occupied.cargo_space_name,
occupied.warehouse_id,
occupied.group_coding,
occupied.this_cargo_area,
occupied.the_platoon,
occupied.the_column,
occupied.the_floor,
occupied.status,
occupied.locked,
occupied.tray_code,
isnull(area_config.direction_type, '1') as direction_type,
boundary.min_column,
boundary.max_column
from bas_cargo_space occupied
left join bas_cargo_area_config area_config
on area_config.warehouse_id = occupied.warehouse_id
and area_config.cargo_area_code = occupied.this_cargo_area
and area_config.is_active = '1'
cross apply
(
select min(lane_space.the_column) as min_column,
max(lane_space.the_column) as max_column
from bas_cargo_space lane_space
where lane_space.warehouse_id = occupied.warehouse_id
and lane_space.group_coding = occupied.group_coding
and lane_space.the_floor = occupied.the_floor
) boundary
where occupied.warehouse_id = #{warehouseId}
and occupied.group_coding = #{groupCoding}
and occupied.the_floor = #{theFloor}
and occupied.status in ('2', '3', '4')
and occupied.tray_code is not null
and ltrim(rtrim(occupied.tray_code)) != ''
</select>
<select id="selectBlockersForUpdate" resultMap="DenseLanePositionResult">
;with source_position as
(
select source_space.warehouse_id,
source_space.group_coding,
source_space.the_floor,
source_space.the_column,
isnull(area_config.direction_type, '1') as direction_type,
boundary.min_column,
boundary.max_column,
case when source_space.the_column - boundary.min_column
&lt;= boundary.max_column - source_space.the_column
then source_space.the_column - boundary.min_column
else boundary.max_column - source_space.the_column end as distance_to_edge
from bas_cargo_space source_space with (UPDLOCK,HOLDLOCK)
left join bas_cargo_area_config area_config
on area_config.warehouse_id = source_space.warehouse_id
and area_config.cargo_area_code = source_space.this_cargo_area
and area_config.is_active = '1'
cross apply
(
select min(lane_space.the_column) as min_column,
max(lane_space.the_column) as max_column
from bas_cargo_space lane_space with (UPDLOCK,HOLDLOCK)
where lane_space.warehouse_id = source_space.warehouse_id
and lane_space.group_coding = source_space.group_coding
and lane_space.the_floor = source_space.the_floor
) boundary
where source_space.cargo_space_id = #{cargoSpaceId}
and source_space.warehouse_id = '03'
and source_space.group_coding is not null
), lane_positions as
(
select occupied.cargo_space_id,
occupied.cargo_space_name,
occupied.warehouse_id,
occupied.group_coding,
occupied.this_cargo_area,
occupied.the_platoon,
occupied.the_column,
occupied.the_floor,
occupied.status,
occupied.locked,
occupied.tray_code,
source.direction_type,
source.min_column,
source.max_column,
case when occupied.the_column - source.min_column
&lt;= source.max_column - occupied.the_column
then occupied.the_column - source.min_column
else source.max_column - occupied.the_column end as distance_to_edge,
source.the_column as source_column,
source.distance_to_edge as source_distance
from bas_cargo_space occupied with (UPDLOCK,HOLDLOCK)
inner join source_position source
on source.warehouse_id = occupied.warehouse_id
and source.group_coding = occupied.group_coding
and source.the_floor = occupied.the_floor
where occupied.status in ('2', '3', '4')
and occupied.tray_code is not null
and ltrim(rtrim(occupied.tray_code)) != ''
and not exists
(
select 1
from mydo_job active_job with (UPDLOCK,HOLDLOCK)
where active_job.in_or_out &gt;= 20
and active_job.status in ('1', '2', '3')
and (active_job.cargo_space_id = occupied.cargo_space_id
or active_job.tray_code = occupied.tray_code)
)
)
select cargo_space_id, cargo_space_name, warehouse_id, group_coding,
this_cargo_area, the_platoon, the_column, the_floor, status,
locked, tray_code, direction_type, min_column, max_column
from lane_positions
where (direction_type = '1' and the_column &gt; source_column)
or (direction_type = '2' and the_column &lt; source_column)
or (direction_type = '3' and
(distance_to_edge &lt; source_distance
or (distance_to_edge = source_distance and the_column &lt; source_column)))
order by case when direction_type = '1' then the_column end desc,
case when direction_type = '2' then the_column end asc,
case when direction_type = '3' then distance_to_edge end asc,
the_column asc
</select>
<select id="selectRelocationTargetForUpdate"
parameterType="com.ruoyi.out.dense.domain.RelocationTargetQuery"
resultType="com.ruoyi.bas.cargospace.domain.BasCargoSpace">
select top 1 target_space.*
from bas_cargo_space target_space with (UPDLOCK,HOLDLOCK,ROWLOCK)
left join bas_cargo_area_config area_config
on area_config.warehouse_id = target_space.warehouse_id
and area_config.cargo_area_code = target_space.this_cargo_area
and area_config.is_active = '1'
where target_space.warehouse_id = #{warehouseId}
and target_space.the_floor = #{theFloor}
and target_space.group_coding != #{sourceGroupCoding}
and target_space.group_coding is not null
and target_space.status = '1'
and isnull(target_space.locked, 'N') = 'N'
and target_space.tray_code is null
<if test="targetGroupCoding != null and targetGroupCoding != ''">
and target_space.group_coding = #{targetGroupCoding}
</if>
<if test="cargoSpaceType != null and cargoSpaceType != ''">
and target_space.the_type = #{cargoSpaceType}
</if>
<if test="height != null">
and target_space.the_height &gt;= #{height}
</if>
and not exists
(
select 1
from mydo_job active_job with (UPDLOCK,HOLDLOCK)
where active_job.status in ('1', '2', '3')
and (active_job.cargo_space_id = target_space.cargo_space_id
or active_job.target_cargo_space_id = target_space.cargo_space_id)
)
order by case when isnull(area_config.direction_type, '1') = '1'
then target_space.the_column end asc,
case when area_config.direction_type = '2'
then target_space.the_column end desc,
case when area_config.direction_type = '3'
then abs(target_space.the_column - 1) end asc,
target_space.group_coding asc,
target_space.cargo_space_id asc
</select>
<select id="isCurrentOutermost" resultType="java.lang.Integer">
select case
when source_space.warehouse_id != '03'

View File

@@ -0,0 +1,84 @@
package com.ruoyi.out.dense.mapper;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class DenseLaneMapperSqlTest
{
@Test
void occupiedLaneQueryUsesAllReservedStatusesAndStableGeometry() throws Exception
{
String xml = Files.readString(resolveMapperXml());
String sql = section(xml, "<select id=\"selectOccupiedLanePositions\"", "</select>");
assertThat(sql)
.contains("status in ('2', '3', '4')")
.contains("tray_code is not null")
.contains("from bas_cargo_space lane_space")
.contains("min(lane_space.the_column) as min_column")
.contains("max(lane_space.the_column) as max_column")
.contains("direction_type");
}
@Test
void blockerQueryLocksRowsOrdersOuterToInnerAndSkipsActiveJobs() throws Exception
{
String xml = Files.readString(resolveMapperXml());
String sql = section(xml, "<select id=\"selectBlockersForUpdate\"", "</select>");
assertThat(sql)
.containsIgnoringCase("UPDLOCK")
.containsIgnoringCase("HOLDLOCK")
.contains("status in ('2', '3', '4')")
.contains("not exists")
.contains("from mydo_job active_job")
.contains("active_job.in_or_out &gt;= 20")
.contains("active_job.status in ('1', '2', '3')")
.contains("case when direction_type = '1' then the_column end desc")
.contains("case when direction_type = '2' then the_column end asc")
.contains("then distance_to_edge end asc")
.contains("the_column asc");
}
@Test
void relocationTargetIsLockedSameFloorDifferentLaneAndUnreserved() throws Exception
{
String xml = Files.readString(resolveMapperXml());
String sql = section(xml, "<select id=\"selectRelocationTargetForUpdate\"", "</select>");
assertThat(sql)
.containsIgnoringCase("UPDLOCK")
.containsIgnoringCase("HOLDLOCK")
.contains("warehouse_id = #{warehouseId}")
.contains("the_floor = #{theFloor}")
.contains("group_coding != #{sourceGroupCoding}")
.contains("status = '1'")
.contains("isnull(target_space.locked, 'N') = 'N'")
.contains("target_space.tray_code is null")
.contains("target_cargo_space_id")
.contains("not exists");
}
private Path resolveMapperXml()
{
return Stream.of(
Path.of("src/main/resources/mapper/out/DenseLaneMapper.xml"),
Path.of("ruoyi-admin/src/main/resources/mapper/out/DenseLaneMapper.xml"))
.filter(Files::exists)
.findFirst()
.orElseThrow();
}
private String section(String text, String startMarker, String endMarker)
{
int start = text.indexOf(startMarker);
assertThat(start).isGreaterThanOrEqualTo(0);
int end = text.indexOf(endMarker, start);
assertThat(end).isGreaterThan(start);
return text.substring(start, end);
}
}

View File

@@ -0,0 +1,116 @@
package com.ruoyi.out.dense.service.impl;
import java.util.List;
import com.ruoyi.in.job.domain.MydoJob;
import com.ruoyi.out.dense.domain.DenseLanePosition;
import com.ruoyi.out.dense.mapper.DenseLaneMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class DenseLaneServiceImplTest
{
private DenseLaneMapper mapper;
private DenseLaneServiceImpl service;
@BeforeEach
void setUp()
{
mapper = mock(DenseLaneMapper.class);
service = new DenseLaneServiceImpl(mapper);
}
@Test
void directionOneUsesMaximumOccupiedColumnAsOutermost()
{
DenseLanePosition source = position("CS-8", 8L, "1", 1L, 10L);
DenseLanePosition outermost = position("CS-10", 10L, "1", 1L, 10L);
when(mapper.selectPosition("CS-8")).thenReturn(source);
when(mapper.selectPosition("CS-10")).thenReturn(outermost);
when(mapper.selectOccupiedLanePositions(source)).thenReturn(List.of(
position("CS-2", 2L, "1", 1L, 10L),
source,
outermost));
when(mapper.selectOccupiedLanePositions(outermost)).thenReturn(List.of(
position("CS-2", 2L, "1", 1L, 10L),
source,
outermost));
assertThat(service.checkDispatchEligibility(job("03", "CS-8")).isEligible()).isFalse();
assertThat(service.checkDispatchEligibility(job("03", "CS-10")).isEligible()).isTrue();
}
@Test
void directionTwoUsesMinimumOccupiedColumnAsOutermost()
{
DenseLanePosition source = position("CS-2", 2L, "2", 1L, 10L);
when(mapper.selectPosition("CS-2")).thenReturn(source);
when(mapper.selectOccupiedLanePositions(source)).thenReturn(List.of(
position("CS-2", 2L, "2", 1L, 10L),
position("CS-7", 7L, "2", 1L, 10L)));
assertThat(service.checkDispatchEligibility(job("03", "CS-2")).isEligible()).isTrue();
}
@Test
void directionThreeUsesNearestBoundaryThenSmallerColumn()
{
List<DenseLanePosition> ordered = service.orderOutermostFirst(List.of(
position("CS-6", 6L, "3", 1L, 10L),
position("CS-10", 10L, "3", 1L, 10L),
position("CS-1", 1L, "3", 1L, 10L),
position("CS-5", 5L, "3", 1L, 10L)));
assertThat(ordered).extracting(DenseLanePosition::getCargoSpaceId)
.containsExactly("CS-1", "CS-10", "CS-5", "CS-6");
}
@Test
void nonWarehouse03BypassesDenseLookup()
{
assertThat(service.checkDispatchEligibility(job("02", "CS-1")).isEligible()).isTrue();
verify(mapper, never()).selectPosition("CS-1");
}
@Test
void blockerQueryReturnsOnlyUnassignedOuterTrays()
{
List<DenseLanePosition> blockers = List.of(
position("CS-9", 9L, "1", 1L, 10L),
position("CS-8", 8L, "1", 1L, 10L));
when(mapper.selectBlockersForUpdate("CS-7")).thenReturn(blockers);
assertThat(service.selectBlockersForUpdate("CS-7"))
.extracting(DenseLanePosition::getCargoSpaceId)
.containsExactly("CS-9", "CS-8");
}
private MydoJob job(String warehouseId, String cargoSpaceId)
{
MydoJob job = new MydoJob();
job.setWarehouseId(warehouseId);
job.setCargoSpaceId(cargoSpaceId);
return job;
}
private DenseLanePosition position(String cargoSpaceId, Long column, String direction,
Long minColumn, Long maxColumn)
{
DenseLanePosition position = new DenseLanePosition();
position.setCargoSpaceId(cargoSpaceId);
position.setWarehouseId("03");
position.setGroupCoding("G-01-01");
position.setTheFloor(1L);
position.setTheColumn(column);
position.setDirectionType(direction);
position.setMinColumn(minColumn);
position.setMaxColumn(maxColumn);
position.setTrayCode("T-" + cargoSpaceId);
return position;
}
}