清理价格计算相关的逻辑
This commit is contained in:
parent
c54b330cdf
commit
0e6d94876d
@ -1,18 +0,0 @@
|
|||||||
package cn.iocoder.mall.promotionservice;
|
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
|
||||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
@EnableDiscoveryClient
|
|
||||||
@EnableFeignClients(basePackages = {"cn.iocoder.mall.productservice.rpc"})
|
|
||||||
public class PromotionServiceApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(PromotionServiceApplication.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
package cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon;
|
|
||||||
|
|
||||||
import cn.iocoder.mall.mybatis.core.dataobject.BaseDO;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 优惠劵 DO
|
|
||||||
*/
|
|
||||||
@TableName("coupon_card")
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Accessors(chain = true)
|
|
||||||
public class CouponCardDO extends BaseDO {
|
|
||||||
|
|
||||||
// ========== 基本信息 BEGIN ==========
|
|
||||||
/**
|
|
||||||
* 优惠劵编号
|
|
||||||
*/
|
|
||||||
private Integer id;
|
|
||||||
/**
|
|
||||||
* 优惠劵(码)分组编号,{@link CouponTemplateDO} 的 id
|
|
||||||
*/
|
|
||||||
private Integer templateId;
|
|
||||||
/**
|
|
||||||
* 优惠劵名
|
|
||||||
*
|
|
||||||
* 冗余自 {@link CouponTemplateDO} 的 title
|
|
||||||
*
|
|
||||||
* TODO 芋艿,暂时不考虑冗余的更新
|
|
||||||
*/
|
|
||||||
private String title;
|
|
||||||
// /**
|
|
||||||
// * 核销码
|
|
||||||
// */
|
|
||||||
// private String verifyCode;
|
|
||||||
/**
|
|
||||||
* 优惠码状态
|
|
||||||
*
|
|
||||||
* 1-未使用
|
|
||||||
* 2-已使用
|
|
||||||
* 3-已失效
|
|
||||||
*/
|
|
||||||
private Integer status;
|
|
||||||
|
|
||||||
// ========== 基本信息 END ==========
|
|
||||||
|
|
||||||
// ========== 领取情况 BEGIN ==========
|
|
||||||
/**
|
|
||||||
* 用户编号
|
|
||||||
*/
|
|
||||||
private Integer userId;
|
|
||||||
/**
|
|
||||||
* 领取类型
|
|
||||||
*
|
|
||||||
* 1 - 用户主动领取
|
|
||||||
* 2 - 后台自动发放
|
|
||||||
*/
|
|
||||||
private Integer takeType;
|
|
||||||
// ========== 领取情况 END ==========
|
|
||||||
|
|
||||||
// ========== 使用规则 BEGIN ==========
|
|
||||||
/**
|
|
||||||
* 是否设置满多少金额可用,单位:分
|
|
||||||
*/
|
|
||||||
private Integer priceAvailable;
|
|
||||||
/**
|
|
||||||
* 生效开始时间
|
|
||||||
*/
|
|
||||||
private Date validStartTime;
|
|
||||||
/**
|
|
||||||
* 生效结束时间
|
|
||||||
*/
|
|
||||||
private Date validEndTime;
|
|
||||||
// ========== 使用规则 END ==========
|
|
||||||
|
|
||||||
// ========== 使用效果 BEGIN ==========
|
|
||||||
/**
|
|
||||||
* 优惠类型
|
|
||||||
*
|
|
||||||
* 1-代金卷
|
|
||||||
* 2-折扣卷
|
|
||||||
*/
|
|
||||||
private Integer preferentialType;
|
|
||||||
/**
|
|
||||||
* 折扣
|
|
||||||
*/
|
|
||||||
private Integer percentOff;
|
|
||||||
/**
|
|
||||||
* 优惠金额,单位:分。
|
|
||||||
*/
|
|
||||||
private Integer priceOff;
|
|
||||||
/**
|
|
||||||
* 折扣上限,仅在 {@link #preferentialType} 等于 2 时生效。
|
|
||||||
*
|
|
||||||
* 例如,折扣上限为 20 元,当使用 8 折优惠券,订单金额为 1000 元时,最高只可折扣 20 元,而非 80 元。
|
|
||||||
*/
|
|
||||||
private Integer discountPriceLimit;
|
|
||||||
// ========== 使用效果 END ==========
|
|
||||||
|
|
||||||
// ========== 使用情况 BEGIN ==========
|
|
||||||
// /**
|
|
||||||
// * 使用订单号
|
|
||||||
// */
|
|
||||||
// private Integer usedOrderId; // TODO 芋艿,暂时不考虑这个字段
|
|
||||||
// /**
|
|
||||||
// * 订单中优惠面值,单位:分
|
|
||||||
// */
|
|
||||||
// private Integer usedPrice; // TODO 芋艿,暂时不考虑这个字段
|
|
||||||
/**
|
|
||||||
* 使用时间
|
|
||||||
*/
|
|
||||||
private Date usedTime;
|
|
||||||
|
|
||||||
// TODO 芋艿,后续要加优惠劵的使用日志,因为下单后,可能会取消。
|
|
||||||
|
|
||||||
// ========== 使用情况 END ==========
|
|
||||||
|
|
||||||
}
|
|
@ -1,336 +0,0 @@
|
|||||||
package cn.iocoder.mall.promotionservice.manager.price;
|
|
||||||
|
|
||||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
|
||||||
import cn.iocoder.common.framework.util.CollectionUtils;
|
|
||||||
import cn.iocoder.common.framework.vo.CommonResult;
|
|
||||||
import cn.iocoder.mall.productservice.rpc.sku.ProductSkuFeign;
|
|
||||||
import cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuListQueryReqDTO;
|
|
||||||
import cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuRespDTO;
|
|
||||||
import cn.iocoder.mall.productservice.rpc.spu.ProductSpuFeign;
|
|
||||||
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuRespDTO;
|
|
||||||
import cn.iocoder.mall.promotion.api.enums.MeetTypeEnum;
|
|
||||||
import cn.iocoder.mall.promotion.api.enums.PreferentialTypeEnum;
|
|
||||||
import cn.iocoder.mall.promotion.api.enums.RangeTypeEnum;
|
|
||||||
import cn.iocoder.mall.promotion.api.enums.activity.PromotionActivityStatusEnum;
|
|
||||||
import cn.iocoder.mall.promotion.api.enums.activity.PromotionActivityTypeEnum;
|
|
||||||
import cn.iocoder.mall.promotion.api.rpc.activity.dto.PromotionActivityRespDTO;
|
|
||||||
import cn.iocoder.mall.promotion.api.rpc.coupon.dto.card.CouponCardRespDTO;
|
|
||||||
import cn.iocoder.mall.promotion.api.rpc.coupon.dto.template.CouponTemplateRespDTO;
|
|
||||||
import cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcReqDTO;
|
|
||||||
import cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcRespDTO;
|
|
||||||
import cn.iocoder.mall.promotionservice.service.activity.PromotionActivityService;
|
|
||||||
import cn.iocoder.mall.promotionservice.service.coupon.CouponCardService;
|
|
||||||
import cn.iocoder.mall.promotionservice.service.coupon.CouponTemplateService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static cn.iocoder.mall.promotion.api.enums.PromotionErrorCodeConstants.*;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@Validated
|
|
||||||
public class PriceManager {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ProductSkuFeign productSkuFeign;
|
|
||||||
@Autowired
|
|
||||||
private ProductSpuFeign productSpuFeign;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PromotionActivityService promotionActivityService;
|
|
||||||
@Autowired
|
|
||||||
private CouponCardService couponCardService;
|
|
||||||
@Autowired
|
|
||||||
private CouponTemplateService couponTemplateService;
|
|
||||||
|
|
||||||
public PriceProductCalcRespDTO calcProductPrice(PriceProductCalcReqDTO calcReqDTO) {
|
|
||||||
// 拼装结果(主要是计算价格)
|
|
||||||
PriceProductCalcRespDTO calcRespDTO = new PriceProductCalcRespDTO();
|
|
||||||
|
|
||||||
List<PromotionActivityRespDTO> activityRespDTOs = promotionActivityService.listPromotionActivitiesBySpuIds(
|
|
||||||
CollectionUtils.convertSet(listProductSkusResult.getData(), ProductSkuRespDTO::getSpuId),
|
|
||||||
Collections.singleton(PromotionActivityStatusEnum.RUN.getValue()));
|
|
||||||
// 2. 计算【限时折扣】促销
|
|
||||||
this.modifyPriceByTimeLimitDiscount(calcItemRespDTOs, activityRespDTOs);
|
|
||||||
// 3. 计算【满减送】促销
|
|
||||||
List<PriceProductCalcRespDTO.ItemGroup> itemGroups = this.groupByFullPrivilege(calcItemRespDTOs, activityRespDTOs);
|
|
||||||
calcRespDTO.setItemGroups(itemGroups);
|
|
||||||
// 4. 计算优惠劵 TODO 芋艿:未详细测试;
|
|
||||||
if (calcReqDTO.getCouponCardId() != null) {
|
|
||||||
Integer result = this.modifyPriceByCouponCard(calcReqDTO.getUserId(), calcReqDTO.getCouponCardId(), itemGroups);
|
|
||||||
calcRespDTO.setCouponCardDiscountTotal(result);
|
|
||||||
}
|
|
||||||
// 5. 计算最终的价格
|
|
||||||
int buyTotal = 0;
|
|
||||||
int discountTotal = 0;
|
|
||||||
int presentTotal = 0;
|
|
||||||
for (PriceProductCalcRespDTO.ItemGroup itemGroup : calcRespDTO.getItemGroups()) {
|
|
||||||
buyTotal += itemGroup.getItems().stream().mapToInt(item -> item.getSelected() ? item.getBuyTotal() : 0).sum();
|
|
||||||
discountTotal += itemGroup.getItems().stream().mapToInt(item -> item.getSelected() ? item.getDiscountTotal() : 0).sum();
|
|
||||||
presentTotal += itemGroup.getItems().stream().mapToInt(item -> item.getSelected() ? item.getPresentTotal() : 0).sum();
|
|
||||||
}
|
|
||||||
Assert.isTrue(buyTotal - discountTotal == presentTotal,
|
|
||||||
String.format("价格合计( %d - %d == %d )不正确", buyTotal, discountTotal, presentTotal));
|
|
||||||
calcRespDTO.setFee(new PriceProductCalcRespDTO.Fee(buyTotal, discountTotal, 0, presentTotal));
|
|
||||||
return calcRespDTO;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void modifyPriceByTimeLimitDiscount(List<PriceProductCalcRespDTO.Item> items, List<PromotionActivityRespDTO> activityList) {
|
|
||||||
for (PriceProductCalcRespDTO.Item item : items) {
|
|
||||||
// 获得符合条件的限时折扣
|
|
||||||
PromotionActivityRespDTO timeLimitedDiscount = activityList.stream()
|
|
||||||
.filter(activity -> PromotionActivityTypeEnum.TIME_LIMITED_DISCOUNT.getValue().equals(activity.getActivityType())
|
|
||||||
&& activity.getTimeLimitedDiscount().getItems().stream().anyMatch(item0 -> item0.getSpuId().equals(item.getSpuId())))
|
|
||||||
.findFirst().orElse(null);
|
|
||||||
if (timeLimitedDiscount == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 计算价格
|
|
||||||
Integer newBuyPrice = calcSkuPriceByTimeLimitDiscount(item, timeLimitedDiscount);
|
|
||||||
if (newBuyPrice.equals(item.getBuyPrice())) { // 未优惠
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 设置优惠
|
|
||||||
item.setActivityId(timeLimitedDiscount.getId());
|
|
||||||
// 设置价格
|
|
||||||
item.setBuyPrice(newBuyPrice);
|
|
||||||
item.setBuyTotal(newBuyPrice * item.getBuyQuantity());
|
|
||||||
item.setPresentTotal(item.getBuyTotal() - item.getDiscountTotal());
|
|
||||||
item.setPresentPrice(item.getPresentTotal() / item.getBuyQuantity());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算指定 SKU 在限时折扣下的价格
|
|
||||||
*
|
|
||||||
* @param sku SKU
|
|
||||||
* @param timeLimitedDiscount 限时折扣促销。
|
|
||||||
* 传入的该活动,要保证该 SKU 在该促销下一定有优惠。
|
|
||||||
* @return 计算后的价格
|
|
||||||
*/
|
|
||||||
private Integer calcSkuPriceByTimeLimitDiscount(PriceProductCalcRespDTO.Item sku, PromotionActivityRespDTO timeLimitedDiscount) {
|
|
||||||
if (timeLimitedDiscount == null) {
|
|
||||||
return sku.getBuyPrice();
|
|
||||||
}
|
|
||||||
// 获得对应的优惠项
|
|
||||||
PromotionActivityRespDTO.TimeLimitedDiscount.Item item = timeLimitedDiscount.getTimeLimitedDiscount().getItems().stream()
|
|
||||||
.filter(item0 -> item0.getSpuId().equals(sku.getSpuId()))
|
|
||||||
.findFirst().orElse(null);
|
|
||||||
if (item == null) {
|
|
||||||
throw new IllegalArgumentException(String.format("折扣活动(%s) 不存在商品(%s) 的优惠配置",
|
|
||||||
timeLimitedDiscount.toString(), sku.toString()));
|
|
||||||
}
|
|
||||||
// 计算价格
|
|
||||||
if (PreferentialTypeEnum.PRICE.getValue().equals(item.getPreferentialType())) { // 减价
|
|
||||||
int presentPrice = sku.getBuyPrice() - item.getPreferentialValue();
|
|
||||||
return presentPrice >= 0 ? presentPrice : sku.getBuyPrice(); // 如果计算优惠价格小于 0 ,则说明无法使用优惠。
|
|
||||||
}
|
|
||||||
if (PreferentialTypeEnum.DISCOUNT.getValue().equals(item.getPreferentialType())) { // 打折
|
|
||||||
return sku.getBuyPrice() * item.getPreferentialValue() / 100;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException(String.format("折扣活动(%s) 的优惠类型不正确", timeLimitedDiscount.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PriceProductCalcRespDTO.ItemGroup> groupByFullPrivilege(List<PriceProductCalcRespDTO.Item> items, List<PromotionActivityRespDTO> activityList) {
|
|
||||||
List<PriceProductCalcRespDTO.ItemGroup> itemGroups = new ArrayList<>();
|
|
||||||
// 获得所有满减送促销
|
|
||||||
List<PromotionActivityRespDTO> fullPrivileges = activityList.stream()
|
|
||||||
.filter(activity -> PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue().equals(activity.getActivityType()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
// 基于满减送促销,进行分组
|
|
||||||
if (!fullPrivileges.isEmpty()) {
|
|
||||||
items = new ArrayList<>(items); // 因为下面会修改数组,进行浅拷贝,避免影响传入的 items 。
|
|
||||||
for (PromotionActivityRespDTO fullPrivilege : fullPrivileges) {
|
|
||||||
// 创建 fullPrivilege 对应的分组
|
|
||||||
PriceProductCalcRespDTO.ItemGroup itemGroup = new PriceProductCalcRespDTO.ItemGroup()
|
|
||||||
.setActivityId(fullPrivilege.getId())
|
|
||||||
.setItems(new ArrayList<>());
|
|
||||||
// 筛选商品到分组中
|
|
||||||
for (Iterator<PriceProductCalcRespDTO.Item> iterator = items.iterator(); iterator.hasNext(); ) {
|
|
||||||
PriceProductCalcRespDTO.Item item = iterator.next();
|
|
||||||
if (!isSpuMatchFullPrivilege(item.getSpuId(), fullPrivilege)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
itemGroup.getItems().add(item);
|
|
||||||
iterator.remove();
|
|
||||||
}
|
|
||||||
// 如果匹配到,则添加到 itemGroups 中
|
|
||||||
if (!itemGroup.getItems().isEmpty()) {
|
|
||||||
itemGroups.add(itemGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 处理未参加活动的商品,形成一个分组
|
|
||||||
if (!items.isEmpty()) {
|
|
||||||
itemGroups.add(new PriceProductCalcRespDTO.ItemGroup().setItems(items));
|
|
||||||
}
|
|
||||||
// 计算每个分组的价格
|
|
||||||
Map<Integer, PromotionActivityRespDTO> activityMap = CollectionUtils.convertMap(activityList, PromotionActivityRespDTO::getId);
|
|
||||||
for (PriceProductCalcRespDTO.ItemGroup itemGroup : itemGroups) {
|
|
||||||
itemGroup.setActivityDiscountTotal(calcSkuPriceByFullPrivilege(itemGroup, activityMap.get(itemGroup.getActivityId())));
|
|
||||||
}
|
|
||||||
// 返回结果
|
|
||||||
return itemGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSpuMatchFullPrivilege(Integer spuId, PromotionActivityRespDTO activity) {
|
|
||||||
Assert.isTrue(PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue().equals(activity.getActivityType()),
|
|
||||||
"传入的必须的促销活动必须是满减送");
|
|
||||||
PromotionActivityRespDTO.FullPrivilege fullPrivilege = activity.getFullPrivilege();
|
|
||||||
if (RangeTypeEnum.ALL.getValue().equals(fullPrivilege.getRangeType())) {
|
|
||||||
return true;
|
|
||||||
} else if (RangeTypeEnum.PRODUCT_INCLUDE_PART.getValue().equals(fullPrivilege.getRangeType())) {
|
|
||||||
return fullPrivilege.getRangeValues().contains(spuId);
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException(String.format("促销活动(%s) 可用范围的类型是不正确", activity.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer calcSkuPriceByFullPrivilege(PriceProductCalcRespDTO.ItemGroup itemGroup, PromotionActivityRespDTO activity) {
|
|
||||||
if (itemGroup.getActivityId() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Assert.isTrue(PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue().equals(activity.getActivityType()),
|
|
||||||
"传入的必须的满减送活动必须是满减送");
|
|
||||||
// 获得优惠信息
|
|
||||||
List<PriceProductCalcRespDTO.Item> items = itemGroup.getItems().stream().filter(PriceProductCalcRespDTO.Item::getSelected)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
Integer itemCnt = items.stream().mapToInt(PriceProductCalcRespDTO.Item::getBuyQuantity).sum();
|
|
||||||
Integer originalTotal = items.stream().mapToInt(PriceProductCalcRespDTO.Item::getPresentTotal).sum();
|
|
||||||
List<PromotionActivityRespDTO.FullPrivilege.Privilege> privileges = activity.getFullPrivilege().getPrivileges().stream()
|
|
||||||
.filter(privilege -> {
|
|
||||||
if (MeetTypeEnum.PRICE.getValue().equals(privilege.getMeetType())) {
|
|
||||||
return originalTotal >= privilege.getMeetValue();
|
|
||||||
}
|
|
||||||
if (MeetTypeEnum.QUANTITY.getValue().equals(privilege.getMeetType())) {
|
|
||||||
return itemCnt >= privilege.getMeetValue();
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException(String.format("满减送活动(%s) 的匹配(%s)不正确",
|
|
||||||
activity.toString(), privilege.toString()));
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
// 获得不到优惠信息,返回原始价格
|
|
||||||
if (privileges.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 获得到优惠信息,进行价格计算
|
|
||||||
PromotionActivityRespDTO.FullPrivilege.Privilege privilege = privileges.get(privileges.size() - 1);
|
|
||||||
Integer presentTotal;
|
|
||||||
if (PreferentialTypeEnum.PRICE.getValue().equals(privilege.getPreferentialType())) { // 减价
|
|
||||||
// 计算循环次数。这样,后续优惠的金额就是相乘了
|
|
||||||
Integer cycleCount = 1;
|
|
||||||
if (activity.getFullPrivilege().getCycled()) {
|
|
||||||
if (MeetTypeEnum.PRICE.getValue().equals(privilege.getMeetType())) {
|
|
||||||
cycleCount = originalTotal / privilege.getMeetValue();
|
|
||||||
} else if (MeetTypeEnum.QUANTITY.getValue().equals(privilege.getMeetType())) {
|
|
||||||
cycleCount = itemCnt / privilege.getMeetValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
presentTotal = originalTotal - cycleCount * privilege.getMeetValue();
|
|
||||||
if (presentTotal < 0) { // 如果计算优惠价格小于 0 ,则说明无法使用优惠。
|
|
||||||
presentTotal = originalTotal;
|
|
||||||
}
|
|
||||||
} else if (PreferentialTypeEnum.DISCOUNT.getValue().equals(privilege.getPreferentialType())) { // 打折
|
|
||||||
presentTotal = originalTotal * privilege.getPreferentialValue() / 100;
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(String.format("满减送促销(%s) 的优惠类型不正确", activity.toString()));
|
|
||||||
}
|
|
||||||
int discountTotal = originalTotal - presentTotal;
|
|
||||||
if (discountTotal == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 按比例,拆分 presentTotal
|
|
||||||
splitDiscountPriceToItems(items, discountTotal, presentTotal);
|
|
||||||
// 返回优惠金额
|
|
||||||
return originalTotal - presentTotal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer modifyPriceByCouponCard(Integer userId, Integer couponCardId, List<PriceProductCalcRespDTO.ItemGroup> itemGroups) {
|
|
||||||
Assert.isTrue(couponCardId != null, "优惠劵编号不能为空");
|
|
||||||
// 查询优惠劵
|
|
||||||
CouponCardRespDTO couponCard = couponCardService.getCouponCard(userId, couponCardId);
|
|
||||||
if (couponCard == null) {
|
|
||||||
throw ServiceExceptionUtil.exception(COUPON_CARD_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
CouponTemplateRespDTO couponTemplate = couponTemplateService.getCouponTemplate(couponCardId);
|
|
||||||
if (couponTemplate == null) {
|
|
||||||
throw ServiceExceptionUtil.exception(COUPON_TEMPLATE_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
// 获得匹配的商品
|
|
||||||
List<PriceProductCalcRespDTO.Item> items = new ArrayList<>();
|
|
||||||
if (RangeTypeEnum.ALL.getValue().equals(couponTemplate.getRangeType())) {
|
|
||||||
itemGroups.forEach(itemGroup -> items.addAll(itemGroup.getItems()));
|
|
||||||
} else if (RangeTypeEnum.PRODUCT_INCLUDE_PART.getValue().equals(couponTemplate.getRangeType())) {
|
|
||||||
itemGroups.forEach(itemGroup -> items.forEach(item -> {
|
|
||||||
if (couponTemplate.getRangeValues().contains(item.getSpuId())) {
|
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else if (RangeTypeEnum.PRODUCT_EXCLUDE_PART.getValue().equals(couponTemplate.getRangeType())) {
|
|
||||||
itemGroups.forEach(itemGroup -> items.forEach(item -> {
|
|
||||||
if (!couponTemplate.getRangeValues().contains(item.getSpuId())) {
|
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else if (RangeTypeEnum.CATEGORY_INCLUDE_PART.getValue().equals(couponTemplate.getRangeType())) {
|
|
||||||
itemGroups.forEach(itemGroup -> items.forEach(item -> {
|
|
||||||
if (couponTemplate.getRangeValues().contains(item.getCid())) {
|
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else if (RangeTypeEnum.CATEGORY_EXCLUDE_PART.getValue().equals(couponTemplate.getRangeType())) {
|
|
||||||
itemGroups.forEach(itemGroup -> items.forEach(item -> {
|
|
||||||
if (!couponTemplate.getRangeValues().contains(item.getCid())) {
|
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
// 判断是否符合条件
|
|
||||||
int originalTotal = items.stream().mapToInt(PriceProductCalcRespDTO.Item::getPresentTotal).sum(); // 此处,指的是以优惠劵视角的原价
|
|
||||||
if (originalTotal == 0 || originalTotal < couponCard.getPriceAvailable()) {
|
|
||||||
throw ServiceExceptionUtil.exception(COUPON_CARD_NOT_MATCH); // TODO 芋艿,这种情况,会出现错误码的提示,无法格式化出来。另外,这块的最佳实践,找人讨论下。
|
|
||||||
}
|
|
||||||
// 计算价格
|
|
||||||
// 获得到优惠信息,进行价格计算
|
|
||||||
int presentTotal;
|
|
||||||
if (PreferentialTypeEnum.PRICE.getValue().equals(couponCard.getPreferentialType())) { // 减价
|
|
||||||
// 计算循环次数。这样,后续优惠的金额就是相乘了
|
|
||||||
presentTotal = originalTotal - couponCard.getPriceOff();
|
|
||||||
Assert.isTrue(presentTotal > 0, "计算后,价格为负数:" + presentTotal);
|
|
||||||
} else if (PreferentialTypeEnum.DISCOUNT.getValue().equals(couponCard.getPreferentialType())) { // 打折
|
|
||||||
presentTotal = originalTotal * couponCard.getPercentOff() / 100;
|
|
||||||
if (couponCard.getDiscountPriceLimit() != null // 空,代表不限制优惠上限
|
|
||||||
&& originalTotal - presentTotal > couponCard.getDiscountPriceLimit()) {
|
|
||||||
presentTotal = originalTotal - couponCard.getDiscountPriceLimit();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(String.format("优惠劵(%s) 的优惠类型不正确", couponCard.toString()));
|
|
||||||
}
|
|
||||||
int discountTotal = originalTotal - presentTotal;
|
|
||||||
Assert.isTrue(discountTotal > 0, "计算后,不产生优惠:" + discountTotal);
|
|
||||||
// 按比例,拆分 presentTotal
|
|
||||||
splitDiscountPriceToItems(items, discountTotal, presentTotal);
|
|
||||||
// 返回优惠金额
|
|
||||||
return originalTotal - presentTotal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void splitDiscountPriceToItems(List<PriceProductCalcRespDTO.Item> items, Integer discountTotal, Integer presentTotal) {
|
|
||||||
for (int i = 0; i < items.size(); i++) {
|
|
||||||
PriceProductCalcRespDTO.Item item = items.get(i);
|
|
||||||
Integer discountPart;
|
|
||||||
if (i < items.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
|
|
||||||
discountPart = (int) (discountTotal * (1.0D * item.getPresentTotal() / presentTotal));
|
|
||||||
discountTotal -= discountPart;
|
|
||||||
} else {
|
|
||||||
discountPart = discountTotal;
|
|
||||||
}
|
|
||||||
Assert.isTrue(discountPart > 0, "优惠金额必须大于 0");
|
|
||||||
item.setDiscountTotal(item.getDiscountTotal() + discountPart);
|
|
||||||
item.setPresentTotal(item.getBuyTotal() - item.getDiscountTotal());
|
|
||||||
item.setPresentPrice(item.getPresentTotal() / item.getBuyQuantity());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
package cn.iocoder.mall.promotionservice.service.banner;
|
|
||||||
|
|
||||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
|
||||||
import cn.iocoder.common.framework.vo.PageResult;
|
|
||||||
import cn.iocoder.mall.promotion.api.rpc.banner.dto.*;
|
|
||||||
import cn.iocoder.mall.promotionservice.convert.banner.BannerConvert;
|
|
||||||
import cn.iocoder.mall.promotionservice.dal.mysql.dataobject.banner.BannerDO;
|
|
||||||
import cn.iocoder.mall.promotionservice.dal.mysql.mapper.banner.BannerMapper;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
|
||||||
|
|
||||||
import javax.validation.Valid;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static cn.iocoder.mall.promotion.api.enums.PromotionErrorCodeConstants.BANNER_NOT_EXISTS;
|
|
||||||
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@Validated
|
|
||||||
public class BannerService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private BannerMapper bannerMapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得 Banner 列表
|
|
||||||
*
|
|
||||||
* @param listReqDTO Banner 列表查询
|
|
||||||
* @return Banner 列表
|
|
||||||
*/
|
|
||||||
public List<BannerRespDTO> listBanners(BannerListReqDTO listReqDTO) {
|
|
||||||
List<BannerDO> banners = bannerMapper.selectList(listReqDTO);
|
|
||||||
return BannerConvert.INSTANCE.convertList(banners);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得 Banner 分页
|
|
||||||
*
|
|
||||||
* @param bannerPageDTO Banner 分页查询
|
|
||||||
* @return Banner 分页结果
|
|
||||||
*/
|
|
||||||
public PageResult<BannerRespDTO> pageBanner(BannerPageReqDTO bannerPageDTO) {
|
|
||||||
IPage<BannerDO> bannerPage = bannerMapper.selectPage(bannerPageDTO);
|
|
||||||
return BannerConvert.INSTANCE.convertPage(bannerPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建 Banner
|
|
||||||
*
|
|
||||||
* @param createReqDTO 创建 Banner 信息
|
|
||||||
* @return banner
|
|
||||||
*/
|
|
||||||
public Integer createBanner(@Valid BannerCreateReqDTO createReqDTO) {
|
|
||||||
// 插入到数据库
|
|
||||||
BannerDO bannerDO = BannerConvert.INSTANCE.convert(createReqDTO);
|
|
||||||
bannerMapper.insert(bannerDO);
|
|
||||||
// 返回
|
|
||||||
return bannerDO.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 Banner
|
|
||||||
*
|
|
||||||
* @param updateReqDTO 更新 Banner 信息
|
|
||||||
*/
|
|
||||||
public void updateBanner(@Valid BannerUpdateReqDTO updateReqDTO) {
|
|
||||||
// 校验更新的 Banner 是否存在
|
|
||||||
if (bannerMapper.selectById(updateReqDTO.getId()) == null) {
|
|
||||||
throw ServiceExceptionUtil.exception(BANNER_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
// 更新到数据库
|
|
||||||
BannerDO updateObject = BannerConvert.INSTANCE.convert(updateReqDTO);
|
|
||||||
bannerMapper.updateById(updateObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除 Banner
|
|
||||||
*
|
|
||||||
* @param bannerId Banner 编号
|
|
||||||
*/
|
|
||||||
public void deleteBanner(Integer bannerId) {
|
|
||||||
// 校验 Banner 存在
|
|
||||||
if (bannerMapper.selectById(bannerId) == null) {
|
|
||||||
throw ServiceExceptionUtil.exception(BANNER_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
// 更新到数据库
|
|
||||||
bannerMapper.deleteById(bannerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user