优化
This commit is contained in:
@@ -2,6 +2,7 @@ package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.PendingTask;
|
||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.PalletCandidate;
|
||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.GroupMemberCandidate;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
@@ -254,6 +255,17 @@ public interface DropshipPalletizationMapper {
|
||||
""")
|
||||
List<PrintSkuRow> printSkuRows(@Param("palletId") long palletId);
|
||||
|
||||
@Select("""
|
||||
SELECT o.tenant_id accountId,o.id inboundId,o.order_id orderId,o.order_no orderNo,
|
||||
o.inbound_code inboundCode,o.customer_name customerName,COUNT(DISTINCT c.id) availablePackageCount,
|
||||
o.process_status orderStatusSnapshot,o.expected_arrival_at expectedArrivalAt
|
||||
FROM fl_dropship_pallet_carton c
|
||||
JOIN fl_inbound_order o ON o.id=c.inbound_id AND o.tenant_id=c.tenant_id
|
||||
WHERE c.pallet_id=#{palletId}
|
||||
GROUP BY o.tenant_id,o.id,o.order_id,o.order_no,o.inbound_code,o.customer_name,o.process_status,o.expected_arrival_at
|
||||
ORDER BY o.inbound_code,o.id
|
||||
""") List<GroupMemberCandidate> palletInboundMembers(@Param("palletId") long palletId);
|
||||
|
||||
@Select("""
|
||||
SELECT p.id pallet_id,p.pallet_no,'SEALED_PALLET' unit_type,TRUE indivisible,COUNT(DISTINCT c.id) carton_count,
|
||||
(SELECT COUNT(DISTINCT sku.sku_code) FROM fl_dropship_pallet_carton pc
|
||||
|
||||
@@ -142,7 +142,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
||||
<script>
|
||||
SELECT current_group.id groupId,
|
||||
CASE WHEN current_group.id IS NULL THEN eligible.accountId END accountId,
|
||||
CASE WHEN current_group.id IS NULL THEN eligible.inboundId END inboundId,
|
||||
MIN(CASE WHEN current_group.id IS NULL THEN eligible.inboundId END) inboundId,
|
||||
CASE WHEN current_group.id IS NULL THEN eligible.palletId END palletId,
|
||||
MIN(eligible.expectedArrivalAt) sortArrivalAt,MIN(eligible.inboundId) sortInboundId
|
||||
FROM (
|
||||
@@ -202,7 +202,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
||||
WHERE eligible.candidateStatus<>'WAIT_SPLIT' OR eligible.availablePackageCount>0
|
||||
GROUP BY current_group.id,
|
||||
CASE WHEN current_group.id IS NULL THEN eligible.accountId END,
|
||||
CASE WHEN current_group.id IS NULL THEN eligible.inboundId END,
|
||||
CASE WHEN current_group.id IS NULL AND eligible.palletId IS NULL THEN eligible.inboundId END,
|
||||
CASE WHEN current_group.id IS NULL THEN eligible.palletId END
|
||||
HAVING (current_group.id IS NULL OR (
|
||||
COUNT(DISTINCT eligible.accountId,eligible.inboundId)=(SELECT COUNT(*) FROM fl_coload_group_member group_member_count
|
||||
@@ -230,7 +230,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
||||
<if test="query.createdEnd != null">AND MIN(eligible.createdAt)<DATE_ADD(#{query.createdEnd},INTERVAL 1 DAY)</if>
|
||||
<if test="query.expectedArrivalStart != null">AND MIN(eligible.expectedArrivalAt)>=#{query.expectedArrivalStart}</if>
|
||||
<if test="query.expectedArrivalEnd != null">AND MIN(eligible.expectedArrivalAt)<DATE_ADD(#{query.expectedArrivalEnd},INTERVAL 1 DAY)</if>
|
||||
ORDER BY sortArrivalAt IS NULL,sortArrivalAt,sortInboundId
|
||||
ORDER BY sortArrivalAt IS NULL,sortArrivalAt,sortInboundId,palletId
|
||||
</script>
|
||||
""")
|
||||
Page<CandidateUnit> globalCandidateUnits(Page<CandidateUnit> page,
|
||||
|
||||
@@ -103,7 +103,7 @@ public class SmartLoadingService {
|
||||
Map<Long, List<Candidate>> grouped = candidates(bookingId, warehouseId).stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(Candidate::inboundId, LinkedHashMap::new,
|
||||
java.util.stream.Collectors.toList()));
|
||||
return grouped.values().stream().flatMap(details -> inboundCandidates(details).stream()).toList();
|
||||
return uniqueCandidateUnits(grouped.values().stream().flatMap(details -> inboundCandidates(details).stream()).toList());
|
||||
}
|
||||
|
||||
private List<InboundCandidate> recommendationCandidates(ContainerBookingEntity booking,
|
||||
@@ -289,7 +289,7 @@ public class SmartLoadingService {
|
||||
first.unitNetWeightKg(), pallet.cartonCount()==null||pallet.cartonCount()==0?null:pallet.volumeCbm().divide(BigDecimal.valueOf(pallet.cartonCount()),6,RoundingMode.HALF_UP),
|
||||
pallet.volumeCbm(),"SEALED_PALLET",pallet.palletId(),pallet.palletNo(),true,
|
||||
first.coLoadGroupId(), first.groupNo(), first.memberCount(),first.groupTotalBoxes(), false, !StringUtils.hasText(palletBlock), palletBlock,
|
||||
first.orderStatusSnapshot(),first.expectedArrivalAt(),List.of());
|
||||
first.orderStatusSnapshot(),first.expectedArrivalAt(),dropshipPallets.palletInboundMembers(pallet.palletId()));
|
||||
}).toList();
|
||||
return List.of(new InboundCandidate(first.accountId(), first.inboundId(), first.orderId(), first.orderNo(),
|
||||
first.inboundCode(), first.warehouseId(), first.channelCode(), first.channelName(),
|
||||
@@ -367,7 +367,17 @@ public class SmartLoadingService {
|
||||
}
|
||||
|
||||
private static String unitKey(Long groupId, Long accountId, Long inboundId, Long palletId) {
|
||||
return groupId == null ? "I|" + accountId + '|' + inboundId + '|' + palletId : "G|" + groupId;
|
||||
return groupId != null ? "G|" + groupId : palletId != null ? "P|" + palletId : "I|" + accountId + '|' + inboundId + '|' + palletId;
|
||||
}
|
||||
|
||||
private static List<InboundCandidate> uniqueCandidateUnits(List<InboundCandidate> candidates) {
|
||||
Map<String, InboundCandidate> units = new LinkedHashMap<>();
|
||||
for (InboundCandidate candidate : candidates) {
|
||||
String key = candidate.palletId() != null ? "P|" + candidate.palletId()
|
||||
: "I|" + candidate.accountId() + '|' + candidate.inboundId();
|
||||
units.putIfAbsent(key, candidate);
|
||||
}
|
||||
return List.copyOf(units.values());
|
||||
}
|
||||
|
||||
private static BigDecimal weightedAverage(List<InboundCandidate> members,
|
||||
|
||||
@@ -571,7 +571,8 @@ paths:
|
||||
description: >-
|
||||
需要 platform:firstleg:smart-loading:list 权限。查询不绑定柜次;渠道、仓库、客户、入仓码、
|
||||
订单创建日期、预计到货时间和订单状态共同作用于全局候选。每个普通入库单返回一行;同柜不可拆组作为一个
|
||||
分页单元返回,groupMembers 包含完整成员,不会跨页或因单个成员命中搜索条件而拆散。
|
||||
分页单元返回;一件代发每个已封托托盘仅返回一行,合托不按入仓码重复分页。
|
||||
groupMembers 包含同柜组或托盘的完整成员,不会跨页或因单个成员命中搜索条件而拆散;托盘行箱数为整托箱数,成员箱数为本票在本托的实际箱数。
|
||||
平均单箱毛重、净重按整票实测重量除以总箱数,平均单箱体积按剩余规格数量加权。
|
||||
orderStatuses 可重复传参且最多四项;未传或空数组表示 PENDING_REVIEW、WAIT_MEASURE、WAIT_SPLIT、
|
||||
LOADING 全部未完成状态,已完成装柜永不返回。状态映射为:Order SUBMITTED=PENDING_REVIEW;
|
||||
@@ -2443,7 +2444,7 @@ components:
|
||||
blockReason: {type: string, nullable: true}
|
||||
groupMembers:
|
||||
type: array
|
||||
description: 同柜组聚合行的完整成员;普通候选为空数组
|
||||
description: 同柜组或封托托盘的完整入仓成员;SEALED_PALLET 行按托盘唯一分页,成员 availablePackageCount 为该入仓码在本托的箱数,行 availablePackageCount 为整托总箱数。搜索命中任一成员仍返回整托完整组成;普通箱件候选为空数组。
|
||||
items:
|
||||
type: object
|
||||
required: [accountId, inboundId, orderId, orderNo, inboundCode, customerName, availablePackageCount]
|
||||
|
||||
+19
@@ -20,6 +20,21 @@ import java.time.LocalDateTime;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SmartLoadingCandidateMapperContractTest {
|
||||
@Test
|
||||
void sharedPalletPaginationGroupsByPalletRatherThanInbound() throws Exception {
|
||||
String sql = String.join(" ", SmartLoadingCandidateMapper.class
|
||||
.getDeclaredMethod("globalCandidateUnits", Page.class, InboundCandidateQuery.class, Set.class)
|
||||
.getAnnotation(Select.class).value());
|
||||
assertThat(sql).contains("MIN(CASE WHEN current_group.id IS NULL THEN eligible.inboundId END) inboundId")
|
||||
.contains("CASE WHEN current_group.id IS NULL AND eligible.palletId IS NULL THEN eligible.inboundId END")
|
||||
.contains("sortInboundId,palletId");
|
||||
String members = String.join(" ", com.baiyue.firstleg.mapper.DropshipPalletizationMapper.class
|
||||
.getDeclaredMethod("palletInboundMembers", long.class).getAnnotation(Select.class).value());
|
||||
assertThat(members).contains("COUNT(DISTINCT c.id) availablePackageCount")
|
||||
.contains("WHERE c.pallet_id=#{palletId}")
|
||||
.contains("GROUP BY o.tenant_id,o.id")
|
||||
.doesNotContain("#{inboundId}");
|
||||
}
|
||||
@Test
|
||||
void candidateHasOnlyTheCanonicalMybatisConstructor() {
|
||||
assertThat(Candidate.class.getDeclaredConstructors()).singleElement()
|
||||
@@ -165,6 +180,10 @@ class SmartLoadingCandidateMapperContractTest {
|
||||
.contains("eligible.candidateStatus='WAIT_SPLIT'")
|
||||
.contains("MAX(CASE WHEN LOWER(eligible.inboundCode)")
|
||||
.contains("MAX(CASE WHEN eligible.accountId=");
|
||||
assertThat(renderedUnits.indexOf("MAX(CASE WHEN LOWER(eligible.inboundCode)"))
|
||||
.as("inbound search must match any member after grouping, without cutting pallet composition")
|
||||
.isGreaterThan(renderedUnits.indexOf("HAVING"));
|
||||
assertThat(renderedUnits).contains("LIKE CONCAT('%',LOWER(");
|
||||
|
||||
Method details = SmartLoadingCandidateMapper.class.getDeclaredMethod(
|
||||
"globalCandidatesByUnits", List.class);
|
||||
|
||||
@@ -261,6 +261,30 @@ class SmartLoadingServiceTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void sharedPalletIsOneUnitWithExactInboundComposition() {
|
||||
Candidate dropship = withStatus(withChannel(candidate(9L, 10L, 20L, 100L, 20), "EXX_TRUCK", "EXX海卡"), "WAIT_SPLIT", false);
|
||||
// Searching a non-representative member must still return the whole pallet.
|
||||
InboundCandidateQuery query = new InboundCandidateQuery(List.of("EXX"), null, null, "I11",
|
||||
null, null, null, null, List.of(CandidateOrderStatus.WAIT_SPLIT), 1, 1);
|
||||
var unit = new CandidateUnit(null, 9L, 10L, 801L, null, 10L);
|
||||
when(repository.globalCandidateUnits(eq(query), anySet())).thenReturn(new PageResult<>(List.of(unit), 1, 1, 1, 1));
|
||||
when(repository.globalCandidatesByUnits(List.of(unit))).thenReturn(List.of(dropship));
|
||||
when(dropshipPallets.palletCandidates(10L)).thenReturn(List.of(
|
||||
new PalletCandidate(801L, "DP-0025", "SEALED_PALLET", true, 13, 1, new BigDecimal("130"), new BigDecimal("1.3"))));
|
||||
var members = List.of(
|
||||
new com.baiyue.firstleg.dto.SmartLoadingDtos.GroupMemberCandidate(9L, 10L, 100L, "O10", "I10", "客户", 3, "WAIT_SPLIT", null),
|
||||
new com.baiyue.firstleg.dto.SmartLoadingDtos.GroupMemberCandidate(9L, 11L, 101L, "O11", "I11", "客户", 10, "WAIT_SPLIT", null));
|
||||
when(dropshipPallets.palletInboundMembers(801L)).thenReturn(members);
|
||||
var result = service.inboundCandidates(query);
|
||||
assertThat(result.total()).isEqualTo(1);
|
||||
assertThat(result.records()).singleElement().satisfies(row -> {
|
||||
assertThat(row.availablePackageCount()).isEqualTo(13);
|
||||
assertThat(row.groupMembers()).containsExactlyElementsOf(members);
|
||||
assertThat(row.unitGrossWeightKg()).isEqualByComparingTo("10");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void combinesAllUnfinishedStatusesBeforeReturningTheRequestedPage() {
|
||||
InboundCandidateQuery query = new InboundCandidateQuery(
|
||||
|
||||
Reference in New Issue
Block a user