test: verify dense outbound dispatch workflows
This commit is contained in:
@@ -173,11 +173,14 @@ public class DenseLaneOutboundServiceImpl implements IDenseLaneOutboundService
|
||||
position.getCargoSpaceId(), position.getTrayCode());
|
||||
position.setStockDetails(details == null ? new ArrayList<>() : details);
|
||||
});
|
||||
String[] trayCodes = selected.stream().map(DenseLanePosition::getTrayCode).toArray(String[]::new);
|
||||
BigDecimal total = relocationJobMapper.sumTrayStockQuantity(trayCodes);
|
||||
BigDecimal total = selected.stream()
|
||||
.flatMap(position -> position.getStockDetails().stream())
|
||||
.map(MydoJobDetail::getStockQuantity)
|
||||
.filter(quantity -> quantity != null)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
DenseLaneOutboundPreview preview = new DenseLaneOutboundPreview();
|
||||
preview.setTrays(selected);
|
||||
preview.setTotalQuantity(total == null ? BigDecimal.ZERO : total);
|
||||
preview.setTotalQuantity(total);
|
||||
preview.setPreviewToken(previewToken(request, selected));
|
||||
return preview;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.ruoyi.out.relocation.mapper;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
@@ -21,8 +20,6 @@ public interface RelocationJobMapper
|
||||
|
||||
List<DenseLanePosition> selectRelocationSourcesForUpdate(RelocationGenerateRequest request);
|
||||
|
||||
BigDecimal sumTrayStockQuantity(@Param("trayCodes") String[] trayCodes);
|
||||
|
||||
int reserveTarget(Map<String, Object> params);
|
||||
|
||||
int reserveSource(Map<String, Object> params);
|
||||
|
||||
@@ -82,9 +82,11 @@ public class RelocationJobServiceImpl implements IRelocationJobService
|
||||
.stream().limit(request.getTrayCount()).collect(Collectors.toList());
|
||||
RelocationPreview preview = new RelocationPreview();
|
||||
preview.setTrays(selected);
|
||||
String[] trayCodes = selected.stream().map(DenseLanePosition::getTrayCode).toArray(String[]::new);
|
||||
BigDecimal total = mapper.sumTrayStockQuantity(trayCodes);
|
||||
preview.setTotalQuantity(total == null ? BigDecimal.ZERO : total);
|
||||
BigDecimal total = selected.stream()
|
||||
.map(DenseLanePosition::getStockQuantity)
|
||||
.filter(quantity -> quantity != null)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
preview.setTotalQuantity(total);
|
||||
return preview;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="maxColumn" column="max_column" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="DenseLaneOptionResult" type="com.ruoyi.out.dense.domain.DenseLaneOption">
|
||||
<id property="groupCoding" column="group_coding" />
|
||||
<result property="cargoArea" column="cargo_area" />
|
||||
<result property="thePlatoon" column="the_platoon" />
|
||||
<result property="theFloor" column="the_floor" />
|
||||
<result property="directionType" column="direction_type" />
|
||||
<result property="fullTrayCount" column="full_tray_count" />
|
||||
<result property="emptyLocationCount" column="empty_location_count" />
|
||||
<result property="totalQuantity" column="total_quantity" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="DenseLaneSummaryResult" type="com.ruoyi.out.dense.domain.DenseLaneSummary">
|
||||
<id property="groupCoding" column="group_coding" />
|
||||
<result property="cargoArea" column="cargo_area" />
|
||||
<result property="thePlatoon" column="the_platoon" />
|
||||
<result property="theFloor" column="the_floor" />
|
||||
<result property="directionType" column="direction_type" />
|
||||
<result property="fullTrayCount" column="full_tray_count" />
|
||||
<result property="availableTrayCount" column="available_tray_count" />
|
||||
<result property="totalQuantity" column="total_quantity" />
|
||||
<result property="outermostTrayCode" column="outermost_tray_code" />
|
||||
<result property="outermostCargoSpaceId" column="outermost_cargo_space_id" />
|
||||
</resultMap>
|
||||
|
||||
<select id="selectPosition" resultMap="DenseLanePositionResult">
|
||||
select source_space.cargo_space_id,
|
||||
source_space.cargo_space_name,
|
||||
@@ -178,7 +202,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<select id="selectRelocationTargetForUpdate"
|
||||
parameterType="com.ruoyi.out.dense.domain.RelocationTargetQuery"
|
||||
resultType="com.ruoyi.bas.cargospace.domain.BasCargoSpace">
|
||||
resultMap="com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper.BasCargoSpaceResult">
|
||||
select top 1 target_space.*
|
||||
from bas_cargo_space target_space with (UPDLOCK,HOLDLOCK,ROWLOCK)
|
||||
left join bas_cargo_area_config area_config
|
||||
@@ -235,7 +259,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
target_space.cargo_space_id asc
|
||||
</select>
|
||||
|
||||
<select id="selectLaneOptions" resultType="com.ruoyi.out.dense.domain.DenseLaneOption">
|
||||
<select id="selectLaneOptions" resultMap="DenseLaneOptionResult">
|
||||
;with reserved_targets as
|
||||
(
|
||||
select distinct target_cargo_space_id as cargo_space_id
|
||||
@@ -282,7 +306,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
order by lane.group_coding
|
||||
</select>
|
||||
|
||||
<select id="selectLaneSummaries" resultType="com.ruoyi.out.dense.domain.DenseLaneSummary">
|
||||
<select id="selectLaneSummaries" resultMap="DenseLaneSummaryResult">
|
||||
;with lane_positions as
|
||||
(
|
||||
select cargo.cargo_space_id,
|
||||
|
||||
@@ -4,14 +4,49 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.out.relocation.mapper.RelocationJobMapper">
|
||||
|
||||
<select id="selectJobForUpdate" resultType="com.ruoyi.in.job.domain.MydoJob">
|
||||
<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="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" />
|
||||
<result property="stockQuantity" column="stock_quantity" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="StockDetailResult" type="com.ruoyi.in.jobdetail.domain.MydoJobDetail">
|
||||
<result property="stockId" column="stock_id" />
|
||||
<result property="lotNo" column="lot_no" />
|
||||
<result property="inDate" column="in_date" />
|
||||
<result property="stockinId" column="stockin_id" />
|
||||
<result property="stockinDetailId" column="stockin_detail_id" />
|
||||
<result property="productId" column="product_id" />
|
||||
<result property="productName" column="product_name" />
|
||||
<result property="stockQuantity" column="stock_quantity" />
|
||||
<result property="customerId" column="customer_id" />
|
||||
<result property="customerName" column="customer_name" />
|
||||
<result property="supplierId" column="supplier_id" />
|
||||
<result property="supplierName" column="supplier_name" />
|
||||
<result property="projectNo" column="project_no" />
|
||||
</resultMap>
|
||||
|
||||
<select id="selectJobForUpdate"
|
||||
resultMap="com.ruoyi.in.job.mapper.MydoJobMapper.MydoJobResult">
|
||||
select *
|
||||
from mydo_job with (UPDLOCK,HOLDLOCK)
|
||||
where job_id = #{jobId}
|
||||
</select>
|
||||
|
||||
<select id="selectStockDetailsForUpdate"
|
||||
resultType="com.ruoyi.in.jobdetail.domain.MydoJobDetail">
|
||||
resultMap="StockDetailResult">
|
||||
select detail.stock_id,
|
||||
detail.lot_no,
|
||||
convert(varchar(19), stock.in_date, 120) as in_date,
|
||||
@@ -22,8 +57,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
detail.quantity as stock_quantity,
|
||||
cast(null as nvarchar(255)) as customer_id,
|
||||
cast(null as nvarchar(255)) as customer_name,
|
||||
stock.suplier_id as supplier_id,
|
||||
stock.suplier_name as supplier_name,
|
||||
stock.supplier_id as supplier_id,
|
||||
stock.supplier_name as supplier_name,
|
||||
detail.project_no
|
||||
from bas_stock stock with (UPDLOCK,HOLDLOCK)
|
||||
inner join bas_stock_detail detail with (UPDLOCK,HOLDLOCK)
|
||||
@@ -35,7 +70,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</select>
|
||||
|
||||
<select id="selectRelocationSourcesForUpdate"
|
||||
resultType="com.ruoyi.out.dense.domain.DenseLanePosition">
|
||||
resultMap="DenseLanePositionResult">
|
||||
select cargo.cargo_space_id,
|
||||
cargo.cargo_space_name,
|
||||
cargo.warehouse_id,
|
||||
@@ -87,16 +122,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="sumTrayStockQuantity" resultType="java.math.BigDecimal">
|
||||
select isnull(sum(isnull(detail.quantity, 0)), 0)
|
||||
from bas_stock stock
|
||||
inner join bas_stock_detail detail on detail.stock_id = stock.stock_id
|
||||
where stock.tray_code in
|
||||
<foreach collection="trayCodes" item="trayCode" open="(" separator="," close=")">
|
||||
#{trayCode}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<update id="reserveTarget" parameterType="java.util.Map">
|
||||
update bas_cargo_space
|
||||
set status = '3', tray_code = #{trayCode}
|
||||
|
||||
@@ -51,6 +51,7 @@ class DenseLaneMapperSqlTest
|
||||
String sql = section(xml, "<select id=\"selectRelocationTargetForUpdate\"", "</select>");
|
||||
|
||||
assertThat(sql)
|
||||
.contains("resultMap=\"com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper.BasCargoSpaceResult\"")
|
||||
.containsIgnoringCase("UPDLOCK")
|
||||
.containsIgnoringCase("HOLDLOCK")
|
||||
.contains("warehouse_id = #{warehouseId}")
|
||||
@@ -82,6 +83,25 @@ class DenseLaneMapperSqlTest
|
||||
.doesNotContain("abs(target_space.the_column - 1)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void laneOptionsAndSummariesUseExplicitSnakeCaseResultMaps() throws Exception
|
||||
{
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String optionMap = section(xml, "<resultMap id=\"DenseLaneOptionResult\"", "</resultMap>");
|
||||
String summaryMap = section(xml, "<resultMap id=\"DenseLaneSummaryResult\"", "</resultMap>");
|
||||
String optionSelect = section(xml, "<select id=\"selectLaneOptions\"", "</select>");
|
||||
String summarySelect = section(xml, "<select id=\"selectLaneSummaries\"", "</select>");
|
||||
|
||||
assertThat(optionMap)
|
||||
.contains("property=\"groupCoding\" column=\"group_coding\"")
|
||||
.contains("property=\"emptyLocationCount\" column=\"empty_location_count\"");
|
||||
assertThat(summaryMap)
|
||||
.contains("property=\"groupCoding\" column=\"group_coding\"")
|
||||
.contains("property=\"outermostTrayCode\" column=\"outermost_tray_code\"");
|
||||
assertThat(optionSelect).contains("resultMap=\"DenseLaneOptionResult\"");
|
||||
assertThat(summarySelect).contains("resultMap=\"DenseLaneSummaryResult\"");
|
||||
}
|
||||
|
||||
private Path resolveMapperXml()
|
||||
{
|
||||
return Stream.of(
|
||||
|
||||
@@ -106,8 +106,10 @@ class DenseLaneOutboundServiceImplTest
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(positions);
|
||||
when(denseLaneService.orderOutermostFirst(positions))
|
||||
.thenReturn(List.of(positions.get(1), positions.get(0), positions.get(2)));
|
||||
when(relocationJobMapper.sumTrayStockQuantity(any(String[].class)))
|
||||
.thenReturn(new BigDecimal("25"));
|
||||
when(relocationJobMapper.selectStockDetailsForUpdate("CS-3", "A003"))
|
||||
.thenReturn(List.of(detail("STOCK-3", "D-3", "P-3", "15")));
|
||||
when(relocationJobMapper.selectStockDetailsForUpdate("CS-2", "A002"))
|
||||
.thenReturn(List.of(detail("STOCK-2", "D-2", "P-2", "10")));
|
||||
|
||||
DenseLaneOutboundPreview preview = service.preview(request);
|
||||
|
||||
@@ -128,8 +130,6 @@ class DenseLaneOutboundServiceImplTest
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(position));
|
||||
when(denseLaneService.orderOutermostFirst(any())).thenReturn(List.of(position));
|
||||
when(relocationJobMapper.selectStockDetailsForUpdate("CS-1", "A001")).thenReturn(details);
|
||||
when(relocationJobMapper.sumTrayStockQuantity(any(String[].class)))
|
||||
.thenReturn(new BigDecimal("12"));
|
||||
|
||||
DenseLaneOutboundPreview preview = service.preview(request);
|
||||
|
||||
@@ -137,6 +137,22 @@ class DenseLaneOutboundServiceImplTest
|
||||
assertThat(tray.getStockDetails()).containsExactlyElementsOf(details));
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewTotalUsesOnlySelectedCargoSpaceInventoryDetails()
|
||||
{
|
||||
DenseLaneOutboundRequest request = request(1);
|
||||
DenseLanePosition position = position("CS-1", 1L, "DUPLICATE-TRAY");
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(position));
|
||||
when(denseLaneService.orderOutermostFirst(any())).thenReturn(List.of(position));
|
||||
when(relocationJobMapper.selectStockDetailsForUpdate("CS-1", "DUPLICATE-TRAY"))
|
||||
.thenReturn(List.of(detail("STOCK-1", "D-1", "P-1", "7"),
|
||||
detail("STOCK-1", "D-2", "P-2", "5")));
|
||||
|
||||
DenseLaneOutboundPreview preview = service.preview(request);
|
||||
|
||||
assertThat(preview.getTotalQuantity()).isEqualByComparingTo("12");
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewRejectsQuantityOverAvailableCapacity()
|
||||
{
|
||||
@@ -154,7 +170,6 @@ class DenseLaneOutboundServiceImplTest
|
||||
DenseLanePosition position = position("CS-1", 1L, "A001");
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(position));
|
||||
when(denseLaneService.orderOutermostFirst(any())).thenReturn(List.of(position));
|
||||
when(relocationJobMapper.sumTrayStockQuantity(any(String[].class))).thenReturn(BigDecimal.TEN);
|
||||
when(relocationJobMapper.selectStockDetailsForUpdate("CS-1", "A001"))
|
||||
.thenReturn(List.of(detail("STOCK-1", "D-1", "P-1", "10")))
|
||||
.thenReturn(List.of(detail("STOCK-1", "D-1", "P-1", "9")));
|
||||
@@ -172,7 +187,6 @@ class DenseLaneOutboundServiceImplTest
|
||||
DenseLanePosition first = position("CS-1", 1L, "A001");
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(first));
|
||||
when(denseLaneService.orderOutermostFirst(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(relocationJobMapper.sumTrayStockQuantity(any(String[].class))).thenReturn(BigDecimal.TEN);
|
||||
request.setPreviewToken(service.preview(request).getPreviewToken());
|
||||
DenseLanePosition changed = position("CS-2", 2L, "A002");
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(changed));
|
||||
@@ -187,7 +201,6 @@ class DenseLaneOutboundServiceImplTest
|
||||
DenseLanePosition position = position("CS-1", 1L, "A001");
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(position));
|
||||
when(denseLaneService.orderOutermostFirst(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(relocationJobMapper.sumTrayStockQuantity(any(String[].class))).thenReturn(new BigDecimal("12"));
|
||||
List<MydoJobDetail> stockDetails = List.of(detail("STOCK-1", "D-1", "P-1", "7"),
|
||||
detail("STOCK-1", "D-2", "P-2", "5"));
|
||||
when(relocationJobMapper.selectStockDetailsForUpdate("CS-1", "A001"))
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.ruoyi.out.relocation.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 RelocationJobMapperSqlTest
|
||||
{
|
||||
@Test
|
||||
void relocationRuntimeQueriesUseExplicitSnakeCaseResultMaps() throws Exception
|
||||
{
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String positionMap = section(xml, "<resultMap id=\"DenseLanePositionResult\"", "</resultMap>");
|
||||
String stockMap = section(xml, "<resultMap id=\"StockDetailResult\"", "</resultMap>");
|
||||
String jobSelect = section(xml, "<select id=\"selectJobForUpdate\"", "</select>");
|
||||
String stockSelect = section(xml, "<select id=\"selectStockDetailsForUpdate\"", "</select>");
|
||||
String sourceSelect = section(xml, "<select id=\"selectRelocationSourcesForUpdate\"", "</select>");
|
||||
|
||||
assertThat(positionMap)
|
||||
.contains("property=\"cargoSpaceId\" column=\"cargo_space_id\"")
|
||||
.contains("property=\"stockQuantity\" column=\"stock_quantity\"");
|
||||
assertThat(stockMap)
|
||||
.contains("property=\"stockId\" column=\"stock_id\"")
|
||||
.contains("property=\"stockinDetailId\" column=\"stockin_detail_id\"")
|
||||
.contains("property=\"projectNo\" column=\"project_no\"");
|
||||
assertThat(jobSelect)
|
||||
.contains("resultMap=\"com.ruoyi.in.job.mapper.MydoJobMapper.MydoJobResult\"");
|
||||
assertThat(stockSelect).contains("resultMap=\"StockDetailResult\"");
|
||||
assertThat(sourceSelect).contains("resultMap=\"DenseLanePositionResult\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stockDetailQueryUsesActualBasStockSupplierColumns() throws Exception
|
||||
{
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String stockSelect = section(xml, "<select id=\"selectStockDetailsForUpdate\"", "</select>");
|
||||
|
||||
assertThat(stockSelect)
|
||||
.contains("stock.supplier_id as supplier_id")
|
||||
.contains("stock.supplier_name as supplier_name")
|
||||
.doesNotContain("stock.suplier_id")
|
||||
.doesNotContain("stock.suplier_name");
|
||||
}
|
||||
|
||||
private Path resolveMapperXml()
|
||||
{
|
||||
return Stream.of(
|
||||
Path.of("src/main/resources/mapper/out/RelocationJobMapper.xml"),
|
||||
Path.of("ruoyi-admin/src/main/resources/mapper/out/RelocationJobMapper.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);
|
||||
}
|
||||
}
|
||||
@@ -118,6 +118,24 @@ class RelocationJobServiceImplTest
|
||||
.hasMessageContaining("不同通道");
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewTotalUsesExactSelectedCargoSpaceQuantities()
|
||||
{
|
||||
RelocationGenerateRequest request = new RelocationGenerateRequest();
|
||||
request.setWarehouseId("03");
|
||||
request.setTheFloor(1L);
|
||||
request.setSourceGroupCoding("SOURCE-GROUP");
|
||||
request.setTargetGroupCoding("TARGET-GROUP");
|
||||
request.setTrayCount(2);
|
||||
DenseLanePosition first = source("CS-1", "DUPLICATE-TRAY");
|
||||
first.setStockQuantity(new BigDecimal("7"));
|
||||
DenseLanePosition second = source("CS-2", "UNIQUE-TRAY");
|
||||
second.setStockQuantity(new BigDecimal("5"));
|
||||
when(mapper.selectRelocationSourcesForUpdate(request)).thenReturn(List.of(first, second));
|
||||
when(denseLaneService.orderOutermostFirst(any())).thenReturn(List.of(first, second));
|
||||
assertThat(service.preview(request).getTotalQuantity()).isEqualByComparingTo("12");
|
||||
}
|
||||
|
||||
@Test
|
||||
void completionMovesInventorySwapsCargoSpacesArchivesAndWakesNextJob()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user