fix(firstleg): allocate handover carton marks per inbound
This commit is contained in:
@@ -3,6 +3,8 @@ package com.baiyue.firstleg.mapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -10,12 +12,15 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface LoadingCartonManifestMapper {
|
||||
@Select("""
|
||||
SELECT box_code FROM fl_inbound_carton_label
|
||||
WHERE tenant_id=#{tenantId} AND inbound_id=#{inboundId} AND inbound_detail_id=#{inboundDetailId}
|
||||
AND batch_status='ACTIVE'
|
||||
SELECT inbound_detail_id, box_code FROM fl_inbound_carton_label
|
||||
WHERE tenant_id=#{tenantId} AND inbound_id=#{inboundId} AND batch_status='ACTIVE'
|
||||
ORDER BY carton_index,id
|
||||
""")
|
||||
List<String> labelPool(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
|
||||
@Param("inboundDetailId") long inboundDetailId);
|
||||
List<LabelRow> labelPool(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
|
||||
@Getter @Setter
|
||||
class LabelRow {
|
||||
private Long inboundDetailId;
|
||||
private String boxCode;
|
||||
}
|
||||
}
|
||||
|
||||
+42
-18
@@ -16,6 +16,7 @@ import com.baiyue.firstleg.mapper.LoadingCompletedEventMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingCompletedEventMapper.Row;
|
||||
import com.baiyue.firstleg.mapper.LoadingCompletedEventMapper.PalletRow;
|
||||
import com.baiyue.firstleg.mapper.LoadingCartonManifestMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingCartonManifestMapper.LabelRow;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -30,9 +31,11 @@ import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/** 在装柜完成事务内按客户写跨区业务 Outbox。 */
|
||||
@Service
|
||||
@@ -106,33 +109,54 @@ public class LoadingCompletedOutboxService {
|
||||
/** 国内按实装箱数从可用标签池稳定随机抽取箱唛,重复发布同一计划时结果不变。 */
|
||||
private Map<Long, List<String>> cartonLabelPools(List<Row> rows) {
|
||||
Map<Long, List<String>> result = new java.util.LinkedHashMap<>();
|
||||
for (Row row : rows) {
|
||||
if (dropship(row.getBusinessType())) continue;
|
||||
if (row.getSkuCode() == null || row.getSkuCode().isBlank()) {
|
||||
Map<LabelPoolKey, List<Row>> byInbound = rows.stream().filter(row -> !dropship(row.getBusinessType()))
|
||||
.collect(java.util.stream.Collectors.groupingBy(
|
||||
row -> new LabelPoolKey(row.getAccountId(), row.getSourceInboundId()),
|
||||
LinkedHashMap::new, java.util.stream.Collectors.toList()));
|
||||
for (Map.Entry<LabelPoolKey, List<Row>> entry : byInbound.entrySet()) {
|
||||
List<Row> inboundRows = entry.getValue().stream()
|
||||
.sorted(Comparator.comparing(Row::getLoadingPlanItemId)).toList();
|
||||
if (inboundRows.stream().anyMatch(row -> row.getSkuCode() == null || row.getSkuCode().isBlank())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "装柜明细缺少 SKU,不能建立箱唛与 SKU 的对应关系");
|
||||
}
|
||||
List<String> labels = Objects.requireNonNullElse(cartonManifestMapper.labelPool(row.getAccountId(),
|
||||
row.getSourceInboundId(), row.getSourceInboundDetailId()), List.of());
|
||||
if (labels.isEmpty()) throw new BizException(ResultCode.CONFLICT, "装柜明细没有已打印的可用箱唛池,不能生成海外交接事件");
|
||||
if (labels.stream().anyMatch(label -> label == null || label.isBlank())
|
||||
|| labels.stream().distinct().count() != labels.size()) {
|
||||
List<LabelRow> labels = Objects.requireNonNullElse(cartonManifestMapper.labelPool(
|
||||
entry.getKey().accountId(), entry.getKey().inboundId()), List.of());
|
||||
if (labels.isEmpty()) {
|
||||
throw new BizException(ResultCode.CONFLICT, "装柜明细没有已打印的可用箱唛池,不能生成海外交接事件");
|
||||
}
|
||||
if (labels.stream().anyMatch(label -> label == null || label.getBoxCode() == null
|
||||
|| label.getBoxCode().isBlank())
|
||||
|| labels.stream().map(LabelRow::getBoxCode).distinct().count() != labels.size()) {
|
||||
throw new BizException(ResultCode.CONFLICT, "装柜明细箱唛池包含空值或重复箱号");
|
||||
}
|
||||
if (labels.size() < Objects.requireNonNullElse(row.getPackageCount(), 0)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "装柜明细箱唛数量少于实装箱数,不能生成海外交接事件");
|
||||
Set<String> used = new HashSet<>();
|
||||
for (Row row : inboundRows) {
|
||||
int loadedPackages = Objects.requireNonNullElse(row.getPackageCount(), 0);
|
||||
String seed = row.getLoadingPlanId() + ":" + row.getLoadingPlanItemId();
|
||||
Comparator<LabelRow> stableOrder = Comparator.comparing(label -> EventPayloadHash.sha256(
|
||||
(seed + ":" + label.getBoxCode()).getBytes(StandardCharsets.UTF_8)));
|
||||
List<String> selected = new ArrayList<>();
|
||||
labels.stream().filter(label -> !used.contains(label.getBoxCode())
|
||||
&& Objects.equals(label.getInboundDetailId(), row.getSourceInboundDetailId()))
|
||||
.sorted(stableOrder).limit(loadedPackages).map(LabelRow::getBoxCode).forEach(selected::add);
|
||||
if (selected.size() < loadedPackages) {
|
||||
labels.stream().filter(label -> !used.contains(label.getBoxCode())
|
||||
&& !selected.contains(label.getBoxCode()))
|
||||
.sorted(stableOrder).limit(loadedPackages - selected.size())
|
||||
.map(LabelRow::getBoxCode).forEach(selected::add);
|
||||
}
|
||||
if (selected.size() < loadedPackages) {
|
||||
throw new BizException(ResultCode.CONFLICT, "装柜明细箱唛数量少于实装箱数,不能生成海外交接事件");
|
||||
}
|
||||
used.addAll(selected);
|
||||
result.put(row.getLoadingPlanItemId(), List.copyOf(selected));
|
||||
}
|
||||
int loadedPackages = Objects.requireNonNullElse(row.getPackageCount(), 0);
|
||||
String seed = row.getLoadingPlanId() + ":" + row.getLoadingPlanItemId();
|
||||
List<String> selected = labels.stream()
|
||||
.sorted(Comparator.comparing(label -> EventPayloadHash.sha256(
|
||||
(seed + ":" + label).getBytes(StandardCharsets.UTF_8))))
|
||||
.limit(loadedPackages)
|
||||
.toList();
|
||||
result.put(row.getLoadingPlanItemId(), selected);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private record LabelPoolKey(long accountId, long inboundId) { }
|
||||
|
||||
private List<FirstLegLoadingPlanCompletedV2.PalletSnapshot> palletSnapshots(List<Row> values, List<PalletRow> source) {
|
||||
if (source.isEmpty()) return List.of();
|
||||
Map<String, List<PalletRow>> byPallet = source.stream().collect(java.util.stream.Collectors.groupingBy(
|
||||
|
||||
Reference in New Issue
Block a user