|
|
@@ -5,15 +5,10 @@ import com.etotem.cfc.dto.CartItemDTO;
|
|
|
import com.etotem.cfc.dto.CartListResultDTO;
|
|
|
import com.etotem.cfc.entity.Cart;
|
|
|
import com.etotem.cfc.entity.Product;
|
|
|
-import com.etotem.cfc.entity.ProductBundleItem;
|
|
|
import com.etotem.cfc.entity.ProductSku;
|
|
|
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;
|
|
|
@@ -41,9 +36,6 @@ public class CartService {
|
|
|
@Resource
|
|
|
private ProductMapper productMapper;
|
|
|
|
|
|
- @Resource
|
|
|
- private ProductBundleItemMapper bundleItemMapper;
|
|
|
-
|
|
|
@Resource
|
|
|
private ProductSkuService productSkuService;
|
|
|
|
|
|
@@ -53,9 +45,6 @@ public class CartService {
|
|
|
@Resource
|
|
|
private SysConfigService sysConfigService;
|
|
|
|
|
|
- @Resource
|
|
|
- private BundlePricingService bundlePricingService;
|
|
|
-
|
|
|
@Resource
|
|
|
private MemberPriceResolver memberPriceResolver;
|
|
|
|
|
|
@@ -126,24 +115,17 @@ public class CartService {
|
|
|
CartItemDTO dto = buildCartItemDTO(item);
|
|
|
if (dto != null) {
|
|
|
Product product = productMap.get(item.getProductId());
|
|
|
- if (product != null && "bundle".equals(product.getProductType())) {
|
|
|
- dto.setBundleInfo(buildBundleInfo(product, dto));
|
|
|
- }
|
|
|
result.add(dto);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 自动套餐模式:检测购物车子商品组合是否凑成某套餐,命中则挂 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;
|
|
|
}
|
|
|
|
|
|
@@ -152,144 +134,6 @@ public class CartService {
|
|
|
* (每个子商品数量 ≥ 组成数量),则在该子商品项上挂 bundleMatch(含套餐价/节省金额/组成明细)。
|
|
|
* 命中多个套餐时,参与项上只挂第一个命中结果,供前端提示「可享套餐价」。
|
|
|
*/
|
|
|
- private void attachBundleMatches(List<Cart> cartList, List<CartItemDTO> result) {
|
|
|
- if (cartList == null || cartList.isEmpty() || result == null || result.isEmpty()) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 1. 聚合购物车数量(按 productId / productId+skuId 两级)
|
|
|
- Map<Long, Integer> productQtyMap = new HashMap<>();
|
|
|
- Map<String, Integer> productSkuQtyMap = new HashMap<>();
|
|
|
- for (Cart c : cartList) {
|
|
|
- productQtyMap.put(c.getProductId(), productQtyMap.getOrDefault(c.getProductId(), 0) + c.getQuantity());
|
|
|
- if (c.getSkuId() != null) {
|
|
|
- String key = c.getProductId() + "_" + c.getSkuId();
|
|
|
- productSkuQtyMap.put(key, productSkuQtyMap.getOrDefault(key, 0) + c.getQuantity());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 查询所有 bundle 商品
|
|
|
- List<Product> bundles = productMapper.selectList(
|
|
|
- new LambdaQueryWrapper<Product>().eq(Product::getProductType, "bundle"));
|
|
|
- if (bundles == null || bundles.isEmpty()) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 3. 对每个 bundle 检测购物车覆盖情况
|
|
|
- for (Product bundle : bundles) {
|
|
|
- if (bundle.getId() == null) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- List<ProductBundleItem> items = bundleItemMapper.selectList(
|
|
|
- new LambdaQueryWrapper<ProductBundleItem>()
|
|
|
- .eq(ProductBundleItem::getBundleProductId, bundle.getId()));
|
|
|
- if (items == null || items.isEmpty()) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- // 计算最大可匹配组数 + 1 组子商品单品总价
|
|
|
- int maxGroups = Integer.MAX_VALUE;
|
|
|
- int singleTotalPrice = 0;
|
|
|
- boolean matchable = true;
|
|
|
- for (ProductBundleItem bi : items) {
|
|
|
- int needQty = bi.getQuantity() != null ? bi.getQuantity() : 1;
|
|
|
- Integer haveQty;
|
|
|
- if (bi.getChildSkuId() != null) {
|
|
|
- haveQty = productSkuQtyMap.get(bi.getChildProductId() + "_" + bi.getChildSkuId());
|
|
|
- } else {
|
|
|
- haveQty = productQtyMap.get(bi.getChildProductId());
|
|
|
- }
|
|
|
- if (haveQty == null || haveQty < needQty) {
|
|
|
- matchable = false;
|
|
|
- break;
|
|
|
- }
|
|
|
- maxGroups = Math.min(maxGroups, haveQty / needQty);
|
|
|
- Product child = productMapper.selectById(bi.getChildProductId());
|
|
|
- if (child != null && child.getPrice() != null) {
|
|
|
- singleTotalPrice += child.getPrice() * needQty;
|
|
|
- }
|
|
|
- }
|
|
|
- if (!matchable || maxGroups <= 0) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- Long bundlePrice = bundle.getPrice() != null ? bundle.getPrice().longValue() : 0L;
|
|
|
- int saving = bundlePrice < singleTotalPrice ? singleTotalPrice - bundlePrice.intValue() : 0;
|
|
|
- if (saving <= 0) {
|
|
|
- continue; // 无优惠不提示
|
|
|
- }
|
|
|
-
|
|
|
- // 构造 BundleMatch
|
|
|
- CartItemDTO.BundleMatch match = new CartItemDTO.BundleMatch();
|
|
|
- match.setBundleProductId(bundle.getId());
|
|
|
- match.setBundleProductName(bundle.getName());
|
|
|
- match.setMatchedGroups(maxGroups);
|
|
|
- match.setBundlePrice(bundlePrice);
|
|
|
- match.setSingleTotalPrice(singleTotalPrice);
|
|
|
- match.setSaving(saving);
|
|
|
- List<CartItemDTO.BundleChild> children = new ArrayList<>();
|
|
|
- for (ProductBundleItem bi : items) {
|
|
|
- CartItemDTO.BundleChild bc = new CartItemDTO.BundleChild();
|
|
|
- bc.setChildProductId(bi.getChildProductId());
|
|
|
- bc.setQuantity(bi.getQuantity() != null ? bi.getQuantity() : 1);
|
|
|
- children.add(bc);
|
|
|
- }
|
|
|
- match.setChildren(children);
|
|
|
-
|
|
|
- // 4. 挂到参与组成的子商品项上(每项只挂第一个命中,供前端提示横幅)
|
|
|
- for (ProductBundleItem bi : items) {
|
|
|
- for (CartItemDTO dto : result) {
|
|
|
- if (dto.getProductId() != null && dto.getProductId().equals(bi.getChildProductId())
|
|
|
- && dto.getBundleMatch() == null) {
|
|
|
- dto.setBundleMatch(match);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private CartItemDTO.BundleInfo buildBundleInfo(Product bundleProduct, CartItemDTO dto) {
|
|
|
- List<ProductBundleItem> bundleItems = bundleItemMapper.selectList(
|
|
|
- new LambdaQueryWrapper<ProductBundleItem>()
|
|
|
- .eq(ProductBundleItem::getBundleProductId, bundleProduct.getId())
|
|
|
- );
|
|
|
- if (bundleItems == null || bundleItems.isEmpty()) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- CartItemDTO.BundleInfo info = new CartItemDTO.BundleInfo();
|
|
|
- info.setBundleProductId(bundleProduct.getId());
|
|
|
-
|
|
|
- List<Long> memberIds = new ArrayList<>();
|
|
|
- List<String> childNames = new ArrayList<>();
|
|
|
- int childQtyPerBundle = 0;
|
|
|
- for (ProductBundleItem bi : bundleItems) {
|
|
|
- memberIds.add(bi.getChildProductId());
|
|
|
- if (bi.getQuantity() != null) {
|
|
|
- childQtyPerBundle += bi.getQuantity();
|
|
|
- }
|
|
|
- Product child = productMapper.selectById(bi.getChildProductId());
|
|
|
- childNames.add(child != null ? child.getName() : "");
|
|
|
- }
|
|
|
- int cartQty = dto.getQuantity() != null ? dto.getQuantity() : 1;
|
|
|
- info.setChildProductIds(memberIds);
|
|
|
- info.setChildProductNames(childNames);
|
|
|
- info.setChildQuantityTotal(childQtyPerBundle * cartQty);
|
|
|
-
|
|
|
- Integer unitPrice = dto.getUnitPrice() != null ? dto.getUnitPrice() : 0;
|
|
|
- Integer quantity = dto.getQuantity() != null ? dto.getQuantity() : 1;
|
|
|
- Integer singleTotal = unitPrice * quantity;
|
|
|
- Long bundlePrice = bundleProduct.getPrice() != null ? bundleProduct.getPrice().longValue() : 0L;
|
|
|
- info.setBundlePrice(bundlePrice);
|
|
|
- info.setSingleTotalPrice(singleTotal);
|
|
|
-
|
|
|
- int saving = bundlePrice < singleTotal ? singleTotal - bundlePrice.intValue() : 0;
|
|
|
- info.setSaving(saving);
|
|
|
- return info;
|
|
|
- }
|
|
|
-
|
|
|
- @Transactional
|
|
|
public void updateQuantity(Long userId, Long productId, Integer quantity) {
|
|
|
Cart cart = cartMapper.selectOne(
|
|
|
new LambdaQueryWrapper<Cart>()
|
|
|
@@ -425,76 +269,6 @@ public class CartService {
|
|
|
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 emptyBundlePromo();
|
|
|
- }
|
|
|
- List<LineItem> lines = new ArrayList<>();
|
|
|
- for (Cart cart : cartList) {
|
|
|
- if (cart == null || cart.getProductId() == null || cart.getQuantity() == null || cart.getQuantity() <= 0) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- Product product = productMap.get(cart.getProductId());
|
|
|
- if (product == null) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- 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));
|
|
|
- }
|
|
|
- if (lines.isEmpty()) {
|
|
|
- return emptyBundlePromo();
|
|
|
- }
|
|
|
- BundleDiscountResult result = bundlePricingService.compute(lines);
|
|
|
- return toBundlePromo(result);
|
|
|
- }
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
- }
|
|
|
- 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);
|