|
|
@@ -758,8 +758,14 @@ public class ProductOrderService {
|
|
|
int firstUnitPrice = 0;
|
|
|
// 收集下单口径行项(解析后身份 + 结算单价),用于服务端权威的套餐自动定价
|
|
|
java.util.List<LineItem> bundleLines = new java.util.ArrayList<>();
|
|
|
+ Long comboSkuId = null; // 首个组合行 SKU(多商品订单组合可不在首行)
|
|
|
|
|
|
for (OrderItemVO item : dto.getItems()) {
|
|
|
+ ProductSku itemSku = item.getSkuId() != null ? productSkuService.getById(item.getSkuId()) : null;
|
|
|
+ boolean comboItem = productSkuService.hasComboItems(itemSku);
|
|
|
+ if (comboItem && comboSkuId == null) {
|
|
|
+ comboSkuId = item.getSkuId();
|
|
|
+ }
|
|
|
Product product = productMapper.selectById(item.getProductId());
|
|
|
if (product == null) {
|
|
|
return Result.error("商品不存在: " + item.getProductId());
|
|
|
@@ -767,9 +773,7 @@ public class ProductOrderService {
|
|
|
if (!"on_shelf".equals(product.getStatus())) {
|
|
|
return Result.error("商品已下架: " + product.getName());
|
|
|
}
|
|
|
- ProductSku itemSku = item.getSkuId() != null ? productSkuService.getById(item.getSkuId()) : null;
|
|
|
- boolean comboItem = productSkuService.hasComboItems(itemSku);
|
|
|
- // 实物商品校验库存;虚拟商品/优惠券跳过库存检查(组合行由组成商品库存校验,不拦截父商品)
|
|
|
+ // 实物商品校验库存;虚拟商品/优惠券跳过库存检查(组合商品按子商品校验)
|
|
|
if (!comboItem && "physical".equals(product.getProductType())
|
|
|
&& product.getStock() != null
|
|
|
&& product.getStock() < item.getQuantity()) {
|
|
|
@@ -782,7 +786,7 @@ public class ProductOrderService {
|
|
|
return Result.error(bundleCheck.getMessage());
|
|
|
}
|
|
|
}
|
|
|
- // 组合 SKU:校验各组成商品/SKU 库存
|
|
|
+ // 组合商品:校验各子商品库存(子 SKU / 子商品双通道)
|
|
|
if (comboItem) {
|
|
|
for (ProductSkuComboItem ci : itemSku.getComboItems()) {
|
|
|
int need = (ci.getQuantity() == null ? 1 : ci.getQuantity()) * item.getQuantity();
|
|
|
@@ -859,7 +863,7 @@ public class ProductOrderService {
|
|
|
order.setProductId(firstProduct.getId());
|
|
|
order.setProductName(firstProduct.getName() + "等多件");
|
|
|
order.setProductType(firstProduct.getProductType());
|
|
|
- order.setSkuId(firstItem.getSkuId());
|
|
|
+ order.setSkuId(comboSkuId != null ? comboSkuId : firstItem.getSkuId());
|
|
|
order.setBuyerId(buyerId);
|
|
|
order.setFamilyId(buyer != null ? buyer.getFamilyId() : null);
|
|
|
order.setQuantity(totalQuantity);
|
|
|
@@ -1287,8 +1291,8 @@ public class ProductOrderService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 组合订单支付成功:按组合明细逐项扣减库存(不扣父商品库存)。
|
|
|
- * 任一组成 SKU/商品库存不足抛异常,由外层事务整体回滚。
|
|
|
+ * 组合 SKU 出库:按组合行子项(子 SKU / 子商品)扣减库存并记出库流水。
|
|
|
+ * 包级可见,供单测直接调用。任一子项库存不足抛 {@link RuntimeException} 由事务回滚。
|
|
|
*/
|
|
|
void deductComboStock(ProductSku combo, ProductOrder order, String orderNo) {
|
|
|
for (ProductSkuComboItem ci : combo.getComboItems()) {
|
|
|
@@ -1311,9 +1315,8 @@ public class ProductOrderService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 组合订单取消/退款:反向回补各组成商品/SKU 库存(不回补父商品)。
|
|
|
- *
|
|
|
- * @return true 如果该订单是组合订单且已完成回补;false 走原回补逻辑
|
|
|
+ * 组合订单回补:若订单 SKU 为组合,回补各子项库存并记退款入库流水,返回 true;
|
|
|
+ * 非组合订单返回 false,由调用方回退到原有父商品回补逻辑。包级可见,供单测直接调用。
|
|
|
*/
|
|
|
boolean restockComboOrder(ProductOrder order) {
|
|
|
ProductSku sku = order.getSkuId() != null ? productSkuService.getById(order.getSkuId()) : null;
|
|
|
@@ -1353,7 +1356,7 @@ public class ProductOrderService {
|
|
|
releaseCouponForCancelledOrder(order.getCouponId());
|
|
|
// F-5:退还下单时抵扣的 CF 值(原路返还到购买人 member 账本,幂等 refId = 商品订单 ID)
|
|
|
refundOrderPoints(order, "取消订单退还CF值:" + orderNo);
|
|
|
- // Restore stock on cancel(组合订单走组合回补,不回补父商品)
|
|
|
+ // Restore stock on cancel(组合订单回补子商品,非组合回补父商品)
|
|
|
if (!restockComboOrder(order)) {
|
|
|
productService.increaseStock(order.getProductId(), order.getQuantity());
|
|
|
// 记录退款入库流水
|
|
|
@@ -2024,7 +2027,6 @@ public class ProductOrderService {
|
|
|
return Result.error("该订单无法关闭");
|
|
|
}
|
|
|
if ("paid".equals(currentStatus) || "pending_receipt".equals(currentStatus)) {
|
|
|
- // 组合订单走组合回补,不回补父商品
|
|
|
if (!restockComboOrder(order)) {
|
|
|
productService.increaseStock(order.getProductId(), order.getQuantity());
|
|
|
}
|