fix(lastleg): create remaining container plans

This commit is contained in:
2026-09-08 13:52:37 +08:00
parent 3c05525e27
commit d318f4a450
4 changed files with 79 additions and 33 deletions
@@ -61,7 +61,7 @@ public class InboundPlanController {
@PostMapping("/convert-container")
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_CREATE + "')")
@Operation(summary = "按整柜批量生成入库计划", description = "平台按 sourceContainerBookingId 锁定整柜订单;每个入仓码独立生成计划,整批原子且可幂等重放。Required authority: " + OverseasPermissions.INBOUND_ORDER_CREATE)
@Operation(summary = "按整柜批量生成剩余入库计划", description = "平台按 sourceContainerBookingId 锁定整柜订单;保留已有有效计划,只为剩余入仓码分别生成独立计划,整批原子且可幂等重放。Required authority: " + OverseasPermissions.INBOUND_ORDER_CREATE)
public ApiResponse<java.util.List<InboundPlanResponse>> convertContainer(
@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ConvertContainerInboundPlanRequest request) {
@@ -285,26 +285,24 @@ public class InboundPlanService {
List<InboundPlan> existingPlans = refs.stream()
.map(ref -> planMapper.findBySourceOrder(ref.customerId(), ref.orderId()))
.toList();
long existingCount = existingPlans.stream().filter(Objects::nonNull).count();
if (existingCount == refs.size()) {
List<InboundPlanResponse> replay = new ArrayList<>(refs.size());
for (int index = 0; index < refs.size(); index++) {
requirePlanContainer(existingPlans.get(index), containerNo);
replay.add(detail(refs.get(index).customerId(), existingPlans.get(index)));
List<String> warehouseIds = new ArrayList<>(refs.size());
List<Integer> pendingIndexes = new ArrayList<>();
List<List<SourceOrderSnapshot>> snapshots = new ArrayList<>();
List<List<WholePalletInput>> wholePallets = new ArrayList<>();
for (int index = 0; index < refs.size(); index++) {
InboundPlanOrderMapper.ContainerOrderRef ref = refs.get(index);
InboundPlan existing = existingPlans.get(index);
if (existing != null) {
requireValidExistingPlan(existing, containerNo);
InboundPlanOrderMapper.ReceivingWarehouseSnapshot warehouse =
orderMapper.findReceivingWarehouse(ref.customerId(), ref.orderId());
warehouseIds.add(warehouse == null ? null : warehouse.overseasWarehouseId());
continue;
}
if (!("PENDING_ASSIGNMENT".equals(ref.currentStatus())
|| "DESTINATION_ASSIGNED".equals(ref.currentStatus()))) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "来源柜存在不能转换的尾程订单");
}
return List.copyOf(replay);
}
if (existingCount != 0) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "来源柜仅部分尾程订单已有入库计划");
}
if (refs.stream().anyMatch(ref -> !("PENDING_ASSIGNMENT".equals(ref.currentStatus())
|| "DESTINATION_ASSIGNED".equals(ref.currentStatus())))) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "来源柜存在不能转换的尾程订单");
}
List<List<SourceOrderSnapshot>> snapshots = new ArrayList<>(refs.size());
List<List<WholePalletInput>> wholePallets = new ArrayList<>(refs.size());
for (InboundPlanOrderMapper.ContainerOrderRef ref : refs) {
List<SourceOrderSnapshot> orders = orderMapper.lockOrder(ref.customerId(), ref.orderId());
if (orders == null || orders.isEmpty()) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "来源柜订单在锁定后不存在");
@@ -314,7 +312,9 @@ public class InboundPlanService {
if (!Objects.equals(containerNo, currentContainerNo)) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "来源柜内尾程订单柜号不一致");
}
pendingIndexes.add(index);
snapshots.add(orders);
warehouseIds.addAll(orders.stream().map(SourceOrderSnapshot::overseasWarehouseId).toList());
List<WholePalletInput> sourcePallets = "ONE_CLICK_DROPSHIP".equals(ref.orderType())
? dropshipOrders.palletSnapshots(ref.customerId(), ref.orderId()).stream()
.map(this::wholePallet).toList()
@@ -328,16 +328,21 @@ public class InboundPlanService {
}
wholePallets.add(sourcePallets);
}
requireSingleWarehouseId(snapshots.stream().flatMap(List::stream)
.map(SourceOrderSnapshot::overseasWarehouseId).toList());
requireSingleWarehouseId(warehouseIds);
List<InboundPlanResponse> created = new ArrayList<>(refs.size());
for (int index = 0; index < refs.size(); index++) {
InboundPlanOrderMapper.ContainerOrderRef ref = refs.get(index);
created.add(createPlan(ref.customerId(), ref.orderId(), snapshots.get(index), List.of(),
wholePallets.get(index), null, null));
Map<Integer, InboundPlanResponse> created = new HashMap<>();
for (int pendingIndex = 0; pendingIndex < pendingIndexes.size(); pendingIndex++) {
int refIndex = pendingIndexes.get(pendingIndex);
InboundPlanOrderMapper.ContainerOrderRef ref = refs.get(refIndex);
created.put(refIndex, createPlan(ref.customerId(), ref.orderId(), snapshots.get(pendingIndex), List.of(),
wholePallets.get(pendingIndex), null, null));
}
return List.copyOf(created);
List<InboundPlanResponse> result = new ArrayList<>(refs.size());
for (int index = 0; index < refs.size(); index++) {
InboundPlan existing = existingPlans.get(index);
result.add(existing == null ? created.get(index) : detail(refs.get(index).customerId(), existing));
}
return List.copyOf(result);
}
private WholePalletInput wholePallet(DropshipPalletSnapshot pallet) {
@@ -397,6 +402,13 @@ public class InboundPlanService {
}
}
private void requireValidExistingPlan(InboundPlan plan, String containerNo) {
requirePlanContainer(plan, containerNo);
if (trimToNull(plan.getPlanNo()) == null || trimToNull(plan.getStatus()) == null) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "来源柜存在无效入库计划,不能继续按柜生成");
}
}
private void validateWholePallets(List<SourceOrderSnapshot> orders, List<WholePalletInput> pallets) {
if (orders.size() != 1) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,