fix: complete order measurement downstream flow

This commit is contained in:
2026-09-18 14:58:40 +08:00
parent 02d2474141
commit c8a99f9fff
43 changed files with 621 additions and 52 deletions
@@ -16,7 +16,9 @@ public final class DeliveryResponses {
List<TransferDelivery> deliveries, boolean replayed) {}
public record OutboundRow(Long id, String outboundNo, String dispatchNo, String overseasWarehouseId, String lxCodes,
String status, int palletCount, int packageCount, LocalDateTime createdAt, String outboundType) {}
String status, int palletCount, int packageCount, BigDecimal grossWeightKg,
BigDecimal volumeWeightKg, BigDecimal volumeCbm,
LocalDateTime createdAt, String outboundType) {}
public record DeliveryRow(Long id, String customerId, String customerName, String deliveryNo,
String outboundNo, String dispatchNo, String overseasWarehouseId,
@@ -51,7 +53,9 @@ public final class DeliveryResponses {
/** palletNo is the warehouse-authoritative physical pallet code after inbound assignment. */
public record OutboundPalletRow(Long id, String palletNo, String cartonMarks, String inboundCode,
String destinationWarehouseCode, String destinationWarehouseName,
String skuCode, int packageCount, int pickedPackageCount, String status) {}
String skuCode, int packageCount, BigDecimal grossWeightKg,
BigDecimal volumeWeightKg, BigDecimal volumeCbm,
int pickedPackageCount, String status) {}
public record OutboundDetail(OutboundRow outbound, List<DeliveryRow> deliveries, List<OutboundPalletRow> pallets) {}
public record OneClickCandidate(Long inventoryPositionId, String orderNo, String customerId, String customerName, String overseasWarehouseId,
String overseasWarehouseName, String palletNo, String inboundCode,
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/** Outbound document created when a dispatch is transferred to delivery. */
@@ -24,6 +25,9 @@ public class OutboundOrder {
private String status;
private Integer packageCount;
private Integer palletCount;
private BigDecimal grossWeightKg;
private BigDecimal volumeWeightKg;
private BigDecimal volumeCbm;
private String idempotencyKey;
private String requestHash;
private String createdBy;
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/** Pallet-level immutable snapshot belonging to one outbound document. */
@@ -21,6 +22,9 @@ public class OutboundPalletItem {
private String destinationWarehouseCode;
private String destinationWarehouseName;
private Integer packageCount;
private BigDecimal grossWeightKg;
private BigDecimal volumeWeightKg;
private BigDecimal volumeCbm;
private Integer pickedPackageCount;
private String status;
private String skuCode;
@@ -165,7 +165,11 @@ public class DeliveryTransferService {
outbound.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); outbound.setStatus("WAIT_OUTBOUND"); outbound.setOutboundType("DISPATCH_DELIVERY");
outbound.setLxCodes(resolveLxCodes(customerId, items));
outbound.setPackageCount(items.stream().map(DispatchItem::getPackageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
outbound.setPalletCount(items.size()); outbound.setIdempotencyKey(request.idempotencyKey().trim());
outbound.setPalletCount(items.size());
outbound.setGrossWeightKg(sum(items, DispatchItem::getGrossWeightKg));
outbound.setVolumeWeightKg(sum(items, DispatchItem::getVolumeWeightKg));
outbound.setVolumeCbm(sum(items, DispatchItem::getVolumeCbm));
outbound.setIdempotencyKey(request.idempotencyKey().trim());
outbound.setCreatedBy(operatorId); outbound.setCreatedAt(now); outbound.setUpdatedAt(now);
try {
outboundOrders.insert(outbound);
@@ -190,7 +194,9 @@ public class DeliveryTransferService {
item.setDestinationWarehouseId(source.getDestinationWarehouseId());
item.setDestinationWarehouseCode(source.getDestinationWarehouseCode());
item.setDestinationWarehouseName(source.getDestinationWarehouseName());
item.setPackageCount(Objects.requireNonNullElse(source.getPackageCount(), 0)); item.setPickedPackageCount(0); item.setStatus("WAIT_PICK");
item.setPackageCount(Objects.requireNonNullElse(source.getPackageCount(), 0));
item.setGrossWeightKg(source.getGrossWeightKg()); item.setVolumeWeightKg(source.getVolumeWeightKg());
item.setVolumeCbm(source.getVolumeCbm()); item.setPickedPackageCount(0); item.setStatus("WAIT_PICK");
if (StringUtils.hasText(source.getCartonMarks())) {
item.setCartonMarks(source.getCartonMarks());
} else if (source.getWarehousePalletId() != null) {
@@ -236,8 +242,8 @@ public class DeliveryTransferService {
delivery.setSellerName(trim(request.sellerName())); delivery.setPlannedDeliveryAt(request.plannedDeliveryAt());
delivery.setPackageCount(groupItems.stream().map(DispatchItem::getPackageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
delivery.setPalletCount(groupItems.size());
delivery.setTotalVolumeCbm(groupItems.stream().map(DispatchItem::getVolumeCbm).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
delivery.setTotalWeightKg(totalWeight(customerId, groupItems)); delivery.setExpectedArrivalAt(dispatch.getEstimatedArrivalAt()); delivery.setRemark(trim(request.remark()));
delivery.setTotalVolumeCbm(sum(groupItems, DispatchItem::getVolumeCbm));
delivery.setTotalWeightKg(sum(groupItems, DispatchItem::getGrossWeightKg)); delivery.setExpectedArrivalAt(dispatch.getEstimatedArrivalAt()); delivery.setRemark(trim(request.remark()));
delivery.setIdempotencyKey(childKey(request.idempotencyKey(), "DEST-" + groupIndex++)); delivery.setCreatedBy(operatorId);
delivery.setCreatedAt(now); delivery.setUpdatedAt(now); deliveryOrders.insert(delivery);
recordOperation(customerId, delivery.getId(), operatorId, "CREATE_DELIVERY", "按目的FBA仓生成派送单", now);
@@ -1171,11 +1177,12 @@ public class DeliveryTransferService {
d.getDeliveryNo(), d.getStatus(), d.getDestinationWarehouseId(), d.getDestinationWarehouseCode(),
d.getDestinationWarehouseName(), value(d.getPalletCount()), value(d.getPackageCount()))).toList(), replayed);
}
private DeliveryResponses.OutboundRow outboundRow(OutboundOrder o) { return new DeliveryResponses.OutboundRow(o.getId(), o.getOutboundNo(), o.getDispatchNo(), o.getOverseasWarehouseId(), o.getLxCodes(), o.getStatus(), o.getPalletCount(), o.getPackageCount(), o.getCreatedAt(), o.getOutboundType()); }
private DeliveryResponses.OutboundRow outboundRow(OutboundOrder o) { return new DeliveryResponses.OutboundRow(o.getId(), o.getOutboundNo(), o.getDispatchNo(), o.getOverseasWarehouseId(), o.getLxCodes(), o.getStatus(), o.getPalletCount(), o.getPackageCount(), o.getGrossWeightKg(), o.getVolumeWeightKg(), o.getVolumeCbm(), o.getCreatedAt(), o.getOutboundType()); }
private DeliveryResponses.OutboundPalletRow outboundPalletRow(OutboundPalletItem item) {
return new DeliveryResponses.OutboundPalletRow(item.getId(), item.getPalletNo(), item.getCartonMarks(),
item.getInboundCode(), item.getDestinationWarehouseCode(), item.getDestinationWarehouseName(),
item.getSkuCode(), value(item.getPackageCount()), value(item.getPickedPackageCount()), item.getStatus());
item.getSkuCode(), value(item.getPackageCount()), item.getGrossWeightKg(), item.getVolumeWeightKg(),
item.getVolumeCbm(), value(item.getPickedPackageCount()), item.getStatus());
}
private DeliveryResponses.OneClickCandidate oneClickCandidate(DropshipOutboundCandidate candidate) {
return new DeliveryResponses.OneClickCandidate(candidate.getInventoryPositionId(), candidate.getOrderNo(), candidate.getCustomerId(), candidate.getCustomerName(),
@@ -1235,26 +1242,9 @@ public class DeliveryTransferService {
Math.addExact(quantity, other.quantity));
}
}
private BigDecimal totalWeight(String customerId, List<DispatchItem> items) {
List<Long> palletIds = items.stream().map(DispatchItem::getWarehousePalletId)
.filter(Objects::nonNull).distinct().toList();
List<WarehouseTransferPallet> transferPallets = palletIds.isEmpty() ? List.of()
: warehouseTransferPallets.selectList(new LambdaQueryWrapper<WarehouseTransferPallet>()
.eq(WarehouseTransferPallet::getCustomerId, customerId)
.in(WarehouseTransferPallet::getWarehousePalletId, palletIds));
Set<Long> measuredPalletIds = transferPallets.stream()
.map(WarehouseTransferPallet::getWarehousePalletId).collect(java.util.stream.Collectors.toSet());
BigDecimal measuredWeight = transferPallets.stream().map(WarehouseTransferPallet::getGrossWeightKg)
.filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
List<Long> orderIds = items.stream()
.filter(item -> item.getWarehousePalletId() == null || !measuredPalletIds.contains(item.getWarehousePalletId()))
.map(DispatchItem::getOrderId).filter(Objects::nonNull).distinct().toList();
if (orderIds.isEmpty()) return measuredWeight;
BigDecimal orderWeight = tailOrderDetails.selectList(new LambdaQueryWrapper<LastlegOrderDetail>()
.eq(LastlegOrderDetail::getCustomerId, customerId).in(LastlegOrderDetail::getOrderId, orderIds))
.stream().map(LastlegOrderDetail::getGrossWeightKg).filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return measuredWeight.add(orderWeight);
private static BigDecimal sum(List<DispatchItem> items,
java.util.function.Function<DispatchItem, BigDecimal> getter) {
return items.stream().map(getter).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
}
private static String trim(String value) { return StringUtils.hasText(value) ? value.trim() : null; }
private String hash(Object value) {
@@ -17,6 +17,7 @@ public record DispatchResponse(Long id, String dispatchNo, String vehiclePlate,
String overseasWarehouseId, String overseasWarehouseName,
String orderNo, String orderRefs, String inboundCode, String destinationWarehouseCode,
String destinationWarehouseName, String fbaWarehouseCode, int packageCount,
BigDecimal grossWeightKg, BigDecimal volumeWeightKg,
BigDecimal volumeCbm, String sourceStage) {
}
@@ -36,6 +36,8 @@ public class DispatchItem {
private String contactName;
private String contactPhone;
private Integer packageCount;
private BigDecimal grossWeightKg;
private BigDecimal volumeWeightKg;
private BigDecimal volumeCbm;
/** PLAN for planned pallets; RECEIVED for warehouse/entity pallets. */
private String sourceStage;
@@ -410,6 +410,8 @@ public class DispatchService {
item.setContactName(candidate.contactName());
item.setContactPhone(candidate.contactPhone());
item.setPackageCount(Objects.requireNonNullElse(candidate.packageCount(), 0));
item.setGrossWeightKg(Objects.requireNonNullElse(candidate.grossWeightKg(), BigDecimal.ZERO));
item.setVolumeWeightKg(Objects.requireNonNullElse(candidate.volumeWeightKg(), BigDecimal.ZERO));
item.setVolumeCbm(Objects.requireNonNullElse(candidate.volumeCbm(), BigDecimal.ZERO));
item.setSourceStage(candidate.sourceStage());
item.setCartonMarks(mergeCartonMarks(candidate.cartonMarks(), candidate.relabelCartonMarks()));
@@ -457,7 +459,7 @@ public class DispatchService {
DispatchOrder dispatch = new DispatchOrder(); dispatch.setId(IdWorker.getId()); dispatch.setCustomerId(customerId);
dispatch.setDispatchNo(orderNoGenerator.next("DISPATCH", warehouse, dispatch.getId())); dispatch.setOverseasWarehouseId(warehouse); dispatch.setOverseasWarehouseName(selected.get(0).overseasWarehouseName());
dispatch.setRoute(candidateRoute(selected)); dispatch.setFbaAppointmentNo(trimToNull(request.fbaAppointmentNo())); dispatch.setFbaAppointmentStatus(defaultStatus(request.fbaAppointmentStatus())); dispatch.setVehicleAppointmentStatus(defaultStatus(request.vehicleAppointmentStatus())); dispatch.setStatus(PENDING_DEPARTURE); dispatch.setItemCount(selected.size()); dispatch.setPalletCount(selected.size()); dispatch.setPackageCount(selected.stream().map(DispatchPalletCandidate::packageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum()); dispatch.setPickedPackageCount(0); dispatch.setShippedPackageCount(0); dispatch.setCreatedBy(trimToNull(operatorId)); dispatch.setCreatedAt(now); dispatch.setUpdatedAt(now); orders.insert(dispatch);
for (DispatchPalletCandidate candidate : selected) { DispatchItem item = new DispatchItem(); item.setId(IdWorker.getId()); item.setDispatchOrderId(dispatch.getId()); item.setCustomerId(customerId); item.setWarehousePalletId(candidate.warehousePalletId()); item.setOrderId(candidate.orderId()); item.setOverseasWarehouseId(candidate.overseasWarehouseId()); item.setOverseasWarehouseName(candidate.overseasWarehouseName()); item.setOrderNo(candidate.orderNo()); item.setOrderRefs(candidate.orderRefs()); item.setInboundCode(candidate.inboundCode()); item.setPalletNo(candidate.palletNo()); item.setDestinationWarehouseCode(candidate.destinationWarehouseCode()); item.setDestinationWarehouseName(candidate.destinationWarehouseName()); item.setFbaWarehouseCode(candidate.fbaWarehouseCode()); item.setPackageCount(Objects.requireNonNullElse(candidate.packageCount(), 0)); item.setVolumeCbm(Objects.requireNonNullElse(candidate.volumeCbm(), BigDecimal.ZERO)); item.setSourceStage("RELABEL"); item.setCartonMarks(candidate.relabelCartonMarks()); item.setSourceBusinessType("RELABEL_ORDER"); item.setSourceBusinessNo(request.relabelNo()); item.setCreatedAt(now); items.insert(item); }
for (DispatchPalletCandidate candidate : selected) { DispatchItem item = new DispatchItem(); item.setId(IdWorker.getId()); item.setDispatchOrderId(dispatch.getId()); item.setCustomerId(customerId); item.setWarehousePalletId(candidate.warehousePalletId()); item.setOrderId(candidate.orderId()); item.setOverseasWarehouseId(candidate.overseasWarehouseId()); item.setOverseasWarehouseName(candidate.overseasWarehouseName()); item.setOrderNo(candidate.orderNo()); item.setOrderRefs(candidate.orderRefs()); item.setInboundCode(candidate.inboundCode()); item.setPalletNo(candidate.palletNo()); item.setDestinationWarehouseCode(candidate.destinationWarehouseCode()); item.setDestinationWarehouseName(candidate.destinationWarehouseName()); item.setFbaWarehouseCode(candidate.fbaWarehouseCode()); item.setPackageCount(Objects.requireNonNullElse(candidate.packageCount(), 0)); item.setGrossWeightKg(Objects.requireNonNullElse(candidate.grossWeightKg(), BigDecimal.ZERO)); item.setVolumeWeightKg(Objects.requireNonNullElse(candidate.volumeWeightKg(), BigDecimal.ZERO)); item.setVolumeCbm(Objects.requireNonNullElse(candidate.volumeCbm(), BigDecimal.ZERO)); item.setSourceStage("RELABEL"); item.setCartonMarks(candidate.relabelCartonMarks()); item.setSourceBusinessType("RELABEL_ORDER"); item.setSourceBusinessNo(request.relabelNo()); item.setCreatedAt(now); items.insert(item); }
DispatchPdaTask task = new DispatchPdaTask(); task.setId(IdWorker.getId()); task.setCustomerId(customerId); task.setDispatchOrderId(dispatch.getId()); task.setTaskNo("DPDA" + task.getId()); task.setStatus("PDA_PENDING"); task.setWarehouseId(warehouse); task.setPushedAt(now); task.setCreatedAt(now); task.setUpdatedAt(now); pdaTasks.insert(task);
if (candidates.markRelabelPreDispatched(customerId, request.relabelNo()) != 1) throw new ResponseStatusException(HttpStatus.CONFLICT, "换标订单已被其他排车占用");
return response(dispatch, items.findByDispatchOrder(customerId, dispatch.getId()));
@@ -653,7 +655,8 @@ public class DispatchService {
item.getWarehousePalletId(), item.getPalletNo(), item.getOverseasWarehouseId(), item.getOverseasWarehouseName(),
item.getOrderNo(), item.getOrderRefs(), item.getInboundCode(),
item.getDestinationWarehouseCode(), item.getDestinationWarehouseName(), item.getFbaWarehouseCode(),
Objects.requireNonNullElse(item.getPackageCount(), 0), item.getVolumeCbm(), item.getSourceStage())).toList(),
Objects.requireNonNullElse(item.getPackageCount(), 0), item.getGrossWeightKg(),
item.getVolumeWeightKg(), item.getVolumeCbm(), item.getSourceStage())).toList(),
bolLineList == null ? List.of() : bolLineList);
}
@@ -47,6 +47,10 @@ public record LastlegOrderSummary(
Integer receivedPackageCount,
@Schema(description = "实收减预报箱数;尚未完成收货时为空")
Integer packageDifference,
@Schema(description = "头程冻结货物毛重(千克)")
BigDecimal grossWeightKg,
@Schema(description = "头程冻结货物净重(千克)")
BigDecimal netWeightKg,
BigDecimal volumeCbm,
Integer productQuantity,
BigDecimal volumeWeightKg,
@@ -22,6 +22,8 @@ public record InboundPlanListResponse(
String customerName,
Integer packageCount,
Integer pickedPackageCount,
BigDecimal grossWeightKg,
BigDecimal netWeightKg,
BigDecimal volumeCbm,
Integer palletCount,
boolean assigned,
@@ -39,7 +41,8 @@ public record InboundPlanListResponse(
public InboundPlanListResponse withReceivingWarehouse(String warehouseCode, String warehouseName) {
return new InboundPlanListResponse(lineId, planId, planNo, customerId, orderId, inboundCode, lxCode,
orderType, overseasWarehouseId, warehouseCode, warehouseName, warehouseId, this.warehouseCode,
this.warehouseName, fbaWarehouseCode, customerName, packageCount, pickedPackageCount, volumeCbm,
this.warehouseName, fbaWarehouseCode, customerName, packageCount, pickedPackageCount,
grossWeightKg, netWeightKg, volumeCbm,
palletCount, assigned, mergeAllowed, urgent, status, appointmentNo, appointmentStatus, appointmentAt,
expectedLoadingAt, dispatchStatus, createdAt, version);
}
@@ -19,6 +19,8 @@ public record InboundPlanResponse(
String status,
int lineCount,
int palletCount,
BigDecimal totalGrossWeightKg,
BigDecimal totalNetWeightKg,
BigDecimal totalVolumeCbm,
int expectedPackageCount,
int pickedPackageCount,
@@ -38,7 +40,8 @@ public record InboundPlanResponse(
) {
public record Line(Long id, Long orderId, String inboundCode, String fbaWarehouseCode, String destinationWarehouseId,
String destinationWarehouseCode, String destinationWarehouseName,
BigDecimal volumeCbm, int requiredPackageCount, int plannedPalletCount) {
BigDecimal grossWeightKg, BigDecimal netWeightKg, BigDecimal volumeCbm,
int requiredPackageCount, int plannedPalletCount) {
}
public record Pallet(Long id, String palletNo, String destinationWarehouseId,
@@ -17,6 +17,8 @@ public interface InboundPlanOrderMapper {
record ReceivingWarehouseSnapshot(String overseasWarehouseId, String overseasWarehouseCode,
String overseasWarehouseName) { }
record DetailOrderSnapshot(String orderNo, String customerName, String orderType) { }
record OrderWeightSnapshot(Long orderId, java.math.BigDecimal grossWeightKg,
java.math.BigDecimal netWeightKg) { }
record ContainerWarehouseRef(String customerId, Long orderId, String sourceContainerBookingId,
String overseasWarehouseId) { }
record ContainerPreviewOrderRow(
@@ -116,6 +118,17 @@ public interface InboundPlanOrderMapper {
DetailOrderSnapshot findDetailOrder(@Param("customerId") String customerId,
@Param("orderId") Long orderId);
@Select("<script>SELECT o.id AS orderId," +
"COALESCE(SUM(d.gross_weight_kg),MAX(i.gross_weight_kg),0) AS grossWeightKg," +
"COALESCE(SUM(d.net_weight_kg),MAX(i.net_weight_kg),0) AS netWeightKg " +
"FROM ll_order o LEFT JOIN ll_order_detail d ON d.order_id=o.id AND d.customer_id=o.customer_id " +
"LEFT JOIN ll_inbound_order i ON i.id=o.id AND i.customer_id=o.customer_id " +
"WHERE o.customer_id=#{customerId} AND o.id IN " +
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
"GROUP BY o.id</script>")
List<OrderWeightSnapshot> findOrderWeights(@Param("customerId") String customerId,
@Param("orderIds") List<Long> orderIds);
@Update("UPDATE ll_order SET inbound_order_no=#{planNo},current_status='INBOUND_PLANNED'," +
"pallet_merge_flag='MERGED',pallet_count=#{palletCount},updated_at=#{updatedAt} " +
"WHERE customer_id=#{customerId} AND id=#{orderId}")
@@ -43,6 +43,10 @@ public interface InboundPlanQueryMapper {
"l.destination_warehouse_name warehouseName,l.fba_warehouse_code fbaWarehouseCode," +
"o.customer_name customerName,l.required_package_count packageCount," +
"COALESCE((SELECT SUM(pa.picked_package_count) FROM ll_inbound_plan_pallet_allocation pa WHERE pa.line_id=l.id),0) pickedPackageCount," +
"COALESCE((SELECT SUM(d.gross_weight_kg) FROM ll_order_detail d WHERE d.order_id=l.order_id AND d.customer_id=p.customer_id)," +
"(SELECT i.gross_weight_kg FROM ll_inbound_order i WHERE i.id=l.order_id AND i.customer_id=p.customer_id),0) grossWeightKg," +
"COALESCE((SELECT SUM(d.net_weight_kg) FROM ll_order_detail d WHERE d.order_id=l.order_id AND d.customer_id=p.customer_id)," +
"(SELECT i.net_weight_kg FROM ll_inbound_order i WHERE i.id=l.order_id AND i.customer_id=p.customer_id),0) netWeightKg," +
"l.volume_cbm volumeCbm," +
"l.planned_pallet_count palletCount,p.assigned_flag assigned,p.merge_required mergeAllowed," +
"p.urgent_flag urgent,p.status status,a.appointment_no appointmentNo," +
@@ -1077,6 +1077,16 @@ public class InboundPlanService {
}
List<InboundPlanLine> lines = lineMapper.selectList(new LambdaQueryWrapper<InboundPlanLine>()
.eq(InboundPlanLine::getPlanId, plan.getId()).orderByAsc(InboundPlanLine::getId));
Map<Long, InboundPlanOrderMapper.OrderWeightSnapshot> weights = lines.isEmpty() ? Map.of()
: orderMapper.findOrderWeights(plan.getCustomerId(), lines.stream().map(InboundPlanLine::getOrderId)
.filter(Objects::nonNull).distinct().toList()).stream()
.collect(Collectors.toMap(InboundPlanOrderMapper.OrderWeightSnapshot::orderId, value -> value));
BigDecimal totalGrossWeightKg = weights.values().stream()
.map(InboundPlanOrderMapper.OrderWeightSnapshot::grossWeightKg).filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalNetWeightKg = weights.values().stream()
.map(InboundPlanOrderMapper.OrderWeightSnapshot::netWeightKg).filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
List<InboundPlanPallet> pallets = palletMapper.selectList(new LambdaQueryWrapper<InboundPlanPallet>()
.eq(InboundPlanPallet::getPlanId, plan.getId()).orderByAsc(InboundPlanPallet::getSequenceNo));
List<InboundPlanPalletAllocation> allocations = allocationMapper.selectList(
@@ -1116,11 +1126,14 @@ public class InboundPlanService {
sourceOrder == null ? null : sourceOrder.customerName(),
sourceOrder == null ? null : sourceOrder.orderType(), plan.getContainerNo(),
authoritativeWarehouse.warehouseCode(), authoritativeWarehouse.warehouseName(),
plan.getStatus(), plan.getLineCount(), plan.getPalletCount(), plan.getTotalVolumeCbm(),
plan.getStatus(), plan.getLineCount(), plan.getPalletCount(), totalGrossWeightKg,
totalNetWeightKg, plan.getTotalVolumeCbm(),
lines.stream().mapToInt(InboundPlanLine::getRequiredPackageCount).sum(),
allocations.stream().mapToInt(InboundPlanPalletAllocation::getPickedPackageCount).sum(),
lines.stream().map(line -> new InboundPlanResponse.Line(line.getId(), line.getOrderId(), line.getInboundCode(), line.getFbaWarehouseCode(),
line.getDestinationWarehouseId(), line.getDestinationWarehouseCode(), line.getDestinationWarehouseName(),
weights.containsKey(line.getOrderId()) ? weights.get(line.getOrderId()).grossWeightKg() : BigDecimal.ZERO,
weights.containsKey(line.getOrderId()) ? weights.get(line.getOrderId()).netWeightKg() : BigDecimal.ZERO,
line.getVolumeCbm(), line.getRequiredPackageCount(), line.getPlannedPalletCount())).toList(),
pallets.stream().map(pallet -> new InboundPlanResponse.Pallet(pallet.getId(), pallet.getPalletNo(),
pallet.getDestinationWarehouseId(), pallet.getDestinationWarehouseCode(), pallet.getDestinationWarehouseName(),
@@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
import java.math.BigDecimal;
@Mapper
public interface LastlegOrderMapper extends BaseMapper<LastlegOrder> {
@@ -27,6 +28,17 @@ public interface LastlegOrderMapper extends BaseMapper<LastlegOrder> {
record DispatchStatusRow(Long orderId, String dispatchStatus) { }
record OrderWeightRow(Long orderId, BigDecimal grossWeightKg, BigDecimal netWeightKg) { }
@Select("<script>SELECT o.id AS orderId," +
"COALESCE(SUM(d.gross_weight_kg),MAX(i.gross_weight_kg),0) AS grossWeightKg," +
"COALESCE(SUM(d.net_weight_kg),MAX(i.net_weight_kg),0) AS netWeightKg " +
"FROM ll_order o LEFT JOIN ll_order_detail d ON d.order_id=o.id AND d.customer_id=o.customer_id " +
"LEFT JOIN ll_inbound_order i ON i.id=o.id AND i.customer_id=o.customer_id WHERE o.id IN " +
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
"GROUP BY o.id</script>")
List<OrderWeightRow> findOrderWeights(@Param("orderIds") List<Long> orderIds);
String CONTAINER_PROGRESS_FILTER = " FROM ll_order o LEFT JOIN ll_inbound_plan p " +
"ON p.customer_id=o.customer_id AND p.source_order_id=o.id " +
"WHERE o.source_container_booking_id IS NOT NULL AND o.source_container_booking_id&lt;&gt;'' " +
@@ -48,6 +48,7 @@ import java.nio.charset.StandardCharsets;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.security.MessageDigest;
import java.util.ArrayList;
@@ -94,9 +95,11 @@ public class LastlegOrderService {
Map<Long, List<LastlegOrderMapper.PalletMergeRelationRow>> mergeRelations = palletMergeRelations(page.getRecords());
Map<String, String> containerNodes = containerNodes(page.getRecords());
Map<Long, String> dispatchStatuses = dispatchStatuses(page.getRecords());
Map<Long, LastlegOrderMapper.OrderWeightRow> weights = orderWeights(page.getRecords());
return new PageResult<>(page.getRecords().stream().map(order -> summary(order, mergeRelations.get(order.getId()),
StringUtils.hasText(order.getSourceContainerBookingId())
? containerNodes.get(order.getSourceContainerBookingId()) : null, dispatchStatuses.getOrDefault(order.getId(), "UNSCHEDULED"))).toList(),
? containerNodes.get(order.getSourceContainerBookingId()) : null,
dispatchStatuses.getOrDefault(order.getId(), "UNSCHEDULED"), weights.get(order.getId()))).toList(),
page.getCurrent(), page.getSize(), page.getTotal(), page.getPages());
}
@@ -349,7 +352,8 @@ public class LastlegOrderService {
value.getNodeCode(), value.getNodeAt(), value.getSource()))
.sorted(java.util.Comparator.comparingInt(value -> MANUAL_NODE_CODES.indexOf(value.nodeCode()))).toList();
return new LastlegOrderDetailResponse(summary(order, palletMergeRelations(List.of(order)).get(order.getId()),
containerNode(order), dispatchStatuses(List.of(order)).getOrDefault(order.getId(), "UNSCHEDULED")), manualNodes, cargo, history.stream()
containerNode(order), dispatchStatuses(List.of(order)).getOrDefault(order.getId(), "UNSCHEDULED"),
orderWeights(List.of(order)).get(order.getId())), manualNodes, cargo, history.stream()
.map(value -> new LastlegOrderDetailResponse.History(value.getId(), value.getFromStatus(),
value.getToStatus(), value.getRemark(), value.getCreatedAt())).toList(), fees, operations.stream()
.map(value -> new LastlegOrderDetailResponse.Operation(value.getId(), value.getOperationNode(),
@@ -625,8 +629,17 @@ public class LastlegOrderService {
LastlegOrderMapper.DispatchStatusRow::dispatchStatus));
}
private Map<Long, LastlegOrderMapper.OrderWeightRow> orderWeights(List<LastlegOrder> orders) {
if (orders.isEmpty()) return Map.of();
List<LastlegOrderMapper.OrderWeightRow> rows = orderMapper.findOrderWeights(
orders.stream().map(LastlegOrder::getId).toList());
if (rows == null) return Map.of();
return rows.stream().collect(Collectors.toMap(LastlegOrderMapper.OrderWeightRow::orderId, row -> row));
}
private LastlegOrderSummary summary(LastlegOrder o, List<LastlegOrderMapper.PalletMergeRelationRow> relations,
String containerNode, String dispatchStatus) {
String containerNode, String dispatchStatus,
LastlegOrderMapper.OrderWeightRow weight) {
List<LastlegOrderMapper.PalletMergeRelationRow> related = relations == null ? List.of() : relations;
return new LastlegOrderSummary(o.getId(), displayOrderNo(o), o.getOrderType(),
LastlegOrderType.labelOf(o.getOrderType()), o.getDestinationType(), o.getSourceInboundId(),
@@ -637,7 +650,8 @@ public class LastlegOrderService {
o.getAbnormal(), o.getDetained(), o.getHoldChargeable(), o.getHoldReason(),
Boolean.TRUE.equals(o.getManualNodeOverride()), Boolean.TRUE.equals(o.getManualNodeOverride()) ? "MANUAL" : "CONTAINER",
o.getVersion() == null ? 0 : o.getVersion(), o.getPalletCount(), o.getPackageCount(),
o.getReceivedPackageCount(), packageDifference(o), o.getVolumeCbm(),
o.getReceivedPackageCount(), packageDifference(o), weight == null ? BigDecimal.ZERO : weight.grossWeightKg(),
weight == null ? BigDecimal.ZERO : weight.netWeightKg(), o.getVolumeCbm(),
o.getProductQuantity(), o.getVolumeWeightKg(), o.getEstimatedArrivalAt(), o.getActualArrivalAt(),
o.getEstimatedWarehouseAt(), o.getInboundOrderNo(), dispatchStatus, o.getRelabelOrderNo(), relabelBusinessStatus(o.getRelabelStatus()),
o.getPalletMergeFlag(), related.stream().map(LastlegOrderMapper.PalletMergeRelationRow::mergeGroupNo).distinct().findFirst().orElse(null),
@@ -1389,6 +1389,8 @@ CREATE TABLE IF NOT EXISTS ll_dispatch_item (
contact_name VARCHAR(128) NULL COMMENT '私人仓联系人快照',
contact_phone VARCHAR(64) NULL COMMENT '私人仓联系电话快照',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数',
gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '毛重(千克)',
volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)',
volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)',
source_stage VARCHAR(32) NOT NULL COMMENT '数据来源阶段',
carton_marks VARCHAR(4000) NULL COMMENT '换标箱唛映射快照',
@@ -1405,6 +1407,7 @@ CREATE TABLE IF NOT EXISTS ll_dispatch_item (
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='carton_marks'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN carton_marks VARCHAR(4000) NULL COMMENT ''换标箱唛映射快照'' AFTER source_stage')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='source_business_type'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN source_business_type VARCHAR(32) NULL COMMENT ''来源业务类型'' AFTER carton_marks')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='source_business_no'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN source_business_no VARCHAR(128) NULL COMMENT ''来源业务单号'' AFTER source_business_type')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='gross_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''毛重(千克)'' AFTER package_count, ADD COLUMN volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''材积重(千克)'' AFTER gross_weight_kg')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='destination_type'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN destination_type VARCHAR(32) NULL COMMENT ''目的类型:FBA或PRIVATE'' AFTER fba_warehouse_code, ADD COLUMN delivery_address VARCHAR(1000) NULL COMMENT ''私人仓目的地址快照'' AFTER destination_type, ADD COLUMN postal_code VARCHAR(32) NULL COMMENT ''私人仓邮编快照'' AFTER delivery_address, ADD COLUMN contact_name VARCHAR(128) NULL COMMENT ''私人仓联系人快照'' AFTER postal_code, ADD COLUMN contact_phone VARCHAR(64) NULL COMMENT ''私人仓联系电话快照'' AFTER contact_name')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
CREATE TABLE IF NOT EXISTS ll_dispatch_pallet_lock (
@@ -1507,6 +1510,8 @@ CREATE TABLE IF NOT EXISTS ll_outbound_order (
outbound_no VARCHAR(64) NOT NULL COMMENT '出库单号', dispatch_order_id BIGINT NULL COMMENT '派车单ID,一件代发为空', dispatch_no VARCHAR(64) NULL COMMENT '派车单号,一件代发为空',
overseas_warehouse_id VARCHAR(128) NULL COMMENT '海外仓ID', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_OUTBOUND' COMMENT '业务状态',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', pallet_count INT NOT NULL DEFAULT 0 COMMENT '托盘数',
gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '毛重(千克)', volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)',
volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)',
idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', created_by VARCHAR(128) NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
outbound_type VARCHAR(32) NOT NULL DEFAULT 'DISPATCH_DELIVERY' COMMENT '出库类型:派送转出库、一件代发', lx_codes VARCHAR(1000) NULL COMMENT '关联一件代发领星入库单代码汇总',
@@ -1521,7 +1526,7 @@ CREATE TABLE IF NOT EXISTS ll_outbound_pallet_item (
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', outbound_order_id BIGINT NOT NULL COMMENT '出库单ID',
warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(128) NULL COMMENT '托盘号',
destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', picked_package_count INT NOT NULL DEFAULT 0 COMMENT '已拣箱数', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_PICK' COMMENT '拣货状态', sku_code VARCHAR(128) NULL COMMENT '一件代发SKU', order_refs VARCHAR(2000) NULL COMMENT '关联订单信息', inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '毛重(千克)', volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)', volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)', picked_package_count INT NOT NULL DEFAULT 0 COMMENT '已拣箱数', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_PICK' COMMENT '拣货状态', sku_code VARCHAR(128) NULL COMMENT '一件代发SKU', order_refs VARCHAR(2000) NULL COMMENT '关联订单信息', inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id),
UNIQUE KEY uk_ll_outbound_pallet (customer_id,outbound_order_id,warehouse_pallet_id,inbound_code,sku_code),
KEY idx_ll_outbound_pallet_list (customer_id,outbound_order_id), KEY idx_ll_outbound_pallet_destination (customer_id,destination_warehouse_code)
@@ -1562,6 +1567,8 @@ CREATE TABLE IF NOT EXISTS ll_outbound_pda_pick (
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='outbound_type'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN outbound_type VARCHAR(32) NOT NULL DEFAULT ''DISPATCH_DELIVERY'' COMMENT ''出库类型'' AFTER status'));
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='gross_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''毛重(千克)'' AFTER pallet_count, ADD COLUMN volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''材积重(千克)'' AFTER gross_weight_kg, ADD COLUMN volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''体积(立方米)'' AFTER volume_weight_kg')); PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='gross_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''毛重(千克)'' AFTER package_count, ADD COLUMN volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''材积重(千克)'' AFTER gross_weight_kg, ADD COLUMN volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''体积(立方米)'' AFTER volume_weight_kg')); PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='lx_codes'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN lx_codes VARCHAR(1000) NULL COMMENT ''关联一件代发领星入库单代码汇总'' AFTER outbound_type'));
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='sku_code'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN sku_code VARCHAR(128) NULL COMMENT ''一件代发SKU'''));
@@ -185,7 +185,7 @@ paths:
/api/platform/v1/last-leg/orders:
get:
summary: 分页查询尾程订单
description: 权限 platform:overseas-warehouse:last-mile-order:list;平台管理员省略 customerId 时查询全部客户。containerNode 来自稳定 sourceContainerBookingId 对应的柜级船节点门禁。
description: 权限 platform:overseas-warehouse:last-mile-order:list;平台管理员省略 customerId 时查询全部客户。containerNode 来自稳定 sourceContainerBookingId 对应的柜级船节点门禁;grossWeightKg/netWeightKg 由头程冻结货物明细聚合。
security: [{ bearerAuth: [] }]
parameters:
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
@@ -206,7 +206,7 @@ paths:
/api/platform/v1/last-leg/orders/{id}:
get:
summary: 查询尾程订单详情
description: 权限 platform:overseas-warehouse:last-mile-order:list;返回订单、货物、状态历史、费用明细和操作记录。订单中的 containerNode 来自稳定 sourceContainerBookingId 对应的柜级船节点门禁。
description: 权限 platform:overseas-warehouse:last-mile-order:list;返回订单、货物、状态历史、费用明细和操作记录。订单中的 containerNode 来自稳定 sourceContainerBookingId 对应的柜级船节点门禁;grossWeightKg/netWeightKg 由头程冻结货物明细聚合。
security: [{ bearerAuth: [] }]
parameters:
- { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
@@ -463,7 +463,7 @@ paths:
/api/platform/v1/last-leg/inbound-plans:
get:
summary: 平台分页查询海外入库订单
description: 权限 platform:overseas-warehouse:inbound-order:list;列表中的 overseasWarehouseCode/overseasWarehouseName 按入库计划冻结接柜仓ID从海外仓权威目录解析,warehouseCode/warehouseName 仍表示目的仓。
description: 权限 platform:overseas-warehouse:inbound-order:list;overseasWarehouseCode/overseasWarehouseName 只取尾程订单预分配接柜仓事实,并按入库计划冻结接柜仓ID从海外仓权威目录解析;warehouseCode/warehouseName 仍表示目的仓。
security: [{ bearerAuth: [] }]
parameters:
- { name: customerId, in: query, required: false, schema: { type: string, pattern: '^[1-9][0-9]*$' } }
@@ -2272,6 +2272,8 @@ components:
destinationWarehouseName: { type: string }
fbaWarehouseCode: { type: string }
packageCount: { type: integer }
grossWeightKg: { type: number, description: 排车托盘冻结毛重(千克) }
volumeWeightKg: { type: number, description: 排车托盘冻结材积重(千克) }
volumeCbm: { type: number }
sourceStage: { type: string, enum: [PLAN, RECEIVED] }
cartonMarks: { type: string, nullable: true, description: 该托盘箱号,逗号分隔 }
@@ -3006,7 +3008,7 @@ components:
properties: { records: { type: array, items: { $ref: '#/components/schemas/InboundPlanSummary' } } }
InboundPlanSummary:
type: object
required: [lineId, planId, planNo, customerId, orderId, inboundCode, overseasWarehouseCode, overseasWarehouseName, packageCount, pickedPackageCount, palletCount, assigned, mergeAllowed, urgent, status, createdAt, version]
required: [lineId, planId, planNo, customerId, orderId, inboundCode, overseasWarehouseCode, overseasWarehouseName, packageCount, pickedPackageCount, grossWeightKg, netWeightKg, palletCount, assigned, mergeAllowed, urgent, status, createdAt, version]
properties:
lineId: { type: string, pattern: '^[0-9]+$' }
planId: { type: string, pattern: '^[0-9]+$' }
@@ -3025,6 +3027,8 @@ components:
customerName: { type: string, nullable: true }
packageCount: { type: integer }
pickedPackageCount: { type: integer }
grossWeightKg: { type: number, description: 头程冻结货物毛重(千克) }
netWeightKg: { type: number, description: 头程冻结货物净重(千克) }
volumeCbm: { type: number, nullable: true }
palletCount: { type: integer }
assigned: { type: boolean, description: 入库计划是否已分配 }
@@ -3040,7 +3044,7 @@ components:
version: { type: integer }
InboundPlanDetail:
type: object
required: [id, planNo, customerId, overseasWarehouseCode, overseasWarehouseName, status, lineCount, palletCount, totalVolumeCbm, expectedPackageCount, pickedPackageCount, lines, pallets, allocations, version, createdAt, updatedAt]
required: [id, planNo, customerId, overseasWarehouseCode, overseasWarehouseName, status, lineCount, palletCount, totalGrossWeightKg, totalNetWeightKg, totalVolumeCbm, expectedPackageCount, pickedPackageCount, lines, pallets, allocations, version, createdAt, updatedAt]
properties:
id: { type: string, pattern: '^[0-9]+$' }
planNo: { type: string }
@@ -3055,6 +3059,8 @@ components:
status: { type: string }
lineCount: { type: integer }
palletCount: { type: integer }
totalGrossWeightKg: { type: number, description: 入库单关联头程货物毛重合计(千克) }
totalNetWeightKg: { type: number, description: 入库单关联头程货物净重合计(千克) }
totalVolumeCbm: { type: number }
expectedPackageCount: { type: integer }
pickedPackageCount: { type: integer }
@@ -3148,6 +3154,8 @@ components:
destinationWarehouseId: { type: string, nullable: true }
destinationWarehouseCode: { type: string, nullable: true }
destinationWarehouseName: { type: string, nullable: true }
grossWeightKg: { type: number, description: 本入仓码头程冻结货物毛重(千克) }
netWeightKg: { type: number, description: 本入仓码头程冻结货物净重(千克) }
volumeCbm: { type: number }
requiredPackageCount: { type: integer }
plannedPalletCount: { type: integer }
@@ -142,6 +142,8 @@ class DeliveryTransferServiceTest {
assertEquals("WAIT_OUTBOUND", outbound.getValue().getStatus());
assertEquals(2, outbound.getValue().getPalletCount());
assertEquals(20, outbound.getValue().getPackageCount());
assertEquals(new BigDecimal("20"), outbound.getValue().getGrossWeightKg());
assertEquals(new BigDecimal("2.0"), outbound.getValue().getVolumeCbm());
assertEquals("transfer-101", outbound.getValue().getIdempotencyKey());
assertTrue(outbound.getValue().getOutboundNo().matches("OB-WH-\\d{8}-\\d{6}"));
assertTrue(delivery.getAllValues().stream().allMatch(value -> outbound.getValue().getId().equals(value.getOutboundOrderId())));
@@ -152,6 +154,10 @@ class DeliveryTransferServiceTest {
.map(OutboundPalletItem::getDestinationWarehouseCode).toList());
assertEquals(List.of("IN-A", "IN-B"), pallets.getAllValues().stream()
.map(OutboundPalletItem::getInboundCode).toList());
assertEquals(List.of(new BigDecimal("12"), new BigDecimal("8")), pallets.getAllValues().stream()
.map(OutboundPalletItem::getGrossWeightKg).toList());
assertEquals(List.of(new BigDecimal("12"), new BigDecimal("8")), delivery.getAllValues().stream()
.map(DeliveryOrder::getTotalWeightKg).toList());
assertEquals(20, result.packageCount());
assertEquals(2, result.palletCount());
assertEquals(2, result.deliveries().size());
@@ -929,6 +935,9 @@ class DeliveryTransferServiceTest {
value.setDestinationWarehouseId("DEST-" + destinationCode);
value.setDestinationWarehouseCode(destinationCode); value.setDestinationWarehouseName(destinationName);
value.setInboundCode(inboundCode); value.setOrderRefs("LMO-1"); value.setPackageCount(packages);
value.setGrossWeightKg(BigDecimal.valueOf(packages));
value.setVolumeWeightKg(BigDecimal.valueOf(packages * 2L));
value.setVolumeCbm(BigDecimal.valueOf(packages, 1));
value.setSourceStage("RECEIVED");
return value;
}
@@ -434,7 +434,7 @@ class ContainerInboundPlanConversionCoordinatorTest {
private InboundPlanResponse plan(String planNo, List<InboundPlanResponse.Pallet> pallets) {
return new InboundPlanResponse(21L, planNo, "100", 11L, "FL-WH-1", "验收客户", "ONE_CLICK_DROPSHIP",
"MSCU6639870", "US-LAX-01",
"Los Angeles", "PENDING_ASSIGNMENT", 1, 1, BigDecimal.ONE, 10, 0,
"Los Angeles", "PENDING_ASSIGNMENT", 1, 1, BigDecimal.TEN, new BigDecimal("8"), BigDecimal.ONE, 10, 0,
List.of(), pallets, List.of(), 1, LocalDate.of(2026, 9, 10), "WOOD", false,
false, false, "MANUAL", "自动生成", null, null);
}
@@ -52,7 +52,8 @@ class InboundPlanPaginationTest {
LocalDateTime.of(2026, 8, 1, 0, 0), LocalDateTime.of(2026, 8, 31, 23, 59));
InboundPlanListResponse row = new InboundPlanListResponse(9007199254740993L, 2L, "LIP2", "100",
3L, "IN-1", null, "FIRST_LEG", 193000000000200003L, "ACY2", "错误目的仓", "W1", "ONT", "安大略仓",
"ONT8", "客户A", 10, 0, BigDecimal.ONE, 1, true, true, true, "PENDING_ASSIGNMENT", null, null, null,
"ONT8", "客户A", 10, 0, new BigDecimal("30"), new BigDecimal("22"), BigDecimal.ONE,
1, true, true, true, "PENDING_ASSIGNMENT", null, null, null,
null, "UNSCHEDULED", LocalDateTime.of(2026, 8, 20, 10, 0), 0);
when(queryMapper.count("100", query)).thenReturn(11L);
when(queryMapper.rows("100", query, 10, 10)).thenReturn(List.of(row));
@@ -87,6 +88,7 @@ class InboundPlanPaginationTest {
"p.overseas_warehouse_id=#{q.overseasWarehouseId}");
assertThat(sql).contains("p.assigned_flag assigned", "p.merge_required mergeAllowed",
"p.urgent_flag urgent");
assertThat(sql).contains("grossWeightKg", "netWeightKg", "ll_order_detail");
assertThat(sql).doesNotContain("l.destination_warehouse_id=#{q.overseasWarehouseId}");
assertThat(sql).contains("q.planNo", "q.inboundCode", "q.status", "q.overseasWarehouseId",
"q.destinationWarehouseCode", "q.warehouse",
@@ -94,10 +94,13 @@ class LastlegOrderServiceTest {
order.setSourceContainerBookingId("BOOKING-1");
LastlegOrderSummary summary = ReflectionTestUtils.invokeMethod(
service, "summary", order, List.of(), null, "UNSCHEDULED");
service, "summary", order, List.of(), null, "UNSCHEDULED",
new LastlegOrderMapper.OrderWeightRow(11L, new java.math.BigDecimal("30"), new java.math.BigDecimal("22")));
assertNotNull(summary);
assertEquals("BOOKING-1", summary.sourceContainerBookingId());
assertEquals(new java.math.BigDecimal("30"), summary.grossWeightKg());
assertEquals(new java.math.BigDecimal("22"), summary.netWeightKg());
}
@BeforeEach