This commit is contained in:
2026-08-27 16:42:27 +08:00
parent ecf48886d5
commit 61a97cd057
9 changed files with 187 additions and 23 deletions
@@ -7,6 +7,7 @@ import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/** 读取 PDA 结算后的最终实装入仓单和冻结货物快照。 */
@@ -16,6 +17,8 @@ public interface LoadingCompletedEventMapper {
SELECT p.id AS loading_plan_id, p.plan_no, p.container_booking_id, p.version AS aggregate_version,
b.container_no, b.cabinet_no, b.warehouse_id, b.warehouse_name,
b.customer_id AS booking_customer_id, b.customer_name,
b.estimated_port_arrival_at, b.actual_port_arrival_at,
b.estimated_warehouse_arrival_at,
i.account_id, i.inbound_id AS source_inbound_id,
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,
@@ -50,7 +53,8 @@ public interface LoadingCompletedEventMapper {
/** 读取一键代发托盘及其箱唛快照;普通订单不会使用该结果。 */
@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,
a.pallet_id, a.reservation_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
@@ -82,6 +86,8 @@ public interface LoadingCompletedEventMapper {
private Integer aggregateVersion; private String containerNo; private String cabinetNo;
private Long warehouseId; private String warehouseName; private Long bookingCustomerId;
private String customerName; private Long accountId;
private LocalDateTime estimatedPortArrivalAt; private LocalDateTime actualPortArrivalAt;
private LocalDateTime estimatedWarehouseArrivalAt;
private Long sourceInboundId; private Long rootInboundId; private Long sourceOrderId;
private Long loadingPlanItemId;
private String sourceOrderNo; private Long rootOrderId; private String inboundCode;
@@ -97,7 +103,8 @@ public interface LoadingCompletedEventMapper {
@Getter @Setter
class PalletRow {
private Long accountId; private Long sourceInboundId; private String palletCode;
private Long accountId; private Long sourceInboundId; private Long palletId;
private Long reservationId; 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;
@@ -68,7 +68,8 @@ public class LoadingCompletedOutboxService {
.filter(item -> Objects.equals(item.getSourceInboundId(), value.get(0).getSourceInboundId())).toList(), cartonMarks)).toList();
var payload = new FirstLegLoadingPlanCompletedV2(String.valueOf(first.getLoadingPlanId()),
first.getPlanNo(), string(first.getContainerBookingId()), first.getContainerNo(), first.getCabinetNo(),
string(first.getWarehouseId()), first.getWarehouseName(), orders);
string(first.getWarehouseId()), first.getWarehouseName(), iso(first.getEstimatedPortArrivalAt()),
iso(first.getActualPortArrivalAt()), iso(first.getEstimatedWarehouseArrivalAt()), orders);
JsonNode payloadNode = objectMapper.valueToTree(payload);
String hash = EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(payloadNode));
String eventKey = "FIRSTLEG_LOADING_COMPLETED:" + first.getLoadingPlanId() + ':' + accountId + ':' + HANDOVER_VERSION;
@@ -103,12 +104,20 @@ public class LoadingCompletedOutboxService {
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), "ONE_CLICK_DROPSHIP".equals(orderType)
? pallets.size() : 0, sum(sorted, Row::getGrossWeightKg), sum(sorted, Row::getNetWeightKg),
sum(sorted, Row::getVolumeCbm), items, string(first.getAccountId()), first.getCustomerName().trim(), orderType, pallets,
sumNullableInt(sorted, Row::getProductQuantity), actualPalletCount(palletRows),
sum(sorted, Row::getGrossWeightKg), sum(sorted, Row::getNetWeightKg),
sum(sorted, Row::getVolumeCbm), sum(sorted, Row::getDimensionalWeightKg), items,
string(first.getAccountId()), first.getCustomerName().trim(), orderType, pallets,
destinationType(sorted));
}
/** 只按 Warehouse 已分配的物理 palletId 计数;无分配事实时保持 null。 */
private static Integer actualPalletCount(List<PalletRow> palletRows) {
Set<Long> palletIds = palletRows.stream().map(PalletRow::getPalletId)
.filter(Objects::nonNull).filter(value -> value > 0).collect(java.util.stream.Collectors.toSet());
return palletIds.isEmpty() ? null : palletIds.size();
}
private void validateCustomerSnapshots(List<Row> rows) {
for (Row row : rows) {
if (row.getAccountId() == null || !Objects.equals(row.getAccountId(), row.getBookingCustomerId())) {
@@ -275,6 +284,7 @@ public class LoadingCompletedOutboxService {
private String json(Object value) { try { return objectMapper.writeValueAsString(value); }
catch (JsonProcessingException e) { throw new BizException(ResultCode.INTERNAL_ERROR, "装柜完成事件序列化失败"); } }
private static String string(Long value) { return value == null ? null : String.valueOf(value); }
private static String iso(java.time.LocalDateTime value) { return value == null ? null : value.toString(); }
private static int sumInt(List<Row> rows, java.util.function.Function<Row,Integer> getter) { return rows.stream().map(getter).filter(java.util.Objects::nonNull).mapToInt(Integer::intValue).sum(); }
private static Integer sumNullableInt(List<Row> rows, java.util.function.Function<Row,Integer> getter) { List<Integer> values=rows.stream().map(getter).filter(java.util.Objects::nonNull).toList(); return values.isEmpty()?null:values.stream().mapToInt(Integer::intValue).sum(); }
private static BigDecimal sum(List<Row> rows, java.util.function.Function<Row,BigDecimal> getter) { List<BigDecimal> values=rows.stream().map(getter).filter(java.util.Objects::nonNull).toList(); return values.isEmpty()?null:values.stream().reduce(BigDecimal.ZERO, BigDecimal::add); }