fix: 按物理箱贯通一件代发履约箱数

This commit is contained in:
2026-09-16 20:49:35 +08:00
parent 8797c0bef9
commit 545ad2083d
34 changed files with 779 additions and 132 deletions
@@ -21,18 +21,44 @@ public interface LoadingModelBridgeMapper extends BaseMapper<PreloadLoadingPlanL
+ "ON b.id=p.container_booking_id WHERE p.id=#{loadingPlanId}")
LoadingPlanSnapshot loadingPlan(@Param("loadingPlanId") long loadingPlanId);
@Select("SELECT COALESCE(SUM(package_count),0) FROM fl_loading_plan_item "
+ "WHERE loading_plan_id=#{loadingPlanId} AND account_id=#{tenantId} "
+ "AND inbound_id=#{inboundId} AND deleted=0")
@Select("""
SELECT COALESCE(SUM(CASE WHEN item.pallet_id IS NULL THEN item.package_count ELSE 0 END),0)
+ COALESCE((SELECT COUNT(DISTINCT carton.id)
FROM fl_dropship_pallet_carton carton
WHERE carton.tenant_id=#{tenantId} AND carton.inbound_id=#{inboundId}
AND EXISTS (SELECT 1 FROM fl_loading_plan_item pallet_item
WHERE pallet_item.loading_plan_id=#{loadingPlanId}
AND pallet_item.account_id=#{tenantId}
AND pallet_item.inbound_id=#{inboundId}
AND pallet_item.pallet_id=carton.pallet_id
AND pallet_item.deleted=0)),0)
FROM fl_loading_plan_item item
WHERE item.loading_plan_id=#{loadingPlanId} AND item.account_id=#{tenantId}
AND item.inbound_id=#{inboundId} AND item.deleted=0
""")
int allocatedPackages(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
@Param("loadingPlanId") long loadingPlanId);
@Select("SELECT item.account_id tenantId,item.inbound_id inboundId,"
+ "COALESCE(SUM(item.package_count),0) allocatedPackageCount,MAX(inbound.process_status) processStatus "
+ "FROM fl_loading_plan_item item JOIN fl_inbound_order inbound "
+ "ON inbound.tenant_id=item.account_id AND inbound.id=item.inbound_id AND inbound.deleted=0 "
+ "WHERE item.loading_plan_id=#{loadingPlanId} AND item.deleted=0 "
+ "GROUP BY item.account_id,item.inbound_id HAVING SUM(item.package_count)>0")
@Select("""
SELECT item.account_id tenantId,item.inbound_id inboundId,
COALESCE(SUM(CASE WHEN item.pallet_id IS NULL THEN item.package_count ELSE 0 END),0)
+ COALESCE((SELECT COUNT(DISTINCT carton.id)
FROM fl_dropship_pallet_carton carton
WHERE carton.tenant_id=item.account_id AND carton.inbound_id=item.inbound_id
AND EXISTS (SELECT 1 FROM fl_loading_plan_item pallet_item
WHERE pallet_item.loading_plan_id=#{loadingPlanId}
AND pallet_item.account_id=item.account_id
AND pallet_item.inbound_id=item.inbound_id
AND pallet_item.pallet_id=carton.pallet_id
AND pallet_item.deleted=0)),0) allocatedPackageCount,
MAX(inbound.process_status) processStatus
FROM fl_loading_plan_item item
JOIN fl_inbound_order inbound ON inbound.tenant_id=item.account_id
AND inbound.id=item.inbound_id AND inbound.deleted=0
WHERE item.loading_plan_id=#{loadingPlanId} AND item.deleted=0
GROUP BY item.account_id,item.inbound_id
HAVING allocatedPackageCount>0
""")
List<AllocationScope> allocatedScopes(@Param("loadingPlanId") long loadingPlanId);
@Update("UPDATE fl_preload_plan SET updated_by=#{operatorId},updated_by_name=#{operatorName},"
@@ -76,9 +102,15 @@ public interface LoadingModelBridgeMapper extends BaseMapper<PreloadLoadingPlanL
preload.version preloadVersion,link.loading_plan_id loadingPlanId,
plan.container_booking_id containerBookingId,booking.container_no containerNo,
plan.plan_status loadingPlanStatus,plan.version loadingPlanVersion,
COALESCE((SELECT SUM(item.package_count) FROM fl_loading_plan_item item
WHERE item.loading_plan_id=link.loading_plan_id AND item.account_id=link.tenant_id
AND item.inbound_id=link.inbound_id AND item.deleted=0),0) allocatedPackageCount,
COALESCE((SELECT SUM(CASE WHEN item.pallet_id IS NULL THEN item.package_count ELSE 0 END)
FROM fl_loading_plan_item item WHERE item.loading_plan_id=link.loading_plan_id
AND item.account_id=link.tenant_id AND item.inbound_id=link.inbound_id AND item.deleted=0),0)
+ COALESCE((SELECT COUNT(DISTINCT carton.id) FROM fl_dropship_pallet_carton carton
WHERE carton.tenant_id=link.tenant_id AND carton.inbound_id=link.inbound_id
AND EXISTS (SELECT 1 FROM fl_loading_plan_item pallet_item
WHERE pallet_item.loading_plan_id=link.loading_plan_id
AND pallet_item.account_id=link.tenant_id AND pallet_item.inbound_id=link.inbound_id
AND pallet_item.pallet_id=carton.pallet_id AND pallet_item.deleted=0)),0) allocatedPackageCount,
link.created_at linkedAt
FROM fl_preload_loading_plan_link link
JOIN fl_preload_plan preload ON preload.id=link.preload_plan_id
@@ -83,15 +83,19 @@ public interface LoadingCompletedEventMapper {
SELECT carton.tenant_id AS account_id,carton.inbound_id AS source_inbound_id,
pallet.id AS pallet_id,1 AS actual_binding,
pallet.pallet_no AS pallet_code,NULL AS allocation_no,
COUNT(*) OVER (PARTITION BY pallet.id,carton.tenant_id,carton.inbound_id) AS allocated_package_count,
carton.inbound_detail_id AS source_inbound_detail_id,carton.sku_code,
(SELECT COUNT(*) FROM fl_dropship_pallet_carton physical
WHERE physical.pallet_id=pallet.id AND physical.tenant_id=carton.tenant_id
AND physical.inbound_id=carton.inbound_id) AS allocated_package_count,
COALESCE(carton_sku.inbound_detail_id,carton.inbound_detail_id) AS source_inbound_detail_id,
COALESCE(carton_sku.sku_code,carton.sku_code) AS sku_code,
item.product_name_zh,item.package_count,item.product_quantity,carton.box_code,
carton.actual_sku_quantity
COALESCE(carton_sku.actual_quantity,carton.actual_sku_quantity) AS actual_sku_quantity
FROM fl_dropship_pallet pallet
JOIN fl_dropship_pallet_carton carton ON carton.pallet_id=pallet.id
LEFT JOIN fl_dropship_pallet_carton_sku carton_sku ON carton_sku.carton_id=carton.id
JOIN fl_loading_plan_item item ON item.loading_plan_id=#{planId}
AND item.account_id=carton.tenant_id AND item.inbound_id=carton.inbound_id
AND item.inbound_detail_id=carton.inbound_detail_id
AND item.inbound_detail_id=COALESCE(carton_sku.inbound_detail_id,carton.inbound_detail_id)
AND item.pallet_id=pallet.id AND item.deleted=0
WHERE pallet.pallet_status='SEALED'
ORDER BY carton.tenant_id,carton.inbound_id,pallet.id,item.inbound_detail_id,carton.id
@@ -83,14 +83,34 @@ public interface LoadingPickItemMapper extends BaseMapper<LoadingPickItemEntity>
@Param("operatorName") String operatorName);
@Select("""
SELECT COALESCE(SUM(CASE
SELECT COALESCE(SUM(CASE WHEN plan_item.pallet_id IS NOT NULL THEN 0
WHEN pick_item.item_status='SKIPPED' AND pick_item.picked_package_count=0 THEN 0
WHEN pick_item.item_status='PARTIAL' AND pick_item.picked_package_count>0
AND pick_item.picked_package_count<=plan_item.package_count
THEN pick_item.picked_package_count
ELSE plan_item.package_count
END),0) expectedCount,
COALESCE(SUM(pick_item.picked_package_count),0) pickedCount,
END),0)
+ COALESCE((SELECT COUNT(DISTINCT carton.id)
FROM fl_dropship_pallet_carton carton
WHERE carton.tenant_id=#{tenantId} AND carton.inbound_id=#{inboundId}
AND EXISTS (SELECT 1 FROM fl_loading_plan_item pallet_plan
WHERE pallet_plan.account_id=#{tenantId}
AND pallet_plan.inbound_id=#{inboundId}
AND pallet_plan.pallet_id=carton.pallet_id
AND pallet_plan.deleted=0)),0) expectedCount,
COALESCE(SUM(CASE WHEN plan_item.pallet_id IS NULL
THEN pick_item.picked_package_count ELSE 0 END),0)
+ COALESCE((SELECT COUNT(DISTINCT carton.id)
FROM fl_dropship_pallet_carton carton
WHERE carton.tenant_id=#{tenantId} AND carton.inbound_id=#{inboundId}
AND EXISTS (SELECT 1 FROM fl_loading_plan_item picked_plan
JOIN fl_loading_pick_item picked_item
ON picked_item.loading_plan_item_id=picked_plan.id
WHERE picked_plan.account_id=#{tenantId}
AND picked_plan.inbound_id=#{inboundId}
AND picked_plan.pallet_id=carton.pallet_id
AND picked_plan.deleted=0
AND picked_item.item_status='PICKED')),0) pickedCount,
COUNT(DISTINCT pick_task.id) taskCount,
COUNT(DISTINCT CASE WHEN pick_task.task_status='COMPLETED' THEN pick_task.id END) completedTaskCount,
SUM(CASE
@@ -88,16 +88,23 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
WHEN task.task_status = 'COMPLETING' THEN 'PROCESSING'
WHEN task.task_status = 'COMPLETION_FAILED' THEN 'COMPLETION_FAILED'
WHEN assignment.pick_task_id IS NULL THEN 'UNASSIGNED'
WHEN SUM(CASE WHEN pick_item.item_status IN ('PENDING', 'PICKING') THEN 1 ELSE 0 END) = 0
WHEN COUNT(DISTINCT CASE WHEN pick_item.item_status IN ('PENDING', 'PICKING')
THEN CASE WHEN plan_item.pallet_id IS NULL THEN CONCAT('I:',pick_item.id)
ELSE CONCAT('P:',plan_item.pallet_id) END END) = 0
THEN 'AWAITING_REVIEW'
ELSE 'IN_PROGRESS' END AS workflowStatus,
task.version AS version, COALESCE(assignment.version, 0) AS assignmentVersion,
COUNT(pick_item.id) AS itemCount,
SUM(CASE WHEN pick_item.item_status IN ('PENDING', 'PICKING') THEN 1 ELSE 0 END) AS pendingItemCount
COUNT(DISTINCT CASE WHEN plan_item.pallet_id IS NULL THEN CONCAT('I:',pick_item.id)
ELSE CONCAT('P:',plan_item.pallet_id) END) AS itemCount,
COUNT(DISTINCT CASE WHEN pick_item.item_status IN ('PENDING', 'PICKING')
THEN CASE WHEN plan_item.pallet_id IS NULL THEN CONCAT('I:',pick_item.id)
ELSE CONCAT('P:',plan_item.pallet_id) END END) AS pendingItemCount
FROM fl_loading_pick_task task
JOIN fl_loading_plan plan ON plan.id = task.loading_plan_id
JOIN fl_container_booking booking ON booking.id = plan.container_booking_id
JOIN fl_loading_pick_item pick_item ON pick_item.pick_task_id = task.id
JOIN fl_loading_plan_item plan_item ON plan_item.id=pick_item.loading_plan_item_id
AND plan_item.loading_plan_id=plan.id AND plan_item.deleted=0
LEFT JOIN fl_loading_pick_assignment assignment ON assignment.pick_task_id = task.id
WHERE task.task_status IN ('LOADING','COMPLETING','COMPLETION_FAILED') AND booking.record_status = 'ACTIVE'
AND booking.warehouse_id = #{warehouseId}
@@ -175,10 +175,12 @@ public class LoadingCompletedOutboxService {
}
Integer frozenPalletCount = dropship(fulfillmentType) || standardLabels.isEmpty()
? actualPalletCount(palletRows) : Integer.valueOf(standardLabels.size());
int physicalPackageCount = dropship(fulfillmentType)
? physicalPackageCount(palletRows) : sumInt(sorted, Row::getPackageCount);
return new FirstLegLoadingPlanCompletedV2.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),
first.getDestinationWarehouseCode(), first.getDestinationWarehouseName(), physicalPackageCount,
sumNullableInt(sorted, Row::getProductQuantity), frozenPalletCount,
sum(sorted, Row::getGrossWeightKg), sum(sorted, Row::getNetWeightKg),
sum(sorted, Row::getVolumeCbm), sum(sorted, Row::getDimensionalWeightKg), items,
@@ -346,7 +348,7 @@ public class LoadingCompletedOutboxService {
Map<String, List<PalletRow>> byPallet = source.stream().collect(java.util.stream.Collectors.groupingBy(
PalletRow::getPalletCode, LinkedHashMap::new, java.util.stream.Collectors.toList()));
List<FirstLegLoadingPlanCompletedV2.PalletSnapshot> result = new ArrayList<>();
int actualPackages = 0;
Set<String> actualPackages = new HashSet<>();
for (Map.Entry<String, List<PalletRow>> pallet : byPallet.entrySet()) {
Map<Long, List<PalletRow>> byDetail = pallet.getValue().stream().collect(
java.util.stream.Collectors.groupingBy(PalletRow::getSourceInboundDetailId,
@@ -365,14 +367,14 @@ public class LoadingCompletedOutboxService {
if (quantity <= 0) throw new BizException(ResultCode.CONFLICT, "一件代发现场托盘缺少实点 SKU 数量");
skus.add(new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot(detail.getSourceInboundDetailId().toString(), detail.getSkuCode(),
detail.getProductNameZh(), marks.size(), quantity, marks, cartonQuantities(detailRows.getValue(), marks)));
actualPackages += marks.size();
marks.forEach(mark -> actualPackages.add(pallet.getKey() + '|' + mark));
}
PalletManifest manifest = manifests.get(pallet.getKey());
if (manifest == null) throw new BizException(ResultCode.CONFLICT, "一件代发托盘缺少完整清单摘要");
result.add(new FirstLegLoadingPlanCompletedV2.PalletSnapshot(pallet.getKey(), skus,
manifest.version(), manifest.packageCount(), manifest.fragmentCount(), manifest.hash()));
}
if (actualPackages != sumInt(values, Row::getPackageCount)) {
if (actualPackages.size() != physicalPackageCount(source)) {
throw new BizException(ResultCode.CONFLICT, "一件代发现场托盘箱唛数与实装箱数不一致");
}
return result;
@@ -390,13 +392,21 @@ public class LoadingCompletedOutboxService {
String canonical = sorted.stream().map(value -> value.getAccountId() + "|" + value.getSourceInboundId()
+ "|" + value.getSourceInboundDetailId() + "|" + value.getSkuCode() + "|" + value.getBoxCode()
+ "|" + value.getActualSkuQuantity()).collect(java.util.stream.Collectors.joining("\n"));
int fragments = (int) sorted.stream().map(PalletRow::getAccountId).distinct().count();
result.put(palletNo, new PalletManifest(1, sorted.size(), fragments,
int fragments = (int) sorted.stream().map(value -> value.getAccountId() + ":" + value.getSourceInboundId())
.distinct().count();
int packages = (int) sorted.stream().map(PalletRow::getBoxCode)
.filter(Objects::nonNull).distinct().count();
result.put(palletNo, new PalletManifest(1, packages, fragments,
manifestHash(canonical)));
});
return result;
}
private static int physicalPackageCount(List<PalletRow> rows) {
return (int) rows.stream().map(row -> row.getPalletCode() + '|' + row.getBoxCode())
.distinct().count();
}
private String manifestHash(String canonical) {
String value = EventPayloadHash.sha256(canonical.getBytes(StandardCharsets.UTF_8));
return value.substring("sha256:".length());
@@ -961,19 +961,7 @@ public class LoadingPickService {
.collect(Collectors.groupingBy(CartonLabelRow::getPickItemId));
Map<Long, Long> tenantByInbound = tenantByInbound(task.getId());
Map<Long, List<InventoryResource>> inventoryByInbound = inventoryByInbound(source, tenantByInbound);
List<PickItem> items = source.stream().map(item -> {
List<CartonLabelRow> cartons = cartonsByItem.getOrDefault(item.getId(), List.of());
CartonLabelRow first = cartons.isEmpty() ? null : cartons.get(0);
Long tenantId = first == null ? tenantByInbound.get(item.getInboundId()) : first.getTenantId();
return new PickItem(item.getId(), item.getLoadingPlanItemId(), tenantId,
item.getInboundId(),
item.getInboundDetailId(), item.getUnitType(), item.getPalletId(), item.getPalletNo(), item.getIndivisible(),
first == null ? null : first.getOrderNo(),
first == null ? null : first.getInboundCode(), first == null ? null : first.getCargoName(),
cartons.stream().map(CartonLabelRow::getBoxCode).toList(),
inventoryByInbound.getOrDefault(item.getInboundId(), List.of()), item.getPlannedPackageCount(),
item.getPickedPackageCount(), item.getItemStatus(), item.getVersion());
}).toList();
List<PickItem> items = pickItems(source, cartonsByItem, tenantByInbound, inventoryByInbound);
LoadingPickAssignmentEntity assigned = repository.assignment(task.getId());
LoadingPickReviewEntity reviewed = repository.review(task.getId());
Assignment assignment = assigned == null ? null : new Assignment(assigned.getAssigneeId(),
@@ -1006,6 +994,71 @@ public class LoadingPickService {
workflowStatus, task.getVersion(), assignment, review, completionCommand, items);
}
/** One sealed pallet is one physical PDA work unit even when its cartons contain several SKU snapshots. */
private List<PickItem> pickItems(List<LoadingPickItemEntity> source,
Map<Long, List<CartonLabelRow>> cartonsByItem,
Map<Long, Long> tenantByInbound,
Map<Long, List<InventoryResource>> inventoryByInbound) {
Map<String, List<LoadingPickItemEntity>> units = source.stream().collect(Collectors.groupingBy(
item -> "SEALED_PALLET".equals(item.getUnitType()) && item.getPalletId() != null
? "P:" + item.getPalletId() : "I:" + item.getId(),
LinkedHashMap::new, Collectors.toList()));
Set<Long> palletIds = units.values().stream().map(items -> items.get(0))
.filter(item -> "SEALED_PALLET".equals(item.getUnitType()) && item.getPalletId() != null)
.map(LoadingPickItemEntity::getPalletId).collect(Collectors.toSet());
Map<Long, com.baiyue.firstleg.mapper.DropshipPalletizationMapper.PalletMetrics> physical =
palletIds.isEmpty() ? Map.of() : dropshipPalletizationMapper.physicalMetricsByPalletIds(palletIds).stream()
.collect(Collectors.toMap(
com.baiyue.firstleg.mapper.DropshipPalletizationMapper.PalletMetrics::palletId,
java.util.function.Function.identity()));
List<PickItem> result = new ArrayList<>();
for (List<LoadingPickItemEntity> members : units.values()) {
LoadingPickItemEntity firstItem = members.get(0);
List<CartonLabelRow> cartons = members.stream()
.flatMap(item -> cartonsByItem.getOrDefault(item.getId(), List.of()).stream()).toList();
CartonLabelRow firstCarton = cartons.isEmpty() ? null : cartons.get(0);
Long tenantId = firstCarton == null ? tenantByInbound.get(firstItem.getInboundId())
: firstCarton.getTenantId();
List<String> boxCodes = cartons.stream().map(CartonLabelRow::getBoxCode)
.filter(StringUtils::hasText).distinct().toList();
List<InventoryResource> inventory = members.stream()
.flatMap(item -> inventoryByInbound.getOrDefault(item.getInboundId(), List.of()).stream())
.collect(Collectors.collectingAndThen(Collectors.toMap(
resource -> resource.reservationId() == null ? "L:" + resource.locationId()
: "R:" + resource.reservationId(),
java.util.function.Function.identity(), (left, right) -> left, LinkedHashMap::new),
values -> List.copyOf(values.values())));
if (!"SEALED_PALLET".equals(firstItem.getUnitType()) || firstItem.getPalletId() == null) {
result.add(new PickItem(firstItem.getId(), firstItem.getLoadingPlanItemId(), tenantId,
firstItem.getInboundId(), firstItem.getInboundDetailId(), firstItem.getUnitType(),
firstItem.getPalletId(), firstItem.getPalletNo(), firstItem.getIndivisible(),
firstCarton == null ? null : firstCarton.getOrderNo(),
firstCarton == null ? null : firstCarton.getInboundCode(),
firstCarton == null ? null : firstCarton.getCargoName(), boxCodes, inventory,
firstItem.getPlannedPackageCount(), firstItem.getPickedPackageCount(),
firstItem.getItemStatus(), firstItem.getVersion()));
continue;
}
var metrics = physical.get(firstItem.getPalletId());
if (metrics == null || count(metrics.cartonCount()) < 1) {
throw conflict("托盘缺少物理箱明细,不能生成PDA整托任务");
}
boolean picked = members.stream().allMatch(item -> "PICKED".equals(item.getItemStatus()));
boolean pending = members.stream().allMatch(item -> "PENDING".equals(item.getItemStatus())
&& count(item.getPickedPackageCount()) == 0);
String status = picked ? "PICKED" : pending ? "PENDING" : "PICKING";
int version = members.stream().map(LoadingPickItemEntity::getVersion).filter(Objects::nonNull)
.max(Integer::compareTo).orElse(0);
result.add(new PickItem(firstItem.getId(), firstItem.getLoadingPlanItemId(), tenantId,
firstItem.getInboundId(), firstItem.getInboundDetailId(), "SEALED_PALLET",
firstItem.getPalletId(), firstItem.getPalletNo(), true,
firstCarton == null ? null : firstCarton.getOrderNo(),
firstCarton == null ? null : firstCarton.getInboundCode(), null, boxCodes, inventory,
metrics.cartonCount(), picked ? metrics.cartonCount() : 0, status, version));
}
return List.copyOf(result);
}
private List<BatchLine> validateBatchLines(LoadingPickTaskEntity task, BatchRequest request) {
long taskId = task.getId();
List<LoadingPickItemEntity> allItems = repository.items(taskId);
@@ -10,6 +10,7 @@ import com.baiyue.firstleg.entity.LoadingPickItemEntity;
import com.baiyue.firstleg.mapper.InboundSplitSettlementMapper;
import com.baiyue.firstleg.mapper.InboundSplitSettlementMapper.MeasurementItemRow;
import com.baiyue.firstleg.mapper.InboundSplitSettlementMapper.ParentRow;
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper;
import com.baiyue.firstleg.repository.InboundPalletAllocationRepository;
import com.baiyue.firstleg.repository.LoadingPickRepository;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
@@ -50,6 +51,8 @@ public class PdaPickSettlementService {
private OrderFulfillmentSplitClient orderSplits;
@Resource
private InboundHoldGuard inboundHoldGuard;
@Resource
private DropshipPalletizationMapper dropshipPallets;
@Value("${baiyue.security.internal-service-token}")
private String internalToken;
@@ -173,13 +176,14 @@ public class PdaPickSettlementService {
if (measurements.stream().anyMatch(item -> !Objects.equals(sourceMeasurementId, item.measurementId()))) {
throw conflict("主入库单存在多个已审核测量批次,不能自动拆分");
}
Map<Long, Integer> pickedByDetail = pickedByDetail(taskItems, parentInboundId);
int pickedByMeasurement = pickedByDetail.values().stream().mapToInt(Integer::intValue).sum();
if (pickedByMeasurement != pickedPackageCount) {
throw conflict("PDA 已拣明细与入库单已拣箱数不一致");
}
boolean dropshipWholePallet = "ONE_CLICK_DROPSHIP".equals(
splits.fulfillmentType(parent.tenantId(), parentInboundId));
Map<Long, Integer> pickedByDetail = dropshipWholePallet
? Map.of() : pickedByDetail(taskItems, parentInboundId);
if (!dropshipWholePallet
&& pickedByDetail.values().stream().mapToInt(Integer::intValue).sum() != pickedPackageCount) {
throw conflict("PDA 已拣明细与入库单已拣箱数不一致");
}
if (dropshipWholePallet && childPackageCount != 0) {
throw conflict("一件代发现场托盘必须整票装柜,不能生成余量分片");
}
@@ -192,8 +196,7 @@ public class PdaPickSettlementService {
}
if (root == null || !"MASTER".equals(root.inboundRole())) throw conflict("根主入库单不存在");
Map<Long, Integer> orderQuantities = dropshipWholePallet
? new LinkedHashMap<>(pickedByDetail) : measurementQuantities(measurementLines);
Map<Long, Integer> orderQuantities = measurementQuantities(measurementLines);
OrderFulfillmentSplitClient.Response orderSplit = createOrderSplit(parent, root, orderQuantities,
pickedPackageCount, childPackageCount, loadingPlanId, containerBookingId, operationId,
"pda-task-" + taskId, operatorId, operatorName);
@@ -337,11 +340,35 @@ public class PdaPickSettlementService {
&& Objects.equals(child.containerBookingId(), containerBookingId);
}
private static Map<Long, Integer> pickedByInbound(List<LoadingPickItemEntity> taskItems) {
return taskItems.stream().filter(item -> "PICKED".equals(item.getItemStatus())
|| "PARTIAL".equals(item.getItemStatus()))
.collect(Collectors.groupingBy(LoadingPickItemEntity::getInboundId,
Collectors.summingInt(item -> count(item.getPickedPackageCount(), "PDA已拣箱数"))));
private Map<Long, Integer> pickedByInbound(List<LoadingPickItemEntity> taskItems) {
Map<Long, Integer> result = new HashMap<>();
Set<Long> pickedPalletIds = new java.util.LinkedHashSet<>();
for (LoadingPickItemEntity item : taskItems) {
if (!("PICKED".equals(item.getItemStatus()) || "PARTIAL".equals(item.getItemStatus()))) continue;
if ("SEALED_PALLET".equals(item.getUnitType()) && item.getPalletId() != null) {
pickedPalletIds.add(item.getPalletId());
} else {
result.merge(item.getInboundId(), count(item.getPickedPackageCount(), "PDA已拣箱数"), Integer::sum);
}
}
if (pickedPalletIds.isEmpty()) return result;
Map<Long, List<LoadingPickItemEntity>> palletItems = taskItems.stream()
.filter(item -> item.getPalletId() != null && pickedPalletIds.contains(item.getPalletId()))
.collect(Collectors.groupingBy(LoadingPickItemEntity::getPalletId));
if (palletItems.values().stream().anyMatch(items -> items.stream()
.anyMatch(item -> !"PICKED".equals(item.getItemStatus())))) {
throw conflict("一件代发整托存在未完成的 SKU 明细,不能结算物理箱数");
}
List<DropshipPalletizationMapper.PlanPalletMemberRow> physical =
dropshipPallets.palletInboundMembersByPalletIds(pickedPalletIds);
Set<Long> resolvedPallets = physical.stream().map(DropshipPalletizationMapper.PlanPalletMemberRow::getPalletId)
.collect(Collectors.toSet());
if (!resolvedPallets.equals(pickedPalletIds)) {
throw conflict("一件代发托盘缺少物理箱明细,不能完成装柜结算");
}
physical.forEach(row -> result.merge(row.getInboundId(),
count(row.getAvailablePackageCount(), "托盘物理箱数"), Integer::sum));
return result;
}
private static Map<Long, Integer> pickedByDetail(List<LoadingPickItemEntity> taskItems, long inboundId) {