This commit is contained in:
2026-08-24 17:12:30 +08:00
parent ce9f08c756
commit 01675b8220
15 changed files with 367 additions and 43 deletions
@@ -30,6 +30,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.HashSet;
import java.util.stream.Collectors;
@Service
@@ -62,6 +63,32 @@ public class InboundPlanService {
@Transactional
public InboundPlanResponse convert(String customerId, Long selectedOrderId, List<String> sourcePalletNos) {
return convert(customerId, selectedOrderId, sourcePalletNos, null);
}
@Transactional
public InboundPlanResponse convertDropship(String customerId, Long selectedOrderId,
List<WholePalletInput> sourcePallets) {
if (sourcePallets == null || sourcePallets.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"One-click dropship inbound requires source pallets");
}
HashSet<String> palletNos = new HashSet<>();
for (WholePalletInput pallet : sourcePallets) {
if (pallet == null || trimToNull(pallet.palletNo()) == null || pallet.packageCount() <= 0) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"One-click dropship source pallet number and package count are required");
}
if (!palletNos.add(pallet.palletNo().trim())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"Duplicate one-click dropship source pallet number: " + pallet.palletNo());
}
}
return convert(customerId, selectedOrderId, List.of(), List.copyOf(sourcePallets));
}
private InboundPlanResponse convert(String customerId, Long selectedOrderId, List<String> sourcePalletNos,
List<WholePalletInput> wholePallets) {
List<SourceOrderSnapshot> orders = orderMapper.lockOrder(customerId, selectedOrderId);
if (orders == null || orders.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Inbound order not found");
@@ -91,21 +118,27 @@ public class InboundPlanService {
Map<Long, SourceOrderSnapshot> orderById = orders.stream()
.collect(Collectors.toMap(SourceOrderSnapshot::orderId, value -> value));
Map<Long, InboundPlanLine> lineByOrder = createLines(plan, orders);
PalletAllocator.Result allocation = PalletAllocator.allocate(orders.stream()
PalletAllocator.Result allocation = wholePallets == null ? PalletAllocator.allocate(orders.stream()
.map(order -> new PalletAllocator.LineInput(order.orderId(), warehouseKey(order), volume(order),
order.packageCount(), order.recommendedPalletCount()))
.toList());
plan.setPalletCount(allocation.pallets().size());
order.packageCount(), order.recommendedPalletCount())).toList()) : null;
if (wholePallets != null) validateWholePallets(orders, wholePallets);
plan.setPalletCount(wholePallets == null ? allocation.pallets().size() : wholePallets.size());
planMapper.insert(plan);
lineByOrder.values().forEach(lineMapper::insert);
Map<PalletAllocator.PalletDraft, InboundPlanPallet> persistedPallets = createPallets(plan, allocation, orderById,
sourcePalletNos == null ? List.of() : sourcePalletNos);
createAllocations(plan, allocation, lineByOrder, persistedPallets);
Map<Long, Integer> palletCounts = allocation.allocations().stream().collect(Collectors.groupingBy(
PalletAllocator.AllocationDraft::orderId,
Collectors.collectingAndThen(Collectors.mapping(item -> item.pallet().sequenceNo(), Collectors.toSet()), java.util.Set::size)));
Map<Long, Integer> palletCounts;
if (wholePallets == null) {
Map<PalletAllocator.PalletDraft, InboundPlanPallet> persistedPallets = createPallets(plan, allocation,
orderById, sourcePalletNos == null ? List.of() : sourcePalletNos);
createAllocations(plan, allocation, lineByOrder, persistedPallets);
palletCounts = allocation.allocations().stream().collect(Collectors.groupingBy(
PalletAllocator.AllocationDraft::orderId, Collectors.collectingAndThen(Collectors.mapping(
item -> item.pallet().sequenceNo(), Collectors.toSet()), java.util.Set::size)));
} else {
SourceOrderSnapshot order = orders.get(0);
createWholePallets(plan, order, lineByOrder.get(order.orderId()), wholePallets);
palletCounts = Map.of(order.orderId(), wholePallets.size());
}
for (SourceOrderSnapshot order : orders) {
int count = palletCounts.getOrDefault(order.orderId(), 0);
InboundPlanLine line = lineByOrder.get(order.orderId());
@@ -120,6 +153,54 @@ public class InboundPlanService {
return detail(customerId, plan);
}
private void validateWholePallets(List<SourceOrderSnapshot> orders, List<WholePalletInput> pallets) {
if (orders.size() != 1) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"One-click dropship inbound plan must belong to one inbound order");
}
int packages = pallets.stream().mapToInt(WholePalletInput::packageCount).sum();
if (packages != orders.get(0).packageCount()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"One-click dropship pallet package total must equal inbound order package count");
}
}
private void createWholePallets(InboundPlan plan, SourceOrderSnapshot order, InboundPlanLine line,
List<WholePalletInput> sourcePallets) {
BigDecimal remainingVolume = volume(order);
int remainingPackages = order.packageCount();
for (int index = 0; index < sourcePallets.size(); index++) {
WholePalletInput source = sourcePallets.get(index);
BigDecimal palletVolume = index == sourcePallets.size() - 1 ? remainingVolume
: volume(order).multiply(BigDecimal.valueOf(source.packageCount()))
.divide(BigDecimal.valueOf(order.packageCount()), 4, java.math.RoundingMode.HALF_UP);
if (palletVolume.compareTo(remainingVolume) > 0) palletVolume = remainingVolume;
InboundPlanPallet pallet = new InboundPlanPallet();
pallet.setId(IdWorker.getId()); pallet.setCustomerId(plan.getCustomerId()); pallet.setPlanId(plan.getId());
pallet.setPalletNo(source.palletNo().trim()); pallet.setDestinationWarehouseId(order.destinationWarehouseId());
pallet.setDestinationWarehouseCode(order.destinationWarehouseCode());
pallet.setDestinationWarehouseName(order.destinationWarehouseName());
pallet.setCapacityCbm(PalletAllocator.PALLET_CAPACITY_CBM); pallet.setAllocatedVolumeCbm(palletVolume);
pallet.setSequenceNo(index + 1); pallet.setAssignmentStatus("UNASSIGNED");
pallet.setActualVolumeCbm(BigDecimal.ZERO); pallet.setActualPackageCount(0); palletMapper.insert(pallet);
InboundPlanPalletAllocation allocation = new InboundPlanPalletAllocation();
allocation.setId(IdWorker.getId()); allocation.setCustomerId(plan.getCustomerId()); allocation.setPlanId(plan.getId());
allocation.setLineId(line.getId()); allocation.setPalletId(pallet.getId());
allocation.setAllocatedVolumeCbm(palletVolume); allocation.setPlannedPackageCount(source.packageCount());
allocation.setCountedPackageCount(0); allocation.setPickedPackageCount(0);
allocation.setActualVolumeCbm(BigDecimal.ZERO); allocation.setStatus("PENDING_COUNT"); allocation.setVersion(0);
allocationMapper.insert(allocation);
remainingVolume = remainingVolume.subtract(palletVolume);
remainingPackages -= source.packageCount();
}
if (remainingPackages != 0 || remainingVolume.signum() != 0) {
throw new IllegalStateException("Failed to conserve one-click dropship pallet totals");
}
}
public record WholePalletInput(String palletNo, int packageCount) {}
@Transactional(readOnly = true)
public InboundPlanResponse detailByPlanNo(String customerId, String planNo) {