From ffef2845e9a174920bf87b48b1642016c56e65cd Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Fri, 20 Mar 2026 17:44:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=9A=8F=E6=9C=BA=E8=B4=A7?= =?UTF-8?q?=E9=81=93=E5=87=BA=E8=B4=A7=E6=B2=A1=E6=9C=89=E6=89=A3=E5=87=8F?= =?UTF-8?q?=E9=9A=8F=E6=9C=BA=E5=87=BA=E8=B4=A7=E8=B4=A7=E9=81=93=E7=9A=84?= =?UTF-8?q?=E5=BA=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/TradeDeviceOrderHandler.java | 59 +++++++++++++------ .../handler/TradeProductSkuOrderHandler.java | 15 ++--- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java index b0bae22..d2592ec 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java @@ -312,7 +312,8 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { /** * 构建设备出货请求体 * - * 逻辑:一个商品(SPU)下可能存在多个货道(SKU)。每条订单行在该商品下「有库存且通道类型匹配」的货道中随机选一个出货,同一货道多次被选中则合并数量。 + * 逻辑:优先使用订单创建时记录的 stockDeductionAllocation(哪个货道扣的库存就从哪个货道出货), + * 保证库存扣减与出货货道一致。无 allocation 的旧订单则从有库存货道中随机选(兼容历史数据)。 * * message 协议格式(示例):T,C1&N1,C2&N2 * - T:通道类型(cabinetType) @@ -327,24 +328,48 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { String cabinetType) { Map channelToCount = new LinkedHashMap<>(); String lastChannel = null; + boolean hasAllocation = orderItems.stream().anyMatch(item -> CollUtil.isNotEmpty(item.getStockDeductionAllocation())); - for (TradeOrderItemDO orderItem : orderItems) { - List skus = productSkuApi.getSkuListBySpuId(Collections.singletonList(orderItem.getSpuId())); - // 有库存、货道非空、通道类型一致 - List inStock = skus.stream() - .filter(sku -> sku != null - && StrUtil.isNotBlank(sku.getCabinetChannel()) - && Objects.equals(sku.getCabinetType(), cabinetType) - && sku.getStock() != null - && sku.getStock() >= orderItem.getCount()) - .collect(Collectors.toList()); - if (inStock.isEmpty()) { - log.error("订单:{} 商品 spuId={} 无可用货道(有库存且通道类型匹配)", order.getNo(), orderItem.getSpuId()); - return null; + if (hasAllocation) { + // 使用创建订单时记录的库存扣减分配:哪个货道扣的库存就从哪个货道出货 + for (TradeOrderItemDO orderItem : orderItems) { + List allocation = orderItem.getStockDeductionAllocation(); + if (CollUtil.isEmpty(allocation)) { + log.error("订单:{} 订单项 spuId={} 无 stockDeductionAllocation,无法确定出货货道", order.getNo(), orderItem.getSpuId()); + return null; + } + List skuIds = allocation.stream().map(TradeOrderItemDO.StockDeductionAllocation::getSkuId).collect(Collectors.toList()); + List skus = productSkuApi.getSkuList(skuIds); + Map skuMap = skus.stream().collect(Collectors.toMap(ProductSkuRespDTO::getId, s -> s, (a, b) -> a)); + for (TradeOrderItemDO.StockDeductionAllocation alloc : allocation) { + ProductSkuRespDTO sku = skuMap.get(alloc.getSkuId()); + if (sku == null || StrUtil.isBlank(sku.getCabinetChannel())) { + log.error("订单:{} skuId={} 无货道编号(cabinetChannel)", order.getNo(), alloc.getSkuId()); + return null; + } + channelToCount.merge(sku.getCabinetChannel(), alloc.getCount(), Integer::sum); + lastChannel = sku.getCabinetChannel(); + } + } + } else { + // 兼容旧订单:从有库存货道中随机选 + for (TradeOrderItemDO orderItem : orderItems) { + List skus = productSkuApi.getSkuListBySpuId(Collections.singletonList(orderItem.getSpuId())); + List inStock = skus.stream() + .filter(sku -> sku != null + && StrUtil.isNotBlank(sku.getCabinetChannel()) + && Objects.equals(sku.getCabinetType(), cabinetType) + && sku.getStock() != null + && sku.getStock() >= orderItem.getCount()) + .collect(Collectors.toList()); + if (inStock.isEmpty()) { + log.error("订单:{} 商品 spuId={} 无可用货道(有库存且通道类型匹配)", order.getNo(), orderItem.getSpuId()); + return null; + } + ProductSkuRespDTO chosen = inStock.get(ThreadLocalRandom.current().nextInt(inStock.size())); + channelToCount.merge(chosen.getCabinetChannel(), orderItem.getCount(), Integer::sum); + lastChannel = chosen.getCabinetChannel(); } - ProductSkuRespDTO chosen = inStock.get(ThreadLocalRandom.current().nextInt(inStock.size())); - channelToCount.merge(chosen.getCabinetChannel(), orderItem.getCount(), Integer::sum); - lastChannel = chosen.getCabinetChannel(); } String messageStr = cabinetType + "," + channelToCount.entrySet().stream() diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java index 6ca182e..39d865a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java @@ -12,9 +12,10 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import static java.util.Collections.singletonList; @@ -62,7 +63,8 @@ public class TradeProductSkuOrderHandler implements TradeOrderHandler { } /** - * 售货机订单:按 SPU 分配库存并扣减。从每个 SPU 下有库存的货道(SKU)中贪婪分配。 + * 售货机订单:按 SPU 分配库存并扣减。从每个 SPU 下有库存的货道(SKU)中随机分配, + * 出货时从对应货道出货,保证扣减与出货货道一致。 */ private void deductStockBySpuAllocation(List orderItems) { List deductItems = new ArrayList<>(); @@ -76,15 +78,14 @@ public class TradeProductSkuOrderHandler implements TradeOrderHandler { if (CollUtil.isEmpty(skus)) { continue; } - // 优先从库存多的货道扣,若有订单行的 skuId 且有库存则优先 - List sorted = skus.stream() + // 有库存的货道随机打乱,随机选货道扣减(出货时也用这批货道) + List inStock = skus.stream() .filter(s -> s.getStock() != null && s.getStock() > 0) - .sorted(Comparator.comparingInt(s -> s.getId().equals(item.getSkuId()) ? 0 : 1) - .thenComparing(Comparator.comparingInt(s -> s.getStock() != null ? s.getStock() : 0).reversed())) .collect(Collectors.toList()); + Collections.shuffle(inStock, ThreadLocalRandom.current()); int remain = item.getCount(); List allocation = new ArrayList<>(); - for (ProductSkuRespDTO sku : sorted) { + for (ProductSkuRespDTO sku : inStock) { if (remain <= 0) { break; }