feat: enforce dense lane outermost order
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
<= 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
|
||||
<= 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 >= 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 > source_column)
|
||||
or (direction_type = '2' and the_column < source_column)
|
||||
or (direction_type = '3' and
|
||||
(distance_to_edge < source_distance
|
||||
or (distance_to_edge = source_distance and the_column < 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 >= #{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'
|
||||
|
||||
@@ -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 >= 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user