package cn.iocoder.yudao.module.trade.service.price; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO; import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi; import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi; import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeProductSettlementRespVO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; import cn.iocoder.yudao.module.trade.service.price.calculator.TradeDiscountActivityPriceCalculator; import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator; import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL; /** * 价格计算 Service 实现类 * * @author 芋道源码 */ @Service @Validated @Slf4j public class TradePriceServiceImpl implements TradePriceService { @Resource private ProductSkuApi productSkuApi; @Resource private ProductSpuApi productSpuApi; @Resource private DiscountActivityApi discountActivityApi; @Resource private RewardActivityApi rewardActivityApi; @Resource private List priceCalculators; @Resource private TradeDiscountActivityPriceCalculator discountActivityPriceCalculator; @Override public TradePriceCalculateRespBO calculateOrderPrice(TradePriceCalculateReqBO calculateReqBO) { // 1.1 获得商品 SKU 数组 List skuList = checkSkuList(calculateReqBO); // 1.2 获得商品 SPU 数组 List spuList = checkSpuList(skuList); // 2.1 计算价格 TradePriceCalculateRespBO calculateRespBO = TradePriceCalculatorHelper .buildCalculateResp(calculateReqBO, spuList, skuList); priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, calculateRespBO)); // 2.2 如果最终支付金额小于等于 0,则抛出业务异常 if (calculateReqBO.getPointActivityId() == null // 积分订单,允许支付金额为 0 && calculateRespBO.getPrice().getPayPrice() <= 0) { log.error("[calculatePrice][价格计算不正确,请求 calculateReqDTO({}),结果 priceCalculate({})]", calculateReqBO, calculateRespBO); throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL); } return calculateRespBO; } private List checkSkuList(TradePriceCalculateReqBO reqBO) { // 获得商品 SKU 数组 Map skuIdCountMap = convertMap(reqBO.getItems(), TradePriceCalculateReqBO.Item::getSkuId, TradePriceCalculateReqBO.Item::getCount); List skus = productSkuApi.getSkuList(skuIdCountMap.keySet()); // 校验商品 SKU skus.forEach(sku -> { Integer count = skuIdCountMap.get(sku.getId()); if (count == null) { throw exception(SKU_NOT_EXISTS); } if (count > sku.getStock()) { throw exception(SKU_STOCK_NOT_ENOUGH); } }); return skus; } private List checkSpuList(List skuList) { return productSpuApi.validateSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId)); } @Override public List calculateProductPrice(Long userId, List spuIds) { // 1.1 获得 SPU 与 SKU 的映射 List allSkuList = productSkuApi.getSkuListBySpuId(spuIds); Map> spuIdAndSkuListMap = convertMultiMap(allSkuList, ProductSkuRespDTO::getSpuId); // 1.2 获得会员等级 MemberLevelRespDTO level = discountActivityPriceCalculator.getMemberLevel(userId); // 1.3 获得限时折扣活动 Map skuIdAndDiscountMap = convertMap( discountActivityApi.getMatchDiscountProductListBySkuIds(convertSet(allSkuList, ProductSkuRespDTO::getId)), DiscountProductRespDTO::getSkuId); // 1.4 获得满减送活动 List rewardActivityMap = rewardActivityApi.getMatchRewardActivityListBySpuIds(spuIds); // 2. 价格计算 return convertList(spuIds, spuId -> { AppTradeProductSettlementRespVO spuVO = new AppTradeProductSettlementRespVO().setSpuId(spuId); // 2.1 优惠价格 List skuList = spuIdAndSkuListMap.get(spuId); List skuVOList = convertList(skuList, sku -> { AppTradeProductSettlementRespVO.Sku skuVO = new AppTradeProductSettlementRespVO.Sku() .setId(sku.getId()); TradePriceCalculateRespBO.OrderItem orderItem = new TradePriceCalculateRespBO.OrderItem() .setPayPrice(sku.getPrice()).setCount(1); // 计算限时折扣的优惠价格 DiscountProductRespDTO discountProduct = skuIdAndDiscountMap.get(sku.getId()); Integer discountPrice = discountActivityPriceCalculator.calculateActivityPrice(discountProduct, orderItem); // 计算 VIP 优惠金额 Integer vipPrice = discountActivityPriceCalculator.calculateVipPrice(level, orderItem); if (discountPrice <= 0 && vipPrice <= 0) { return skuVO; } // 选择一个大的优惠 if (discountPrice > vipPrice) { return skuVO.setPromotionPrice(sku.getPrice() - discountPrice) .setPromotionType(PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()) .setPromotionId(discountProduct.getId()).setPromotionEndTime(discountProduct.getActivityEndTime()); } else { return skuVO.setPromotionPrice(sku.getPrice() - vipPrice) .setPromotionType(PromotionTypeEnum.MEMBER_LEVEL.getType()); } }); spuVO.setSkus(skuVOList); // 2.2 满减送活动 RewardActivityMatchRespDTO rewardActivity = CollUtil.findOne(rewardActivityMap, activity -> CollUtil.contains(activity.getSpuIds(), spuId)); spuVO.setRewardActivity(BeanUtils.toBean(rewardActivity, AppTradeProductSettlementRespVO.RewardActivity.class)); return spuVO; }); } }