This commit is contained in:
2026-09-16 17:49:20 +08:00
parent 392f5b7386
commit 8488d4d989
10 changed files with 148 additions and 60 deletions
@@ -216,12 +216,19 @@ public interface DropshipPalletizationMapper {
""") List<CartonRow> cartons(@Param("palletId") long palletId);
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.task_id=#{taskId} AND c.pallet_id IS NULL ORDER BY c.counted_at,c.id") List<CartonRow> unboundCartons(@Param("taskId") long taskId);
@Select("""
SELECT COALESCE(SUM(o.actual_gross_weight_kg/NULLIF(o.total_package_count,0)),0) weight_kg,
COALESCE(SUM(o.actual_volume_cbm/NULLIF(o.total_package_count,0)),0) volume_cbm
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}""")
PalletMetrics metrics(@Param("palletId") long palletId);
<script>
SELECT c.pallet_id,COUNT(DISTINCT c.id) carton_count,
COALESCE(SUM(o.actual_gross_weight_kg/NULLIF(o.total_package_count,0)),0) gross_weight_kg,
COALESCE(SUM(o.actual_net_weight_kg/NULLIF(o.total_package_count,0)),0) net_weight_kg,
COALESCE(SUM(o.actual_volume_cbm/NULLIF(o.total_package_count,0)),0) volume_cbm
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 IN
<foreach collection="palletIds" item="palletId" open="(" separator="," close=")">#{palletId}</foreach>
GROUP BY c.pallet_id
</script>
""")
List<PalletMetrics> physicalMetricsByPalletIds(@Param("palletIds") Collection<Long> palletIds);
@Select("""
SELECT c.tenant_id,c.inbound_id,o.inbound_code,
@@ -340,7 +347,8 @@ public interface DropshipPalletizationMapper {
@Getter @Setter class LabelSkuRow { private Long inboundDetailId; private String skuCode,cargoName; private Integer expectedQuantity,lineNo; }
@Getter @Setter class CartonRow { private Long id,taskId,palletId,inboundId,inboundDetailId; private String palletNo,inboundCode,skuCode,cargoName,boxCode,discrepancyStatus,discrepancyRemark; private Integer expectedSkuQuantity,actualSkuQuantity; }
@Getter @Setter class CartonSkuRow { private Long inboundDetailId; private String skuCode,cargoName,discrepancyStatus,discrepancyRemark; private Integer expectedQuantity,actualQuantity,lineNo; }
record PalletMetrics(BigDecimal weightKg,BigDecimal volumeCbm) {}
record PalletMetrics(Long palletId,Integer cartonCount,BigDecimal grossWeightKg,
BigDecimal netWeightKg,BigDecimal volumeCbm) {}
@Getter @Setter class PrintInboundRow { private Long tenantId,inboundId; private String inboundCode,destinationWarehouse; private Integer cartonCount,itemQuantity; }
@Getter @Setter class PrintSkuRow { private String skuCode; private Integer cartonCount,itemQuantity,minUnitsPerCarton,maxUnitsPerCarton; }
@Getter @Setter class PalletLine { private Long palletId,tenantId,accountId,inboundId,orderId,warehouseId,destinationWarehouseId,inboundDetailId; private String palletNo,palletStatus,orderNo,inboundCode,channelCode,channelName,destinationWarehouseCode,destinationWarehouseName,customerName,skuCode,cargoName; private Integer cartonCount,actualSkuQuantity; private BigDecimal unitGrossWeightKg,unitNetWeightKg,unitVolumeCbm; }
@@ -45,10 +45,33 @@ public interface LoadingListQueryMapper {
(SELECT pick_task.task_status FROM fl_loading_pick_task pick_task
WHERE pick_task.loading_plan_id = p.id LIMIT 1) AS pda_task_status,
COUNT(DISTINCT NULLIF(i.sku_code, '')) AS sku_count,
COALESCE(SUM(i.package_count), 0) AS total_package_count,
COALESCE(SUM(i.gross_weight_kg), 0) AS total_gross_weight_kg,
COALESCE(SUM(i.net_weight_kg), 0) AS total_net_weight_kg,
COALESCE(SUM(i.volume_cbm), 0) AS total_volume_cbm,
COALESCE(SUM(CASE WHEN i.pallet_id IS NULL THEN i.package_count ELSE 0 END), 0)
+ COALESCE((SELECT COUNT(DISTINCT carton.id)
FROM fl_dropship_pallet_carton carton
WHERE EXISTS (SELECT 1 FROM fl_loading_plan_item pallet_item
WHERE pallet_item.loading_plan_id=p.id AND pallet_item.deleted=0
AND pallet_item.pallet_id=carton.pallet_id)), 0) AS total_package_count,
COALESCE(SUM(CASE WHEN i.pallet_id IS NULL THEN i.gross_weight_kg ELSE 0 END), 0)
+ COALESCE((SELECT SUM(inbound.actual_gross_weight_kg/NULLIF(inbound.total_package_count,0))
FROM fl_dropship_pallet_carton carton
JOIN fl_inbound_order inbound ON inbound.id=carton.inbound_id AND inbound.tenant_id=carton.tenant_id
WHERE EXISTS (SELECT 1 FROM fl_loading_plan_item pallet_item
WHERE pallet_item.loading_plan_id=p.id AND pallet_item.deleted=0
AND pallet_item.pallet_id=carton.pallet_id)), 0) AS total_gross_weight_kg,
COALESCE(SUM(CASE WHEN i.pallet_id IS NULL THEN i.net_weight_kg ELSE 0 END), 0)
+ COALESCE((SELECT SUM(inbound.actual_net_weight_kg/NULLIF(inbound.total_package_count,0))
FROM fl_dropship_pallet_carton carton
JOIN fl_inbound_order inbound ON inbound.id=carton.inbound_id AND inbound.tenant_id=carton.tenant_id
WHERE EXISTS (SELECT 1 FROM fl_loading_plan_item pallet_item
WHERE pallet_item.loading_plan_id=p.id AND pallet_item.deleted=0
AND pallet_item.pallet_id=carton.pallet_id)), 0) AS total_net_weight_kg,
COALESCE(SUM(CASE WHEN i.pallet_id IS NULL THEN i.volume_cbm ELSE 0 END), 0)
+ COALESCE((SELECT SUM(inbound.actual_volume_cbm/NULLIF(inbound.total_package_count,0))
FROM fl_dropship_pallet_carton carton
JOIN fl_inbound_order inbound ON inbound.id=carton.inbound_id AND inbound.tenant_id=carton.tenant_id
WHERE EXISTS (SELECT 1 FROM fl_loading_plan_item pallet_item
WHERE pallet_item.loading_plan_id=p.id AND pallet_item.deleted=0
AND pallet_item.pallet_id=carton.pallet_id)), 0) AS total_volume_cbm,
COALESCE(SUM(i.product_quantity), 0) AS total_quantity
""";
@@ -158,11 +158,12 @@ public class LoadingListRepository {
java.util.ArrayList<String> blockers = new java.util.ArrayList<>();
if (!editing) blockers.add("LOADING_PLAN_NOT_EDITING");
if (linkCount == 0) blockers.add("PRELOAD_LINK_MISSING");
if (!containerReady) blockers.add("CONTAINER_NO_MISSING");
if (!oceanLinked) blockers.add("OCEAN_BOOKING_NOT_LINKED");
else if (!released) blockers.add("OCEAN_BOOKING_NOT_RELEASED");
if (taskCreated) blockers.add("PDA_TASK_ALREADY_CREATED");
boolean allConfirmed = linkCount > 0 && confirmedCount == linkCount;
boolean prerequisitesReady = editing && linkCount > 0 && released;
boolean prerequisitesReady = editing && linkCount > 0 && containerReady && released;
boolean canConfirm = prerequisitesReady && !taskCreated && !allConfirmed;
if (allConfirmed) blockers.add("PRELOAD_ALREADY_CONFIRMED");
String confirmationStatus = allConfirmed ? "CONFIRMED"
@@ -231,10 +231,10 @@ public class DropshipPalletizationService {
values.stream().mapToInt(v->v.line().getActualQuantity()==null?0:v.line().getActualQuantity()).sum(),
values.stream().map(v->v.carton().getBoxCode()).toList());}).toList();
int discrepancies=(int)cartons.stream().filter(c->"ACCEPTED_VARIANCE".equals(c.getDiscrepancyStatus())).count();
PalletMetrics metrics=mapper.metrics(row.getId());
PalletMetrics metrics=mapper.physicalMetricsByPalletIds(List.of(row.getId())).stream().findFirst().orElse(null);
int skuCount=(int)items.stream().map(SkuItem::skuCode).distinct().count();
return new Pallet(row.getId(),row.getPalletNo(),row.getPalletStatus(),cartons.size(),skuCount,discrepancies,
metrics==null?BigDecimal.ZERO:metrics.weightKg(),metrics==null?BigDecimal.ZERO:metrics.volumeCbm(),row.getVersion(),items,
metrics==null?BigDecimal.ZERO:metrics.grossWeightKg(),metrics==null?BigDecimal.ZERO:metrics.volumeCbm(),row.getVersion(),items,
cartons.stream().map(c->cartonView(c,c.getInboundCode(),false,null)).toList(),row.getCompletedAt());
}
@@ -116,6 +116,7 @@ public class LoadingPickService {
public void beginIfReady(long containerBookingId, long operatorId, String operatorName) {
var booking = smartLoadingRepository.booking(containerBookingId);
if (booking == null) return;
if (!StringUtils.hasText(booking.getContainerNo())) return;
var plan = smartLoadingRepository.planByBooking(containerBookingId);
if (plan == null || !"EDITING".equals(plan.getPlanStatus())) return;
inboundHoldGuard.requirePlanNotHeld(plan.getId());
@@ -137,6 +138,10 @@ public class LoadingPickService {
if (!Set.of("PENDING", "CONFIRMING").contains(link.preloadStatus())) {
throw conflict("预装清单已确认或当前状态不允许确认");
}
var booking = smartLoadingRepository.booking(link.containerBookingId());
if (booking == null || !StringUtils.hasText(booking.getContainerNo())) {
throw conflict("确认预装前必须录入真实集装箱号");
}
var allocation = oceanBookingRepository.allocationByContainer(link.containerBookingId());
if (allocation == null) throw conflict("确认预装前必须关联真实订舱柜");
var oceanBooking = oceanBookingRepository.find(allocation.getOceanBookingId());
@@ -476,19 +476,28 @@ public class SmartLoadingService {
var first = lines.get(0);
inboundHoldGuard.requireInboundNotHeld(first.getAccountId(), first.getInboundId());
if (!repository.activeItemsByPallet(request.palletId()).isEmpty()) throw conflict("托盘已被有效柜次占用");
com.baiyue.firstleg.mapper.DropshipPalletizationMapper.PalletMetrics physical =
dropshipPallets.physicalMetricsByPalletIds(List.of(request.palletId())).stream().findFirst()
.orElseThrow(() -> conflict("托盘缺少物理箱明细,不能加入预装清单"));
int sequence = 0;
for (var line : lines) {
boolean primaryPalletLine = sequence == 0;
BigDecimal lineBoxes = BigDecimal.valueOf(line.getCartonCount());
LoadingPlanEntity current = repository.plan(planId);
Candidate candidate = new Candidate(line.getAccountId(), line.getInboundId(), line.getInboundDetailId(),
line.getOrderId(), line.getOrderNo(), line.getInboundCode(), line.getWarehouseId(), line.getChannelCode(),
line.getChannelName(), line.getDestinationWarehouseId(), line.getDestinationWarehouseCode(),
line.getDestinationWarehouseName(), line.getCustomerName(), line.getCargoName(), line.getCartonCount(),
line.getUnitGrossWeightKg(), line.getUnitNetWeightKg(), line.getUnitVolumeCbm(), null, null, null, null,
primaryPalletLine ? unit(physical.grossWeightKg()).divide(lineBoxes, 6, RoundingMode.HALF_UP) : BigDecimal.ZERO,
primaryPalletLine ? unit(physical.netWeightKg()).divide(lineBoxes, 6, RoundingMode.HALF_UP) : BigDecimal.ZERO,
primaryPalletLine ? unit(physical.volumeCbm()).divide(lineBoxes, 6, RoundingMode.HALF_UP) : BigDecimal.ZERO,
null, null, null, null,
false, null, "WAIT_SPLIT", null);
String childOperation = "PAL-" + sha256(operation + '|' + line.getInboundDetailId()).substring(0, 48);
assign(planId, new AssignRequest(line.getInboundId(), line.getInboundDetailId(), line.getCartonCount(),
current.getVersion(), childOperation), operatorId, operatorName, true, request.palletId(),
line.getPalletNo(), line.getActualSkuQuantity(), sequence++ == 0, candidate, false);
line.getPalletNo(), line.getActualSkuQuantity(), primaryPalletLine, candidate, false);
sequence++;
}
LoadingPlanEntity assigned = repository.plan(planId);
history(planId, null, "ADD_PALLET", original.getPlanStatus(), assigned.getPlanStatus(), operation,
@@ -1689,19 +1698,26 @@ public class SmartLoadingService {
Map<Long,List<LoadingPlanItemEntity>> pallets = items.stream().filter(i -> i.getPalletId() != null)
.collect(java.util.stream.Collectors.groupingBy(LoadingPlanItemEntity::getPalletId,
LinkedHashMap::new, java.util.stream.Collectors.toList()));
Map<Long,com.baiyue.firstleg.mapper.DropshipPalletizationMapper.PalletMetrics> physicalByPallet =
pallets.isEmpty() ? Map.of() : dropshipPallets.physicalMetricsByPalletIds(pallets.keySet()).stream()
.collect(java.util.stream.Collectors.toMap(
com.baiyue.firstleg.mapper.DropshipPalletizationMapper.PalletMetrics::palletId,
java.util.function.Function.identity()));
items.stream().filter(i -> i.getPalletId() == null).forEach(result::add);
for (List<LoadingPlanItemEntity> lines : pallets.values()) {
LoadingPlanItemEntity unit = lines.get(0);
unit.setPackageCount(lines.stream().map(LoadingPlanItemEntity::getPackageCount)
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
var physical = physicalByPallet.get(unit.getPalletId());
if (physical == null) throw conflict("托盘缺少物理箱明细,不能汇总预装清单");
unit.setPackageCount(physical.cartonCount());
unit.setProductQuantity(lines.stream().map(LoadingPlanItemEntity::getProductQuantity)
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
unit.setPalletCount(1);
unit.setGrossWeightKg(sumDecimal(lines, LoadingPlanItemEntity::getGrossWeightKg));
unit.setNetWeightKg(sumDecimal(lines, LoadingPlanItemEntity::getNetWeightKg));
unit.setVolumeCbm(sumDecimal(lines, LoadingPlanItemEntity::getVolumeCbm));
unit.setPickedPackageCount(lines.stream().map(LoadingPlanItemEntity::getPickedPackageCount)
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
unit.setGrossWeightKg(physical.grossWeightKg());
unit.setNetWeightKg(physical.netWeightKg());
unit.setVolumeCbm(physical.volumeCbm());
unit.setPickedPackageCount(Math.min(physical.cartonCount(), lines.stream()
.map(LoadingPlanItemEntity::getPickedPackageCount).filter(Objects::nonNull)
.mapToInt(Integer::intValue).sum()));
unit.setTotalPackageCount(unit.getPackageCount());
unit.setRemainingPackageCount(0);
unit.setCanAdjust(false);
@@ -1713,10 +1729,6 @@ public class SmartLoadingService {
.thenComparing(LoadingPlanItemEntity::getId, Comparator.nullsLast(Long::compareTo)))
.toList();
}
private static BigDecimal sumDecimal(List<LoadingPlanItemEntity> lines,
java.util.function.Function<LoadingPlanItemEntity,BigDecimal> getter) {
return lines.stream().map(getter).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
}
private int adjustmentTotal(LoadingPlanItemEntity item) {
int current = item.getPackageCount() == null ? 0 : item.getPackageCount();
try {