|
|
@@ -11,6 +11,9 @@ import com.etotem.cfc.entity.ProductGiftRule;
|
|
|
import com.etotem.cfc.mapper.CartMapper;
|
|
|
import com.etotem.cfc.mapper.ProductBundleItemMapper;
|
|
|
import com.etotem.cfc.mapper.ProductMapper;
|
|
|
+import com.etotem.cfc.pricing.BundleDiscountResult;
|
|
|
+import com.etotem.cfc.pricing.BundleHit;
|
|
|
+import com.etotem.cfc.pricing.LineItem;
|
|
|
import com.etotem.cfc.service.PpointService;
|
|
|
import com.etotem.cfc.service.ProductGiftRuleService;
|
|
|
import com.etotem.cfc.service.ProductSkuService;
|
|
|
@@ -50,6 +53,12 @@ public class CartService {
|
|
|
@Resource
|
|
|
private SysConfigService sysConfigService;
|
|
|
|
|
|
+ @Resource
|
|
|
+ private BundlePricingService bundlePricingService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private MemberPriceResolver memberPriceResolver;
|
|
|
+
|
|
|
@Transactional
|
|
|
public CartItemDTO addToCart(Long userId, Long productId, Integer quantity, Long skuId) {
|
|
|
Product product = productMapper.selectById(productId);
|
|
|
@@ -127,12 +136,14 @@ public class CartService {
|
|
|
// 自动套餐模式:检测购物车子商品组合是否凑成某套餐,命中则挂 bundleMatch 提示
|
|
|
attachBundleMatches(cartList, result);
|
|
|
|
|
|
- // 计算送会员商品聚合信息
|
|
|
+ // 订单套餐自动定价优惠(替换原"送会员一口价"结算价逻辑;giftPromo 置空保持旧前端兼容)
|
|
|
CartItemDTO.GiftPromo giftPromo = computeGiftPromo(cartList, productMap);
|
|
|
+ CartItemDTO.BundlePromo bundlePromo = computeBundlePromo(cartList, productMap, userId);
|
|
|
|
|
|
CartListResultDTO cartResult = new CartListResultDTO();
|
|
|
cartResult.setItems(result);
|
|
|
cartResult.setGiftPromo(giftPromo);
|
|
|
+ cartResult.setBundlePromo(bundlePromo);
|
|
|
return cartResult;
|
|
|
}
|
|
|
|
|
|
@@ -408,78 +419,83 @@ public class CartService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 计算购物车中送会员商品的聚合信息:原价合计、是否达标、折扣金额等。
|
|
|
- * 购物车所有商品默认视为已选中(checked=true)。
|
|
|
+ * 送会员一口价已废弃(2026-10-30 设计:改为订单套餐自动定价)。
|
|
|
+ * 原"达标→一口价1980"结算逻辑不再产生;giftPromo 恒为空,供旧前端读 active=false。
|
|
|
*/
|
|
|
private CartItemDTO.GiftPromo computeGiftPromo(List<Cart> cartList, Map<Long, Product> productMap) {
|
|
|
+ return buildEmptyGiftPromo();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 订单套餐自动定价优惠:扫描购物车商品,按下单结算口径(会员/SKU/linked 价)聚合,
|
|
|
+ * 匹配 product_bundle_items 中 bundle_sku_id 非空的定价套餐,贪心选省最多,返回 bundlePromo。
|
|
|
+ * 展示口径与下单一致(MemberPriceResolver),保证页面提示=实际扣款。
|
|
|
+ */
|
|
|
+ private CartItemDTO.BundlePromo computeBundlePromo(List<Cart> cartList, Map<Long, Product> productMap, Long userId) {
|
|
|
if (cartList == null || cartList.isEmpty()) {
|
|
|
- return buildEmptyGiftPromo();
|
|
|
+ return emptyBundlePromo();
|
|
|
}
|
|
|
-
|
|
|
- // 查询购物车中所有商品的送会员规则
|
|
|
- List<Long> giftProductIds = new ArrayList<>();
|
|
|
+ List<LineItem> lines = new ArrayList<>();
|
|
|
for (Cart cart : cartList) {
|
|
|
- Long pid = cart.getProductId();
|
|
|
- if (pid != null && !giftProductIds.contains(pid)) {
|
|
|
- giftProductIds.add(pid);
|
|
|
+ if (cart == null || cart.getProductId() == null || cart.getQuantity() == null || cart.getQuantity() <= 0) {
|
|
|
+ continue;
|
|
|
}
|
|
|
- }
|
|
|
- List<ProductGiftRule> giftRules = productGiftRuleService.listActiveByProductIds(giftProductIds);
|
|
|
-
|
|
|
- // 按 productId 建立 rule 映射
|
|
|
- Map<Long, ProductGiftRule> ruleMap = new HashMap<>();
|
|
|
- for (ProductGiftRule rule : giftRules) {
|
|
|
- if (rule.getProductId() != null) {
|
|
|
- ruleMap.put(rule.getProductId(), rule);
|
|
|
+ Product product = productMap.get(cart.getProductId());
|
|
|
+ if (product == null) {
|
|
|
+ continue;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- // 计算送会员商品原价合计
|
|
|
- int giftOriginalTotal = 0;
|
|
|
- for (Cart cart : cartList) {
|
|
|
- if (ruleMap.containsKey(cart.getProductId())) {
|
|
|
- Product product = productMap.get(cart.getProductId());
|
|
|
- if (product != null && product.getPrice() != null && cart.getQuantity() != null) {
|
|
|
- giftOriginalTotal += product.getPrice() * cart.getQuantity();
|
|
|
+ ProductSku sku = null;
|
|
|
+ if (cart.getSkuId() != null) {
|
|
|
+ sku = productSkuService.getById(cart.getSkuId());
|
|
|
+ }
|
|
|
+ // 解析后身份 + 下单口径单价(linked 转发商品、SKU 价、否则会员价)
|
|
|
+ Long resolvedProductId = product.getId();
|
|
|
+ int unitPrice = memberPriceResolver.resolveEligibleMemberPrice(product, userId);
|
|
|
+ if (sku != null && sku.getLinkedProductId() != null) {
|
|
|
+ Product linked = productMapper.selectById(sku.getLinkedProductId());
|
|
|
+ if (linked != null) {
|
|
|
+ resolvedProductId = linked.getId();
|
|
|
+ unitPrice = linked.getPrice() != null ? linked.getPrice() : 0;
|
|
|
}
|
|
|
+ } else if (sku != null && sku.getPrice() != null) {
|
|
|
+ unitPrice = sku.getPrice();
|
|
|
}
|
|
|
+ lines.add(new LineItem(resolvedProductId, cart.getSkuId(), cart.getQuantity(), unitPrice));
|
|
|
}
|
|
|
-
|
|
|
- // 读取配置
|
|
|
- String thresholdStr = sysConfigService.getValue("gift_membership_original_threshold");
|
|
|
- int threshold;
|
|
|
- try {
|
|
|
- threshold = thresholdStr != null ? Integer.parseInt(thresholdStr) : 0;
|
|
|
- } catch (NumberFormatException e) {
|
|
|
- threshold = 0;
|
|
|
- }
|
|
|
-
|
|
|
- String payPriceStr = sysConfigService.getValue("gift_membership_pay_price");
|
|
|
- int payPrice;
|
|
|
- try {
|
|
|
- payPrice = payPriceStr != null ? Integer.parseInt(payPriceStr) : 0;
|
|
|
- } catch (NumberFormatException e) {
|
|
|
- payPrice = 0;
|
|
|
+ if (lines.isEmpty()) {
|
|
|
+ return emptyBundlePromo();
|
|
|
}
|
|
|
+ BundleDiscountResult result = bundlePricingService.compute(lines);
|
|
|
+ return toBundlePromo(result);
|
|
|
+ }
|
|
|
|
|
|
- if (threshold <= 0) {
|
|
|
- return buildEmptyGiftPromo();
|
|
|
+ private CartItemDTO.BundlePromo toBundlePromo(BundleDiscountResult r) {
|
|
|
+ CartItemDTO.BundlePromo promo = new CartItemDTO.BundlePromo();
|
|
|
+ promo.setActive(r.isActive());
|
|
|
+ promo.setOriginalTotal(r.getOriginalTotal() != null ? r.getOriginalTotal() : 0);
|
|
|
+ promo.setPayPrice(r.getPayPrice() != null ? r.getPayPrice() : 0);
|
|
|
+ promo.setDiscount(r.getDiscount() != null ? r.getDiscount() : 0);
|
|
|
+ List<CartItemDTO.BundleItemHit> hits = new ArrayList<>();
|
|
|
+ if (r.getBundleList() != null) {
|
|
|
+ for (BundleHit hit : r.getBundleList()) {
|
|
|
+ CartItemDTO.BundleItemHit h = new CartItemDTO.BundleItemHit();
|
|
|
+ h.setBundleSkuId(hit.getBundleSkuId());
|
|
|
+ h.setBundleName(hit.getBundleName());
|
|
|
+ h.setTimes(hit.getTimes());
|
|
|
+ h.setOriginalTotal(hit.getOriginalTotal());
|
|
|
+ h.setBundlePrice(hit.getBundlePrice());
|
|
|
+ h.setSaving(hit.getSaving());
|
|
|
+ hits.add(h);
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- boolean active = giftOriginalTotal >= threshold;
|
|
|
- int discount = active ? Math.max(0, giftOriginalTotal - payPrice) : 0;
|
|
|
- int remaining = active ? 0 : Math.max(0, threshold - giftOriginalTotal);
|
|
|
-
|
|
|
- CartItemDTO.GiftPromo promo = new CartItemDTO.GiftPromo();
|
|
|
- promo.setGiftOriginalTotal(giftOriginalTotal);
|
|
|
- promo.setThreshold(threshold);
|
|
|
- promo.setPayPrice(active ? payPrice : 0);
|
|
|
- promo.setDiscount(discount);
|
|
|
- promo.setActive(active);
|
|
|
- promo.setRemaining(remaining);
|
|
|
+ promo.setBundleList(hits);
|
|
|
return promo;
|
|
|
}
|
|
|
|
|
|
+ private CartItemDTO.BundlePromo emptyBundlePromo() {
|
|
|
+ return toBundlePromo(BundleDiscountResult.inactive());
|
|
|
+ }
|
|
|
+
|
|
|
private CartItemDTO.GiftPromo buildEmptyGiftPromo() {
|
|
|
CartItemDTO.GiftPromo promo = new CartItemDTO.GiftPromo();
|
|
|
promo.setGiftOriginalTotal(0);
|