fix: isolate invalid receiving candidates

This commit is contained in:
2026-09-07 16:06:08 +08:00
parent ff2b02b102
commit d622ddd37f
2 changed files with 30 additions and 7 deletions
@@ -443,13 +443,13 @@ public class ReceivingContainerService {
try { assignedWarehouses.add(Long.valueOf(value)); }
catch (NumberFormatException ignored) { assignedWarehouses.add(Long.MIN_VALUE); }
});
if (assignedWarehouses.size() > 1) throw conflict("柜内接柜仓分配不一致");
if (assignedWarehouses.size() > 1) return null;
if (assignedWarehouses.size() != 1 || !assignedWarehouses.contains(overseasWarehouseId)) return null;
Set<String> warehouseCodes = orderFacts.stream().map(LastlegOrder::getOverseasWarehouseCode)
.map(ReceivingContainerService::trim).filter(Objects::nonNull).collect(Collectors.toSet());
Set<String> warehouseNames = orderFacts.stream().map(LastlegOrder::getOverseasWarehouseName)
.map(ReceivingContainerService::trim).filter(Objects::nonNull).collect(Collectors.toSet());
if (warehouseCodes.size() != 1 || warehouseNames.size() != 1) throw conflict("柜内接柜仓业务快照不一致");
boolean warehouseSnapshotConsistent = warehouseCodes.size() == 1 && warehouseNames.size() == 1;
Set<String> customers = orderFacts.stream().map(LastlegOrder::getCustomerId).collect(Collectors.toSet());
String containerNo = orderFacts.stream().map(LastlegOrder::getContainerNo).filter(Objects::nonNull).findFirst().orElse(null);
int totalOrders = orderFacts.size();
@@ -468,10 +468,11 @@ public class ReceivingContainerService {
&& containerPlans.stream().allMatch(plan -> "COMPLETED".equals(plan.getStatus()));
String customerId = customers.size() == 1 ? customers.iterator().next() : null;
boolean shippingReady = customerId != null && shippingGates.receivingAvailable(customerId, containerNo) == 1;
boolean operable = allPlansReady && shippingReady;
boolean operable = allPlansReady && shippingReady && warehouseSnapshotConsistent;
int pending = Math.max(0, totalOrders - plannedOrders);
String blockReason = operable ? null : (!allPlansReady
? plannedOrders + "个入仓码已生成计划," + pending + "个订单未操作/缺入仓码"
String blockReason = operable ? null : (!warehouseSnapshotConsistent
? "柜内接柜仓业务快照不一致"
: !allPlansReady ? plannedOrders + "个入仓码已生成计划," + pending + "个订单未操作/缺入仓码"
: "柜号尚未达到海外收货可操作节点");
LocalDateTime arrival = orderFacts.stream().map(LastlegOrder::getEstimatedWarehouseAt)
.filter(Objects::nonNull).min(LocalDateTime::compareTo).orElse(null);
@@ -492,8 +493,9 @@ public class ReceivingContainerService {
inboundValues.stream().mapToInt(ReceivingContainerResponses.Inbound::plannedPalletCount).sum(),
inboundValues.stream().mapToInt(ReceivingContainerResponses.Inbound::totalPackages).sum(),
inboundValues.stream().mapToInt(ReceivingContainerResponses.Inbound::receivedPackages).sum());
return new ContainerReadiness(sourceContainerBookingId, containerNo, warehouseCodes.iterator().next(),
warehouseNames.iterator().next(), totalOrders, inboundCodes,
return new ContainerReadiness(sourceContainerBookingId, containerNo,
warehouseCodes.size() == 1 ? warehouseCodes.iterator().next() : null,
warehouseNames.size() == 1 ? warehouseNames.iterator().next() : null, totalOrders, inboundCodes,
totalOrders - inboundCodes, planCount, plannedOrders, pending, allPlansReady, allPlansCompleted, shippingReady, operable,
blockReason, arrival, factHash, totals);
}
@@ -176,6 +176,27 @@ class ReceivingContainerServiceBehaviorTest {
.containsExactly("CONT-EARLY", "CONT-LATE", "CONT-NULL", "CONT-BLOCKED");
}
@Test
void invalidWarehouseSnapshotOnlyBlocksItsContainerWithoutFailingHealthyCandidates() {
InboundPlan brokenPlan = plan("CONT-BROKEN");
InboundPlan healthyPlan = plan("CONT-HEALTHY"); healthyPlan.setId(3L);
InboundPlanLine brokenLine = line("FBA");
InboundPlanLine healthyLine = line("FBA"); healthyLine.setId(11L); healthyLine.setOrderId(2L); healthyLine.setPlanId(3L);
LastlegOrder broken = containerOrder(1L, "100", "CONT-BROKEN", "BOOKING-A", "IN-1");
broken.setOverseasWarehouseCode(null);
LastlegOrder healthy = containerOrder(2L, "100", "CONT-HEALTHY", "BOOKING-B", "IN-2");
when(plans.selectList(any())).thenReturn(List.of(brokenPlan, healthyPlan));
when(lines.selectList(any())).thenReturn(List.of(brokenLine, healthyLine));
when(orders.selectList(any())).thenReturn(List.of(broken, healthy));
var result = service.candidates("7", 100L);
assertThat(result).extracting("containerNo").containsExactly("CONT-HEALTHY", "CONT-BROKEN");
assertThat(result.get(0).operable()).isTrue();
assertThat(result.get(1).operable()).isFalse();
assertThat(result.get(1).blockReason()).isEqualTo("柜内接柜仓业务快照不一致");
}
@Test
void candidatesIncludeUnassignedAndSameWarehouseButHideOtherWarehouse() {
InboundPlan unassigned = plan("CONT-A");