Price 价格服务的编写

This commit is contained in:
YunaiV
2020-08-14 19:09:17 +08:00
parent ed71f5e9c8
commit 5122b68aca
32 changed files with 858 additions and 428 deletions

View File

@@ -1,4 +1,4 @@
package cn.iocoder.mall.promotionservice.service.coupon.bo;
package cn.iocoder.mall.promotion.api.rpc.coupon.dto.card;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -7,11 +7,11 @@ import java.io.Serializable;
import java.util.Date;
/**
* 优惠劵 BO
* 优惠劵 Response DTO
*/
@Data
@Accessors(chain = true)
public class CouponCardBO implements Serializable {
public class CouponCardRespDTO implements Serializable {
// ========== 基本信息 BEGIN ==========
/**
@@ -100,8 +100,6 @@ public class CouponCardBO implements Serializable {
*/
private Date usedTime;
// TODO 芋艿后续要加优惠劵的使用日志因为下单后可能会取消
// ========== 使用情况 END ==========
/**

View File

@@ -1,17 +1,18 @@
package cn.iocoder.mall.promotionservice.service.coupon.bo;
package cn.iocoder.mall.promotion.api.rpc.coupon.dto.template;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 优惠劵模板 BO
*/
@Data
@Accessors(chain = true)
public class CouponTemplateBO implements Serializable {
public class CouponTemplateRespDTO implements Serializable {
// ========== 基本信息 BEGIN ==========
/**
@@ -85,7 +86,7 @@ public class CouponTemplateBO implements Serializable {
/**
* 指定商品 / 分类列表使用逗号分隔商品编号
*/
private String rangeValues;
private List<Integer> rangeValues;
/**
* 生效日期类型
*

View File

@@ -1,14 +1,14 @@
package cn.iocoder.mall.promotion.api.rpc.price;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.util.List;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcReqDTO;
import cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcRespDTO;
/**
* 价格 Rpc 接口,提供价格计算的功能
*/
public class PriceRpc {
public interface PriceRpc {
CommonResult<PriceProductCalcRespDTO> calcProductPrice(PriceProductCalcReqDTO calcReqDTO);
}

View File

@@ -11,7 +11,7 @@ import java.util.List;
* 商品价格计算 Request DTO
*/
@Data
@Accessors
@Accessors(chain = true)
public class PriceProductCalcReqDTO implements Serializable {
/**

View File

@@ -1,5 +1,7 @@
package cn.iocoder.mall.promotion.api.rpc.price.dto;
import cn.iocoder.mall.promotion.api.enums.PromotionActivityTypeEnum;
import cn.iocoder.mall.promotion.api.rpc.activity.dto.PromotionActivityRespDTO;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -48,11 +50,14 @@ public class PriceProductCalcRespDTO implements Serializable {
@Accessors(chain = true)
public static class ItemGroup {
// /**
// * 优惠活动
// */
// // TODO 芋艿,目前会有满减送】的情况,未来有新的促销方式,可能需要改成数组
// private PromotionActivityBO activity;
/**
* 优惠活动
*
* 目前会有满减送 {@link PromotionActivityTypeEnum#FULL_PRIVILEGE} 类型的活动
*
* // TODO 芋艿,目前只会有【满减送】的情况,未来有新的促销方式,可能需要改成数组
*/
private PromotionActivityRespDTO activity;
/**
* 促销减少的金额
*
@@ -77,14 +82,28 @@ public class PriceProductCalcRespDTO implements Serializable {
@Accessors(chain = true)
public static class Item {
/**
* 商品 SPU 编号
*/
private Integer spuId;
/**
* 商品 SKU 编号
*/
private Integer skuId;
/**
* 商品 Category 编号
*/
private Integer cid;
/**
* 购买数量
*/
private Integer buyQuantity;
// /**
// * 优惠活动
// */
// private PromotionActivityBO activity;
/**
* 优惠活动
*
* 目前会有限时折扣 {@link PromotionActivityTypeEnum#TIME_LIMITED_DISCOUNT} 类型的活动
*/
private PromotionActivityRespDTO activity;
/**
* 原始单价,单位:分。
*/

View File

@@ -4,8 +4,6 @@ import cn.iocoder.mall.promotion.api.rpc.coupon.dto.CouponCardDetailRespDTO;
import cn.iocoder.mall.promotion.api.rpc.coupon.dto.CouponCardReqDTO;
import cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponCardDO;
import cn.iocoder.mall.promotionservice.service.coupon.bo.CouponCardAvailableBO;
import cn.iocoder.mall.promotionservice.service.coupon.bo.CouponCardBO;
import cn.iocoder.mall.promotionservice.service.coupon.bo.CouponCardDetailBO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@@ -19,17 +17,13 @@ public interface CouponCardConvert {
// @Mappings({})
// CouponCardBO convertToBO(CouponCardDO banner);
//
@Mappings({})
List<CouponCardBO> convertToBO(List<CouponCardDO> cardList);
List<CouponCardReqDTO> convertToDTO(List<CouponCardDO> cardList);
CouponCardReqDTO convertToSingleDTO(CouponCardDO card);
@Mappings({})
CouponCardBO convert(CouponCardDO card);
@Mappings({})
CouponCardDetailRespDTO convert2(CouponCardDO card);

View File

@@ -0,0 +1,15 @@
package cn.iocoder.mall.promotionservice.convert.coupon.card;
import cn.iocoder.mall.promotion.api.rpc.coupon.dto.card.CouponCardRespDTO;
import cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponCardDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface CouponCardConvert {
CouponCardConvert INSTANCE = Mappers.getMapper(CouponCardConvert.class);
CouponCardRespDTO convert(CouponCardDO bean);
}

View File

@@ -1,12 +1,15 @@
package cn.iocoder.mall.promotionservice.convert.coupon;
package cn.iocoder.mall.promotionservice.convert.coupon.card;
import cn.iocoder.common.framework.util.StringUtils;
import cn.iocoder.mall.promotion.api.rpc.coupon.dto.*;
import cn.iocoder.mall.promotion.api.rpc.coupon.dto.template.CouponTemplateRespDTO;
import cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponTemplateDO;
import cn.iocoder.mall.promotionservice.service.coupon.bo.CouponCardTemplateAddBO;
import cn.iocoder.mall.promotionservice.service.coupon.bo.CouponCardTemplateUpdateBO;
import cn.iocoder.mall.promotionservice.service.coupon.bo.CouponTemplateBO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
import java.util.List;
@@ -18,9 +21,7 @@ public interface CouponTemplateConvert {
// @Mappings({})
// CouponTemplateBO convertToBO(CouponTemplateDO banner);
//
@Mappings({})
List<CouponTemplateBO> convertToBO(List<CouponTemplateDO> templateList);
List<CouponTemplateReqDTO> convertToDTO(List<CouponTemplateDO> templateList);
@@ -42,7 +43,12 @@ public interface CouponTemplateConvert {
@Mappings({})
CouponTemplateDO convert(CouponCardTemplateUpdateBO template);
@Mappings({})
CouponTemplateBO convert(CouponTemplateDO template);
@Mapping(source = "rangeValues", target = "rangeValues", qualifiedByName = "translateStringToIntList")
CouponTemplateRespDTO convert(CouponTemplateDO bean);
@Named("translateStringToIntList")
default List<Integer> translateStringToIntList(String str) {
return StringUtils.splitToInt(str, ",");
}
}

View File

@@ -6,22 +6,27 @@ import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.productservice.rpc.sku.ProductSkuRpc;
import cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuListQueryReqDTO;
import cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuRespDTO;
import cn.iocoder.mall.promotion.api.enums.PromotionActivityStatusEnum;
import cn.iocoder.mall.productservice.rpc.spu.ProductSpuRpc;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuRespDTO;
import cn.iocoder.mall.promotion.api.enums.*;
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.apache.dubbo.config.annotation.DubboReference;
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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.mall.promotion.api.enums.PromotionErrorCodeConstants.PRICE_PRODUCT_SKU_NOT_EXISTS;
import static cn.iocoder.mall.promotion.api.enums.PromotionErrorCodeConstants.*;
@Service
@Validated
@@ -29,9 +34,15 @@ public class PriceManager {
@DubboReference(version = "${dubbo.consumer.ProductSkuRpc.version}")
private ProductSkuRpc productSkuRpc;
@DubboReference(version = "${dubbo.consumer.ProductSpuRpc.version}")
private ProductSpuRpc productSpuRpc;
@Autowired
private PromotionActivityService promotionActivityService;
@Autowired
private CouponCardService couponCardService;
@Autowired
private CouponTemplateService couponTemplateService;
public PriceProductCalcRespDTO calcProductPrice(PriceProductCalcReqDTO calcReqDTO) {
// TODO 芋艿,补充一些表单校验。例如说,需要传入用户编号。
@@ -47,28 +58,54 @@ public class PriceManager {
// TODO 库存相关
// 查询促销活动
List<PromotionActivityRespDTO> activityRespDTOs = promotionActivityService.listPromotionActivitiesBySpuIds(
calcProductItemDTOMap.keySet(), Collections.singleton(PromotionActivityStatusEnum.RUN.getValue()));
CollectionUtils.convertSet(listProductSkusResult.getData(), ProductSkuRespDTO::getSpuId),
Collections.singleton(PromotionActivityStatusEnum.RUN.getValue()));
// 拼装结果(主要是计算价格)
PriceProductCalcRespDTO calcRespDTO = new PriceProductCalcRespDTO();
// 1. 创建初始的每一项的数组
List<PriceProductCalcRespDTO.Item> calcItemRespDTOs = initCalcOrderPriceItems(
List<PriceProductCalcRespDTO.Item> calcItemRespDTOs = this.initCalcOrderPriceItems(
listProductSkusResult.getData(), calcProductItemDTOMap);
// 2. 计算【限时折扣】促销
// modifyPriceByTimeLimitDiscount(items, activityList);
this.modifyPriceByTimeLimitDiscount(calcItemRespDTOs, activityRespDTOs);
// 3. 计算【满减送】促销
// 4. 计算优惠劵
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. 计算最终的价格
return null;
int buyTotal = 0;
int discountTotal = 0;
int presentTotal = 0;
for (PriceProductCalcRespDTO.ItemGroup itemGroup : calcRespDTO.getItemGroups()) {
buyTotal += itemGroup.getItems().stream().mapToInt(PriceProductCalcRespDTO.Item::getBuyTotal).sum();
discountTotal += itemGroup.getItems().stream().mapToInt(PriceProductCalcRespDTO.Item::getDiscountTotal).sum();
presentTotal += itemGroup.getItems().stream().mapToInt(PriceProductCalcRespDTO.Item::getPresentTotal).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 List<PriceProductCalcRespDTO.Item> initCalcOrderPriceItems(List<ProductSkuRespDTO> skus,
Map<Integer, PriceProductCalcReqDTO.Item> calcProductItemDTOMap) {
// 获得商品分类 Map
CommonResult<List<ProductSpuRespDTO>> listProductSpusResult = productSpuRpc.listProductSpus(CollectionUtils.convertSet(skus, ProductSkuRespDTO::getSpuId));
listProductSpusResult.checkError();
Map<Integer, Integer> spuIdCategoryIdMap = CollectionUtils.convertMap(listProductSpusResult.getData(), // SPU 编号与 Category 编号的映射
ProductSpuRespDTO::getId, ProductSpuRespDTO::getCid);
// 生成商品列表
List<PriceProductCalcRespDTO.Item> items = new ArrayList<>();
for (ProductSkuRespDTO sku : skus) {
PriceProductCalcRespDTO.Item item = new PriceProductCalcRespDTO.Item();
items.add(item);
// 将是否选中,购物数量,复制到 item 中
// 将基本信息,复制到 item 中
PriceProductCalcReqDTO.Item calcOrderItem = calcProductItemDTOMap.get(sku.getId());
item.setSpuId(sku.getSpuId()).setSkuId(sku.getId());
item.setCid(spuIdCategoryIdMap.get(sku.getSpuId()));
item.setBuyQuantity(calcOrderItem.getQuantity());
// 计算初始价格
item.setOriginPrice(sku.getPrice());
@@ -81,30 +118,256 @@ public class PriceManager {
return items;
}
// private void modifyPriceByTimeLimitDiscount(List<CalcOrderPriceBO.Item> items, List<PromotionActivityBO> activityList) {
// for (CalcOrderPriceBO.Item item : items) {
// // 获得符合条件的限时折扣
// PromotionActivityBO timeLimitedDiscount = activityList.stream()
// .filter(activity -> PromotionActivityTypeEnum.TIME_LIMITED_DISCOUNT.getValue().equals(activity.getActivityType())
// && activity.getTimeLimitedDiscount().getItems().stream().anyMatch(item0 -> item0.getSpuId().equals(item.getSpu().getId())))
// .findFirst().orElse(null);
// if (timeLimitedDiscount == null) {
// continue;
// }
// // 计算价格
// ProductSkuBO sku = new ProductSkuBO().setId(item.getId()).setSpuId(item.getSpu().getId()).setPrice(item.getPrice());
// Integer newPrice = calcSkuPriceByTimeLimitDiscount(sku, timeLimitedDiscount);
// if (newPrice.equals(item.getPrice())) {
// continue;
// }
// // 设置优惠
// item.setActivity(timeLimitedDiscount);
// // 设置价格
// item.setBuyPrice(newPrice);
// item.setBuyTotal(newPrice * item.getBuyQuantity());
// item.setPresentTotal(item.getBuyTotal() - item.getDiscountTotal());
// item.setPresentPrice(item.getPresentTotal() / item.getBuyQuantity());
// }
// }
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.setActivity(timeLimitedDiscount);
// 设置价格
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()
.setActivity(fullPrivilege)
.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));
}
// 计算每个分组的价格
for (PriceProductCalcRespDTO.ItemGroup itemGroup : itemGroups) {
itemGroup.setActivityDiscountTotal(calcSkuPriceByFullPrivilege(itemGroup));
}
// 返回结果
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) {
if (itemGroup.getActivity() == null) {
return null;
}
PromotionActivityRespDTO activity = itemGroup.getActivity();
Assert.isTrue(PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue().equals(activity.getActivityType()),
"传入的必须的满减送活动必须是满减送");
// 获得优惠信息
List<PriceProductCalcRespDTO.Item> items = itemGroup.getItems();
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)不正确", itemGroup.getActivity().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());
}
}
}

View File

@@ -0,0 +1 @@
package cn.iocoder.mall.promotionservice.rpc;

View File

@@ -0,0 +1,24 @@
package cn.iocoder.mall.promotionservice.rpc.price;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.promotion.api.rpc.price.PriceRpc;
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.manager.price.PriceManager;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import static cn.iocoder.common.framework.vo.CommonResult.success;
@DubboService
public class PriceRpcImpl implements PriceRpc {
@Autowired
private PriceManager priceManager;
@Override
public CommonResult<PriceProductCalcRespDTO> calcProductPrice(PriceProductCalcReqDTO calcReqDTO) {
return success(priceManager.calcProductPrice(calcReqDTO));
}
}

View File

@@ -0,0 +1,34 @@
package cn.iocoder.mall.promotionservice.service.coupon;
import cn.iocoder.mall.promotion.api.rpc.coupon.dto.card.CouponCardRespDTO;
import cn.iocoder.mall.promotionservice.convert.coupon.card.CouponCardConvert;
import cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponCardDO;
import cn.iocoder.mall.promotionservice.dal.mysql.mapper.coupon.CouponCardMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Objects;
/**
* 优惠劵 Service
*/
@Service
@Validated
public class CouponCardService {
@Autowired
private CouponCardMapper couponCardMapper;
public CouponCardRespDTO getCouponCard(Integer userId, Integer couponCardId) {
CouponCardDO card = couponCardMapper.selectById(couponCardId);
if (card == null) {
return null;
}
if (!Objects.equals(userId, card.getUserId())) {
return null;
}
return CouponCardConvert.INSTANCE.convert(card);
}
}

View File

@@ -5,7 +5,7 @@ import cn.iocoder.common.framework.util.DateUtil;
import cn.iocoder.mall.promotion.api.enums.*;
import cn.iocoder.mall.promotion.api.rpc.coupon.dto.*;
import cn.iocoder.mall.promotionservice.convert.coupon.CouponCardConvert;
import cn.iocoder.mall.promotionservice.convert.coupon.CouponTemplateConvert;
import cn.iocoder.mall.promotionservice.convert.coupon.card.CouponTemplateConvert;
import cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponCardDO;
import cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponTemplateDO;
import cn.iocoder.mall.promotionservice.dal.mysql.mapper.coupon.CouponCardMapper;
@@ -31,11 +31,6 @@ public class CouponService {
// ========== 优惠劵(码)模板 ==========
public cn.iocoder.mall.promotionservice.service.coupon.bo.CouponTemplateBO getCouponTemplate(Integer couponTemplateId) {
CouponTemplateDO template = couponTemplateMapper.selectById(couponTemplateId);
return CouponTemplateConvert.INSTANCE.convert(template);
}
public CouponTemplatePageRespDTO getCouponTemplatePage(CouponTemplatePageReqDTO couponTemplatePageDTO) {
CouponTemplatePageRespDTO couponTemplatePageBO = new CouponTemplatePageRespDTO();
// 查询分页数据
@@ -51,7 +46,7 @@ public class CouponService {
return couponTemplatePageBO;
}
public cn.iocoder.mall.promotionservice.service.coupon.bo.CouponTemplateBO addCouponCardTemplate(CouponCardTemplateAddBO couponCardTemplateAddDTO) {
public Integer addCouponCardTemplate(CouponCardTemplateAddBO couponCardTemplateAddDTO) {
// 校验生效日期相关
checkCouponTemplateDateType(couponCardTemplateAddDTO.getDateType(),
couponCardTemplateAddDTO.getValidStartTime(), couponCardTemplateAddDTO.getValidEndTime(),
@@ -68,7 +63,7 @@ public class CouponService {
template.setCreateTime(new Date());
couponTemplateMapper.insert(template);
// 返回成功
return CouponTemplateConvert.INSTANCE.convert(template);
return template.getId();
}
public Boolean updateCouponCodeTemplate(CouponCodeTemplateUpdateBO couponCodeTemplateUpdateDTO) {

View File

@@ -0,0 +1,24 @@
package cn.iocoder.mall.promotionservice.service.coupon;
import cn.iocoder.mall.promotion.api.rpc.coupon.dto.template.CouponTemplateRespDTO;
import cn.iocoder.mall.promotionservice.convert.coupon.card.CouponTemplateConvert;
import cn.iocoder.mall.promotionservice.dal.mysql.mapper.coupon.CouponTemplateMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
/**
* 优惠劵模板 Service
*/
@Service
@Validated
public class CouponTemplateService {
@Autowired
private CouponTemplateMapper couponTemplateMapper;
public CouponTemplateRespDTO getCouponTemplate(Integer couponCardId) {
return CouponTemplateConvert.INSTANCE.convert(couponTemplateMapper.selectById(couponCardId));
}
}

View File

@@ -41,7 +41,7 @@ dubbo:
version: 1.0.0
ProductSkuRpc:
version: 1.0.0
ProductSpuService:
ProductSpuRpc:
version: 1.0.0
# RocketMQ 配置项

View File

@@ -0,0 +1 @@
package cn.iocoder.mall.promotionservice.manager;

View File

@@ -0,0 +1,30 @@
package cn.iocoder.mall.promotionservice.manager.price;
import cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcReqDTO;
import cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcRespDTO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Arrays;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class PriceManagerTest {
@Autowired
private PriceManager priceManager;
@Test
public void testCalcProductPrice() {
PriceProductCalcReqDTO calcReqDTO = new PriceProductCalcReqDTO();
PriceProductCalcReqDTO.Item item01 = new PriceProductCalcReqDTO.Item(33, 2); // 满足满减送的商品
PriceProductCalcReqDTO.Item item02 = new PriceProductCalcReqDTO.Item(34, 2); // 满足限时折扣的商品
calcReqDTO.setItems(Arrays.asList(item01, item02));
PriceProductCalcRespDTO calcRespDTO = priceManager.calcProductPrice(calcReqDTO);
System.out.println(calcRespDTO);
}
}