|
@@ -44,7 +44,6 @@ import com.etotem.cfc.mapper.ProductOrderMapper;
|
|
|
import com.etotem.cfc.mapper.UserMapper;
|
|
import com.etotem.cfc.mapper.UserMapper;
|
|
|
import com.etotem.cfc.entity.ProductBundleItem;
|
|
import com.etotem.cfc.entity.ProductBundleItem;
|
|
|
import com.etotem.cfc.entity.ProductSku;
|
|
import com.etotem.cfc.entity.ProductSku;
|
|
|
-import com.etotem.cfc.entity.ProductSkuComboItem;
|
|
|
|
|
import com.etotem.cfc.mapper.ProductBundleItemMapper;
|
|
import com.etotem.cfc.mapper.ProductBundleItemMapper;
|
|
|
import com.etotem.cfc.service.ProductSkuService;
|
|
import com.etotem.cfc.service.ProductSkuService;
|
|
|
import com.etotem.cfc.service.AssessmentProductService;
|
|
import com.etotem.cfc.service.AssessmentProductService;
|
|
@@ -758,14 +757,9 @@ public class ProductOrderService {
|
|
|
int firstUnitPrice = 0;
|
|
int firstUnitPrice = 0;
|
|
|
// 收集下单口径行项(解析后身份 + 结算单价),用于服务端权威的套餐自动定价
|
|
// 收集下单口径行项(解析后身份 + 结算单价),用于服务端权威的套餐自动定价
|
|
|
java.util.List<LineItem> bundleLines = new java.util.ArrayList<>();
|
|
java.util.List<LineItem> bundleLines = new java.util.ArrayList<>();
|
|
|
- Long comboSkuId = null; // 首个组合行 SKU(多商品订单组合可不在首行)
|
|
|
|
|
|
|
|
|
|
for (OrderItemVO item : dto.getItems()) {
|
|
for (OrderItemVO item : dto.getItems()) {
|
|
|
ProductSku itemSku = item.getSkuId() != null ? productSkuService.getById(item.getSkuId()) : null;
|
|
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());
|
|
Product product = productMapper.selectById(item.getProductId());
|
|
|
if (product == null) {
|
|
if (product == null) {
|
|
|
return Result.error("商品不存在: " + item.getProductId());
|
|
return Result.error("商品不存在: " + item.getProductId());
|
|
@@ -773,8 +767,8 @@ public class ProductOrderService {
|
|
|
if (!"on_shelf".equals(product.getStatus())) {
|
|
if (!"on_shelf".equals(product.getStatus())) {
|
|
|
return Result.error("商品已下架: " + product.getName());
|
|
return Result.error("商品已下架: " + product.getName());
|
|
|
}
|
|
}
|
|
|
- // 实物商品校验库存;虚拟商品/优惠券跳过库存检查(组合商品按子商品校验)
|
|
|
|
|
- if (!comboItem && "physical".equals(product.getProductType())
|
|
|
|
|
|
|
+ // 实物商品校验库存;虚拟商品/优惠券跳过库存检查
|
|
|
|
|
+ if ("physical".equals(product.getProductType())
|
|
|
&& product.getStock() != null
|
|
&& product.getStock() != null
|
|
|
&& product.getStock() < item.getQuantity()) {
|
|
&& product.getStock() < item.getQuantity()) {
|
|
|
return Result.error("库存不足: " + product.getName());
|
|
return Result.error("库存不足: " + product.getName());
|
|
@@ -786,31 +780,9 @@ public class ProductOrderService {
|
|
|
return Result.error(bundleCheck.getMessage());
|
|
return Result.error(bundleCheck.getMessage());
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- // 组合商品:校验各子商品库存(子 SKU / 子商品双通道)
|
|
|
|
|
- if (comboItem) {
|
|
|
|
|
- for (ProductSkuComboItem ci : itemSku.getComboItems()) {
|
|
|
|
|
- int need = (ci.getQuantity() == null ? 1 : ci.getQuantity()) * item.getQuantity();
|
|
|
|
|
- if (ci.getChildSkuId() != null) {
|
|
|
|
|
- if (!productSkuService.hasStock(ci.getChildSkuId(), need)) {
|
|
|
|
|
- return Result.error("组合商品库存不足: " + ci.getChildProductName());
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- Product cp = productMapper.selectById(ci.getChildProductId());
|
|
|
|
|
- if (cp == null || cp.getStock() == null || cp.getStock() < need) {
|
|
|
|
|
- return Result.error("组合商品库存不足: "
|
|
|
|
|
- + (cp == null ? ci.getChildProductId() : cp.getName()));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
int unitPrice = memberPriceResolver.resolveEligibleMemberPrice(product, buyerId);
|
|
int unitPrice = memberPriceResolver.resolveEligibleMemberPrice(product, buyerId);
|
|
|
if (itemSku != null) {
|
|
if (itemSku != null) {
|
|
|
- if (comboItem) {
|
|
|
|
|
- if (itemSku.getPrice() != null) unitPrice = itemSku.getPrice();
|
|
|
|
|
- if (itemSku.getMinQuantity() != null && item.getQuantity() < itemSku.getMinQuantity()) {
|
|
|
|
|
- return Result.error(product.getName() + " 该组合最低需购买" + itemSku.getMinQuantity() + "件");
|
|
|
|
|
- }
|
|
|
|
|
- } else if (itemSku.getLinkedProductId() != null) {
|
|
|
|
|
|
|
+ if (itemSku.getLinkedProductId() != null) {
|
|
|
// Linked SKU: 转发到关联商品
|
|
// Linked SKU: 转发到关联商品
|
|
|
Product linkedProduct = productMapper.selectById(itemSku.getLinkedProductId());
|
|
Product linkedProduct = productMapper.selectById(itemSku.getLinkedProductId());
|
|
|
if (linkedProduct != null) {
|
|
if (linkedProduct != null) {
|
|
@@ -847,9 +819,7 @@ public class ProductOrderService {
|
|
|
itemRecord.setSkuId(item.getSkuId());
|
|
itemRecord.setSkuId(item.getSkuId());
|
|
|
itemRecord.setProductName(product.getName());
|
|
itemRecord.setProductName(product.getName());
|
|
|
itemRecord.setProductImage(product.getCoverImage());
|
|
itemRecord.setProductImage(product.getCoverImage());
|
|
|
- itemRecord.setSkuSpecs(comboItem
|
|
|
|
|
- ? productSkuService.buildComboSummary(itemSku.getComboItems())
|
|
|
|
|
- : item.getSpecOptionIds());
|
|
|
|
|
|
|
+ itemRecord.setSkuSpecs(item.getSpecOptionIds());
|
|
|
itemRecord.setPrice(java.math.BigDecimal.valueOf(unitPrice));
|
|
itemRecord.setPrice(java.math.BigDecimal.valueOf(unitPrice));
|
|
|
itemRecord.setQuantity(item.getQuantity());
|
|
itemRecord.setQuantity(item.getQuantity());
|
|
|
itemRecord.setSubtotal(java.math.BigDecimal.valueOf((long) unitPrice * item.getQuantity()));
|
|
itemRecord.setSubtotal(java.math.BigDecimal.valueOf((long) unitPrice * item.getQuantity()));
|
|
@@ -863,7 +833,7 @@ public class ProductOrderService {
|
|
|
order.setProductId(firstProduct.getId());
|
|
order.setProductId(firstProduct.getId());
|
|
|
order.setProductName(firstProduct.getName() + "等多件");
|
|
order.setProductName(firstProduct.getName() + "等多件");
|
|
|
order.setProductType(firstProduct.getProductType());
|
|
order.setProductType(firstProduct.getProductType());
|
|
|
- order.setSkuId(comboSkuId != null ? comboSkuId : firstItem.getSkuId());
|
|
|
|
|
|
|
+ order.setSkuId(firstItem.getSkuId());
|
|
|
order.setBuyerId(buyerId);
|
|
order.setBuyerId(buyerId);
|
|
|
order.setFamilyId(buyer != null ? buyer.getFamilyId() : null);
|
|
order.setFamilyId(buyer != null ? buyer.getFamilyId() : null);
|
|
|
order.setQuantity(totalQuantity);
|
|
order.setQuantity(totalQuantity);
|
|
@@ -1079,9 +1049,7 @@ public class ProductOrderService {
|
|
|
// 仅对实物商品扣减库存;虚拟商品/优惠券无库存概念
|
|
// 仅对实物商品扣减库存;虚拟商品/优惠券无库存概念
|
|
|
Product paidProduct = productMapper.selectById(order.getProductId());
|
|
Product paidProduct = productMapper.selectById(order.getProductId());
|
|
|
ProductSku paidSku = order.getSkuId() != null ? productSkuService.getById(order.getSkuId()) : null;
|
|
ProductSku paidSku = order.getSkuId() != null ? productSkuService.getById(order.getSkuId()) : null;
|
|
|
- if (productSkuService.hasComboItems(paidSku)) {
|
|
|
|
|
- deductComboStock(paidSku, order, orderNo);
|
|
|
|
|
- } else if (paidProduct != null && "physical".equals(paidProduct.getProductType())) {
|
|
|
|
|
|
|
+ if (paidProduct != null && "physical".equals(paidProduct.getProductType())) {
|
|
|
boolean stockOk = productService.decreaseStock(order.getProductId(), order.getQuantity());
|
|
boolean stockOk = productService.decreaseStock(order.getProductId(), order.getQuantity());
|
|
|
if (!stockOk) {
|
|
if (!stockOk) {
|
|
|
log.error("库存扣减失败: orderNo={}, productId={}, quantity={}",
|
|
log.error("库存扣减失败: orderNo={}, productId={}, quantity={}",
|
|
@@ -1290,73 +1258,6 @@ public class ProductOrderService {
|
|
|
return Result.success("支付成功");
|
|
return Result.success("支付成功");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 组合 SKU 出库:按组合行子项(子 SKU / 子商品)扣减库存并记出库流水。
|
|
|
|
|
- * 包级可见,供单测直接调用。任一子项库存不足抛 {@link RuntimeException} 由事务回滚。
|
|
|
|
|
- */
|
|
|
|
|
- void deductComboStock(ProductSku combo, ProductOrder order, String orderNo) {
|
|
|
|
|
- for (ProductSkuComboItem ci : combo.getComboItems()) {
|
|
|
|
|
- int unit = ci.getQuantity() == null ? 1 : ci.getQuantity();
|
|
|
|
|
- int need = unit * comboLineQuantity(order);
|
|
|
|
|
- if (ci.getChildSkuId() != null) {
|
|
|
|
|
- if (!productSkuService.decreaseStock(ci.getChildSkuId(), need)) {
|
|
|
|
|
- throw new RuntimeException("组合商品库存不足: skuId=" + ci.getChildSkuId());
|
|
|
|
|
- }
|
|
|
|
|
- inventoryService.recordOutbound(ci.getChildProductId(), ci.getChildSkuId(), need,
|
|
|
|
|
- "order", order.getId(), null, "组合出库:" + orderNo);
|
|
|
|
|
- } else {
|
|
|
|
|
- if (!productService.decreaseStock(ci.getChildProductId(), need)) {
|
|
|
|
|
- throw new RuntimeException("组合商品库存不足: productId=" + ci.getChildProductId());
|
|
|
|
|
- }
|
|
|
|
|
- inventoryService.recordOutbound(ci.getChildProductId(), null, need,
|
|
|
|
|
- "order", order.getId(), null, "组合出库:" + orderNo);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 组合订单回补:若订单 SKU 为组合,回补各子项库存并记退款入库流水,返回 true;
|
|
|
|
|
- * 非组合订单返回 false,由调用方回退到原有父商品回补逻辑。包级可见,供单测直接调用。
|
|
|
|
|
- */
|
|
|
|
|
- boolean restockComboOrder(ProductOrder order) {
|
|
|
|
|
- ProductSku sku = order.getSkuId() != null ? productSkuService.getById(order.getSkuId()) : null;
|
|
|
|
|
- if (!productSkuService.hasComboItems(sku)) return false;
|
|
|
|
|
- for (ProductSkuComboItem ci : sku.getComboItems()) {
|
|
|
|
|
- int unit = ci.getQuantity() == null ? 1 : ci.getQuantity();
|
|
|
|
|
- int need = unit * comboLineQuantity(order);
|
|
|
|
|
- if (ci.getChildSkuId() != null) {
|
|
|
|
|
- productSkuService.increaseStock(ci.getChildSkuId(), need);
|
|
|
|
|
- inventoryService.recordRefundInbound(ci.getChildProductId(), ci.getChildSkuId(), need,
|
|
|
|
|
- "order", order.getId(), null, "组合退款入库:" + order.getOrderNo());
|
|
|
|
|
- } else {
|
|
|
|
|
- productService.increaseStock(ci.getChildProductId(), need);
|
|
|
|
|
- inventoryService.recordRefundInbound(ci.getChildProductId(), null, need,
|
|
|
|
|
- "order", order.getId(), null, "组合退款入库:" + order.getOrderNo());
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 组合订单的组合行数量:查 order_items 中与订单 SKU 匹配行的数量合计。
|
|
|
|
|
- * 混合订单(组合行 + 其他行)下 order.quantity 为订单总量,不能用于子项扣减/回补;
|
|
|
|
|
- * 无匹配行(如旧数据/单测缺省)时回退 order.quantity。包级可见,供单测直接调用。
|
|
|
|
|
- */
|
|
|
|
|
- int comboLineQuantity(ProductOrder order) {
|
|
|
|
|
- if (order.getId() == null || order.getSkuId() == null) {
|
|
|
|
|
- return order.getQuantity() == null ? 1 : order.getQuantity();
|
|
|
|
|
- }
|
|
|
|
|
- List<OrderItem> comboLines = orderItemMapper.selectList(
|
|
|
|
|
- new LambdaQueryWrapper<OrderItem>()
|
|
|
|
|
- .eq(OrderItem::getOrderId, order.getId())
|
|
|
|
|
- .eq(OrderItem::getSkuId, order.getSkuId()));
|
|
|
|
|
- int q = 0;
|
|
|
|
|
- for (OrderItem li : comboLines) {
|
|
|
|
|
- q += li.getQuantity() == null ? 0 : li.getQuantity();
|
|
|
|
|
- }
|
|
|
|
|
- return q > 0 ? q : (order.getQuantity() == null ? 1 : order.getQuantity());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
public Result<String> cancel(String orderNo, Long userId) {
|
|
public Result<String> cancel(String orderNo, Long userId) {
|
|
|
ProductOrder order = orderMapper.selectOne(
|
|
ProductOrder order = orderMapper.selectOne(
|
|
|
new LambdaQueryWrapper<ProductOrder>().eq(ProductOrder::getOrderNo, orderNo));
|
|
new LambdaQueryWrapper<ProductOrder>().eq(ProductOrder::getOrderNo, orderNo));
|
|
@@ -1376,13 +1277,11 @@ public class ProductOrderService {
|
|
|
releaseCouponForCancelledOrder(order.getCouponId());
|
|
releaseCouponForCancelledOrder(order.getCouponId());
|
|
|
// F-5:退还下单时抵扣的 CF 值(原路返还到购买人 member 账本,幂等 refId = 商品订单 ID)
|
|
// F-5:退还下单时抵扣的 CF 值(原路返还到购买人 member 账本,幂等 refId = 商品订单 ID)
|
|
|
refundOrderPoints(order, "取消订单退还CF值:" + orderNo);
|
|
refundOrderPoints(order, "取消订单退还CF值:" + orderNo);
|
|
|
- // Restore stock on cancel(组合订单回补子商品,非组合回补父商品)
|
|
|
|
|
- if (!restockComboOrder(order)) {
|
|
|
|
|
- productService.increaseStock(order.getProductId(), order.getQuantity());
|
|
|
|
|
- // 记录退款入库流水
|
|
|
|
|
- inventoryService.recordRefundInbound(order.getProductId(), null, order.getQuantity(),
|
|
|
|
|
- "order", order.getId(), null, "取消订单:" + orderNo);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Restore stock on cancel
|
|
|
|
|
+ productService.increaseStock(order.getProductId(), order.getQuantity());
|
|
|
|
|
+ // 记录退款入库流水
|
|
|
|
|
+ inventoryService.recordRefundInbound(order.getProductId(), null, order.getQuantity(),
|
|
|
|
|
+ "order", order.getId(), null, "取消订单:" + orderNo);
|
|
|
// 特价订单回滚扣量(与恢复库存并列,失败仅记日志不阻断取消)
|
|
// 特价订单回滚扣量(与恢复库存并列,失败仅记日志不阻断取消)
|
|
|
try {
|
|
try {
|
|
|
specialPriceService.releaseQuota(order.getSpecialDealId(), order.getQuantity());
|
|
specialPriceService.releaseQuota(order.getSpecialDealId(), order.getQuantity());
|
|
@@ -1827,11 +1726,9 @@ public class ProductOrderService {
|
|
|
order.setStatus("refunded");
|
|
order.setStatus("refunded");
|
|
|
order.setUpdatedAt(new Date());
|
|
order.setUpdatedAt(new Date());
|
|
|
orderMapper.updateById(order);
|
|
orderMapper.updateById(order);
|
|
|
- if (!restockComboOrder(order)) {
|
|
|
|
|
- productService.increaseStock(order.getProductId(), order.getQuantity());
|
|
|
|
|
- inventoryService.recordRefundInbound(order.getProductId(), null, order.getQuantity(),
|
|
|
|
|
- "order", order.getId(), adminId, "退款:" + orderNo);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ productService.increaseStock(order.getProductId(), order.getQuantity());
|
|
|
|
|
+ inventoryService.recordRefundInbound(order.getProductId(), null, order.getQuantity(),
|
|
|
|
|
+ "order", order.getId(), adminId, "退款:" + orderNo);
|
|
|
// 退款成功:归还优惠券;CF 兑换来的券同时退还 CF 值
|
|
// 退款成功:归还优惠券;CF 兑换来的券同时退还 CF 值
|
|
|
releaseCouponForCancelledOrder(order.getCouponId());
|
|
releaseCouponForCancelledOrder(order.getCouponId());
|
|
|
// F-5:退还下单时抵扣的 CF 值(原路返还到购买人 member 账本)
|
|
// F-5:退还下单时抵扣的 CF 值(原路返还到购买人 member 账本)
|
|
@@ -2047,9 +1944,7 @@ public class ProductOrderService {
|
|
|
return Result.error("该订单无法关闭");
|
|
return Result.error("该订单无法关闭");
|
|
|
}
|
|
}
|
|
|
if ("paid".equals(currentStatus) || "pending_receipt".equals(currentStatus)) {
|
|
if ("paid".equals(currentStatus) || "pending_receipt".equals(currentStatus)) {
|
|
|
- if (!restockComboOrder(order)) {
|
|
|
|
|
- productService.increaseStock(order.getProductId(), order.getQuantity());
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ productService.increaseStock(order.getProductId(), order.getQuantity());
|
|
|
}
|
|
}
|
|
|
order.setStatus("closed");
|
|
order.setStatus("closed");
|
|
|
order.setUpdatedAt(new Date());
|
|
order.setUpdatedAt(new Date());
|