暂存提交

This commit is contained in:
2026-08-19 09:28:40 +08:00
parent 0192380e81
commit 6b077c20db
5 changed files with 206 additions and 9 deletions
@@ -19,7 +19,7 @@ public interface LoadingCompletedEventMapper {
COALESCE(o.root_inbound_id, o.id) AS root_inbound_id,
o.order_id AS source_order_id, o.order_no AS source_order_no,
COALESCE(root_order.order_id, o.order_id) AS root_order_id,
o.inbound_code, o.destination_warehouse_id, o.destination_code AS destination_warehouse_code,
o.inbound_code, o.business_type, o.destination_warehouse_id, o.destination_code AS destination_warehouse_code,
o.destination_name AS destination_warehouse_name,
i.inbound_detail_id AS source_inbound_detail_id, i.sku_code,
i.product_name_zh, i.product_name_en, i.package_count, i.product_quantity,
@@ -36,6 +36,27 @@ public interface LoadingCompletedEventMapper {
""")
List<Row> rows(@Param("planId") long planId);
/** 读取一键代发托盘及其箱唛快照;普通订单不会使用该结果。 */
@Select("""
SELECT i.account_id, i.inbound_id AS source_inbound_id,
a.pallet_code, a.allocation_no, a.picked_package_count AS allocated_package_count,
i.inbound_detail_id AS source_inbound_detail_id, i.sku_code,
i.product_name_zh, i.package_count, i.product_quantity,
label.box_code
FROM fl_loading_plan_item i
JOIN fl_inbound_pallet_allocation a
ON a.tenant_id = i.account_id AND a.inbound_id = i.inbound_id
AND a.allocation_status IN ('CONFIRMED','TRANSFERRED','RELEASED')
AND a.picked_package_count > 0
LEFT JOIN fl_inbound_carton_label label
ON label.tenant_id = i.account_id AND label.inbound_id = i.inbound_id
AND label.inbound_detail_id = i.inbound_detail_id AND label.batch_status = 'ACTIVE'
WHERE i.loading_plan_id = #{planId} AND i.deleted = 0
ORDER BY i.account_id, i.inbound_id, a.allocation_no,
i.inbound_detail_id, label.carton_index, label.id
""")
List<PalletRow> palletRows(@Param("planId") long planId);
@Getter @Setter
class Row {
private Long loadingPlanId; private String planNo; private Long containerBookingId;
@@ -49,5 +70,14 @@ public interface LoadingCompletedEventMapper {
private Integer productQuantity; private Integer palletCount; private BigDecimal grossWeightKg;
private BigDecimal netWeightKg; private BigDecimal dimensionalWeightKg; private BigDecimal volumeCbm;
private String fbaTrackingNo; private String shipmentNo; private String shippingMark;
private String businessType;
}
@Getter @Setter
class PalletRow {
private Long accountId; private Long sourceInboundId; private String palletCode;
private Integer allocationNo; private Integer allocatedPackageCount;
private Long sourceInboundDetailId; private String skuCode; private String productNameZh;
private Integer packageCount; private Integer productQuantity; private String boxCode;
}
}
@@ -14,6 +14,7 @@ import com.baiyue.firstleg.entity.InboundOutboxEntity;
import com.baiyue.firstleg.mapper.InboundOutboxMapper;
import com.baiyue.firstleg.mapper.LoadingCompletedEventMapper;
import com.baiyue.firstleg.mapper.LoadingCompletedEventMapper.Row;
import com.baiyue.firstleg.mapper.LoadingCompletedEventMapper.PalletRow;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -29,6 +30,7 @@ import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** 在装柜完成事务内按客户写跨区业务 Outbox。 */
@Service
@@ -41,17 +43,20 @@ public class LoadingCompletedOutboxService {
public void publish(long planId) {
List<Row> rows = snapshotMapper.rows(planId);
if (rows.isEmpty()) throw new BizException(ResultCode.CONFLICT, "装柜计划没有最终实装入仓单,不能生成尾程交接事件");
List<PalletRow> palletRows = snapshotMapper.palletRows(planId);
rows.stream().collect(java.util.stream.Collectors.groupingBy(Row::getAccountId,
java.util.TreeMap::new, java.util.stream.Collectors.toList()))
.forEach((accountId, accountRows) -> insert(envelope(accountId, accountRows)));
.forEach((accountId, accountRows) -> insert(envelope(accountId, accountRows,
palletRows.stream().filter(item -> accountId.equals(item.getAccountId())).toList())));
}
private EventEnvelope envelope(long accountId, List<Row> rows) {
private EventEnvelope envelope(long accountId, List<Row> rows, List<PalletRow> palletRows) {
Row first = rows.get(0);
Map<Long, List<Row>> byInbound = rows.stream().collect(java.util.stream.Collectors.groupingBy(
Row::getSourceInboundId, java.util.TreeMap::new, java.util.stream.Collectors.toList()));
List<FirstLegLoadingPlanCompletedV1.Order> orders = byInbound.values().stream()
.map(this::order).toList();
.map(value -> order(value, palletRows.stream()
.filter(item -> Objects.equals(item.getSourceInboundId(), value.get(0).getSourceInboundId())).toList())).toList();
var payload = new FirstLegLoadingPlanCompletedV1(String.valueOf(first.getLoadingPlanId()),
first.getPlanNo(), string(first.getContainerBookingId()), first.getContainerNo(), first.getCabinetNo(),
string(first.getWarehouseId()), first.getWarehouseName(), orders);
@@ -65,7 +70,7 @@ public class LoadingCompletedOutboxService {
traceId, eventKey, null, payloadNode, hash).requireValid();
}
private FirstLegLoadingPlanCompletedV1.Order order(List<Row> values) {
private FirstLegLoadingPlanCompletedV1.Order order(List<Row> values, List<PalletRow> palletRows) {
List<Row> sorted = values.stream().sorted(Comparator.comparing(Row::getSourceInboundDetailId)).toList();
Row first = sorted.get(0);
List<FirstLegLoadingPlanCompletedV1.Item> items = sorted.stream().map(row ->
@@ -75,14 +80,81 @@ public class LoadingCompletedOutboxService {
row.getVolumeCbm(), string(row.getDestinationWarehouseId()), row.getDestinationWarehouseCode(),
row.getDestinationWarehouseName(), row.getFbaTrackingNo(), row.getShipmentNo(), row.getShippingMark()))
.toList();
String orderType = dropship(first.getBusinessType()) ? "ONE_CLICK_DROPSHIP" : "FIRST_LEG";
List<FirstLegLoadingPlanCompletedV1.PalletSnapshot> pallets = dropship(first.getBusinessType())
? palletSnapshots(sorted, palletRows) : List.of();
if ("ONE_CLICK_DROPSHIP".equals(orderType) && pallets.isEmpty()) {
throw new BizException(ResultCode.CONFLICT, "一键代发入仓单缺少托盘冻结快照");
}
return new FirstLegLoadingPlanCompletedV1.Order(string(first.getSourceInboundId()),
string(first.getRootInboundId()), string(first.getSourceOrderId()), first.getSourceOrderNo(),
string(first.getRootOrderId()), first.getInboundCode(), string(first.getDestinationWarehouseId()),
first.getDestinationWarehouseCode(), first.getDestinationWarehouseName(), sumInt(sorted, Row::getPackageCount),
sumNullableInt(sorted, Row::getProductQuantity), sumNullableInt(sorted, Row::getPalletCount),
sum(sorted, Row::getGrossWeightKg), sum(sorted, Row::getNetWeightKg), sum(sorted, Row::getVolumeCbm), items);
sumNullableInt(sorted, Row::getProductQuantity), "ONE_CLICK_DROPSHIP".equals(orderType)
? pallets.size() : 0, sum(sorted, Row::getGrossWeightKg), sum(sorted, Row::getNetWeightKg),
sum(sorted, Row::getVolumeCbm), items, string(first.getAccountId()), orderType, pallets);
}
private List<FirstLegLoadingPlanCompletedV1.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(
p -> (p.getAllocationNo() == null ? "" : p.getAllocationNo()) + "|" + p.getPalletCode(),
LinkedHashMap::new, java.util.stream.Collectors.toList()));
int loadedPackages = byPallet.values().stream().map(list -> list.get(0)).map(PalletRow::getAllocatedPackageCount)
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
if (loadedPackages != sumInt(values, Row::getPackageCount)) {
throw new BizException(ResultCode.CONFLICT, "一键代发实装箱数与托盘已拣箱数不一致");
}
Map<Long, DetailSnapshot> details = new LinkedHashMap<>();
values.stream().sorted(Comparator.comparing(Row::getSourceInboundDetailId)).forEach(row ->
details.putIfAbsent(row.getSourceInboundDetailId(), new DetailSnapshot(row.getSourceInboundDetailId(),
row.getSkuCode(), row.getProductNameZh(), row.getPackageCount(), row.getProductQuantity(), new ArrayList<>())));
source.stream().filter(item -> item.getBoxCode() != null).forEach(item -> {
DetailSnapshot detail = details.get(item.getSourceInboundDetailId());
if (detail != null && !detail.marks().contains(item.getBoxCode())) detail.marks().add(item.getBoxCode());
});
Map<Long, Integer> remainingPackages = details.values().stream().collect(java.util.stream.Collectors.toMap(
DetailSnapshot::detailId, DetailSnapshot::packageCount, (a,b)->a, LinkedHashMap::new));
Map<Long, Integer> remainingMarks = details.values().stream().collect(java.util.stream.Collectors.toMap(
DetailSnapshot::detailId, ignored -> 0, (a,b)->a, LinkedHashMap::new));
List<FirstLegLoadingPlanCompletedV1.PalletSnapshot> result = new ArrayList<>();
for (List<PalletRow> palletRows : byPallet.values()) {
PalletRow first = palletRows.get(0);
int capacity = first.getAllocatedPackageCount() == null ? 0 : first.getAllocatedPackageCount();
List<FirstLegLoadingPlanCompletedV1.SkuPackageSnapshot> sku = new ArrayList<>();
for (DetailSnapshot detail : details.values()) {
int remaining = remainingPackages.getOrDefault(detail.detailId(), 0);
if (capacity <= 0 || remaining <= 0) continue;
int count = Math.min(capacity, remaining);
int totalQuantity = detail.productQuantity() == null ? count
: (int) Math.round((double) detail.productQuantity() * count / Math.max(1, detail.packageCount()));
int markStart = remainingMarks.getOrDefault(detail.detailId(), 0);
int markEnd = Math.min(detail.marks().size(), markStart + count);
List<String> marks = new ArrayList<>(detail.marks().subList(markStart, markEnd));
if (marks.size() != count) throw new BizException(ResultCode.CONFLICT, "一键代发箱唛数量与托盘箱数不一致");
sku.add(new FirstLegLoadingPlanCompletedV1.SkuPackageSnapshot(detail.skuCode(),
detail.productName(), count, totalQuantity, marks));
remainingPackages.put(detail.detailId(), remaining - count);
remainingMarks.put(detail.detailId(), markEnd);
capacity -= count;
}
if (!sku.isEmpty()) result.add(new FirstLegLoadingPlanCompletedV1.PalletSnapshot(
first.getPalletCode() == null ? "PALLET-" + first.getAllocationNo() : first.getPalletCode(), sku));
}
if (remainingPackages.values().stream().mapToInt(Integer::intValue).sum() != 0) {
throw new BizException(ResultCode.CONFLICT, "一键代发托盘未覆盖全部实装货物明细");
}
return result;
}
private static boolean dropship(String value) {
return value != null && ("ONE_CLICK_DROPSHIP".equalsIgnoreCase(value)
|| value.toUpperCase(java.util.Locale.ROOT).contains("DROPSHIP"));
}
private record DetailSnapshot(Long detailId, String skuCode, String productName,
Integer packageCount, Integer productQuantity, List<String> marks) {}
private void insert(EventEnvelope envelope) {
InboundOutboxEntity value = new InboundOutboxEntity();
value.setId(IdWorker.getId()); value.setTenantId(Long.valueOf(envelope.tenantId()));