Price 价格服务的编写
parent
ed71f5e9c8
commit
5122b68aca
|
@ -38,10 +38,7 @@ public class UsersCartController {
|
|||
// }
|
||||
|
||||
//
|
||||
// @GetMapping("/list")
|
||||
// public CommonResult<UsersCartDetailVO> list() { // TODO 芋艿,先暂用这个 VO 。等促销活动出来后,做调整
|
||||
// return getCartDetail();
|
||||
// }
|
||||
|
||||
//
|
||||
// private CommonResult<UsersCartDetailVO> getCartDetail() {
|
||||
// // 获得购物车中选中的
|
||||
|
|
|
@ -1,351 +1,54 @@
|
|||
package cn.iocoder.mall.order.biz.service;
|
||||
|
||||
import cn.iocoder.common.framework.enums.CommonStatusEnum;
|
||||
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.mall.order.api.CartService;
|
||||
import cn.iocoder.mall.order.api.bo.CalcOrderPriceBO;
|
||||
import cn.iocoder.mall.order.api.bo.CalcSkuPriceBO;
|
||||
import cn.iocoder.mall.order.api.bo.CartItemBO;
|
||||
import cn.iocoder.mall.order.api.constant.CartItemStatusEnum;
|
||||
import cn.iocoder.mall.order.api.constant.OrderErrorCodeEnum;
|
||||
import cn.iocoder.mall.order.api.dto.CalcOrderPriceDTO;
|
||||
import cn.iocoder.mall.order.biz.convert.CartConvert;
|
||||
import cn.iocoder.mall.order.biz.dao.CartMapper;
|
||||
import cn.iocoder.mall.order.biz.dataobject.CartItemDO;
|
||||
import cn.iocoder.mall.product.api.ProductSpuService;
|
||||
import cn.iocoder.mall.product.api.bo.ProductSkuBO;
|
||||
import cn.iocoder.mall.product.api.bo.ProductSkuDetailBO;
|
||||
import cn.iocoder.mall.promotion.api.CouponService;
|
||||
import cn.iocoder.mall.promotion.api.PromotionActivityService;
|
||||
import cn.iocoder.mall.promotion.api.bo.CouponCardDetailBO;
|
||||
import cn.iocoder.mall.promotion.api.bo.PromotionActivityBO;
|
||||
import cn.iocoder.mall.promotion.api.enums.*;
|
||||
import org.apache.dubbo.config.annotation.Reference;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 购物车服务 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
@org.apache.dubbo.config.annotation.Service(validation = "true", version = "${dubbo.provider.CartService.version}")
|
||||
public class CartServiceImpl implements CartService {
|
||||
|
||||
@Reference(validation = "true", version = "${dubbo.consumer.PromotionActivityService.version}")
|
||||
private ProductSpuService productSpuService;
|
||||
@Reference(validation = "true", version = "${dubbo.consumer.PromotionActivityService.version}")
|
||||
private PromotionActivityService promotionActivityService;
|
||||
@Reference(validation = "true", version = "${dubbo.consumer.CouponService.version}")
|
||||
private CouponService couponService;
|
||||
|
||||
@Autowired
|
||||
private CartMapper cartMapper;
|
||||
|
||||
@Override
|
||||
public CalcOrderPriceBO calcOrderPrice(CalcOrderPriceDTO calcOrderPriceDTO) {
|
||||
// 2. 计算【限时折扣】促销
|
||||
|
||||
// 3. 计算【满减送】促销
|
||||
List<CalcOrderPriceBO.ItemGroup> itemGroups = groupByFullPrivilege(items, activityList);
|
||||
calcOrderPriceBO.setItemGroups(itemGroups);
|
||||
// 4. 计算优惠劵
|
||||
if (calcOrderPriceDTO.getCouponCardId() != null) {
|
||||
Integer result = modifyPriceByCouponCard(calcOrderPriceDTO.getUserId(), calcOrderPriceDTO.getCouponCardId(), itemGroups);
|
||||
calcOrderPriceBO.setCouponCardDiscountTotal(result);
|
||||
}
|
||||
// 5. 计算最终的价格
|
||||
int buyTotal = 0;
|
||||
int discountTotal = 0;
|
||||
int presentTotal = 0;
|
||||
for (CalcOrderPriceBO.ItemGroup itemGroup : calcOrderPriceBO.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));
|
||||
calcOrderPriceBO.setFee(new CalcOrderPriceBO.Fee(buyTotal, discountTotal, 0, presentTotal));
|
||||
// 返回
|
||||
return calcOrderPriceBO;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("Duplicates")
|
||||
public CalcSkuPriceBO calcSkuPrice(Integer skuId) {
|
||||
// 查询 SKU 是否合法
|
||||
ProductSkuBO sku = productSpuService.getProductSku(skuId);
|
||||
if (sku == null
|
||||
|| CommonStatusEnum.DISABLE.getValue().equals(sku.getStatus())) { // sku 被禁用
|
||||
throw ServiceExceptionUtil.exception(OrderErrorCodeEnum.CARD_ITEM_SKU_NOT_FOUND.getCode());
|
||||
}
|
||||
// 查询促销活动
|
||||
List<PromotionActivityBO> activityList = promotionActivityService.getPromotionActivityListBySpuId(sku.getSpuId(),
|
||||
Arrays.asList(PromotionActivityStatusEnum.WAIT.getValue(), PromotionActivityStatusEnum.RUN.getValue()));
|
||||
if (activityList.isEmpty()) { // 如果无促销活动,则直接返回默认结果即可
|
||||
return new CalcSkuPriceBO().setOriginalPrice(sku.getPrice()).setBuyPrice(sku.getPrice());
|
||||
}
|
||||
// 如果有促销活动,则开始做计算 TODO 芋艿,因为现在暂时只有限时折扣 + 满减送。所以写的比较简单先
|
||||
PromotionActivityBO fullPrivilege = findPromotionActivityByType(activityList, PromotionActivityTypeEnum.FULL_PRIVILEGE);
|
||||
PromotionActivityBO timeLimitedDiscount = findPromotionActivityByType(activityList, PromotionActivityTypeEnum.TIME_LIMITED_DISCOUNT);
|
||||
Integer presentPrice = calcSkuPriceByTimeLimitDiscount(sku, timeLimitedDiscount);
|
||||
// 返回结果
|
||||
return new CalcSkuPriceBO().setFullPrivilege(fullPrivilege).setTimeLimitedDiscount(timeLimitedDiscount)
|
||||
.setOriginalPrice(sku.getPrice()).setBuyPrice(presentPrice);
|
||||
}
|
||||
public class CartServiceImpl {
|
||||
|
||||
|
||||
|
||||
private List<CalcOrderPriceBO.ItemGroup> groupByFullPrivilege(List<CalcOrderPriceBO.Item> items, List<PromotionActivityBO> activityList) {
|
||||
List<CalcOrderPriceBO.ItemGroup> itemGroups = new ArrayList<>();
|
||||
// 获得所有满减送促销
|
||||
List<PromotionActivityBO> fullPrivileges = activityList.stream()
|
||||
.filter(activity -> PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue().equals(activity.getActivityType()))
|
||||
.collect(Collectors.toList());
|
||||
// 基于满减送促销,进行分组
|
||||
if (!fullPrivileges.isEmpty()) {
|
||||
items = new ArrayList<>(items); // 因为下面会修改数组,进行浅拷贝,避免影响传入的 items 。
|
||||
for (PromotionActivityBO fullPrivilege : fullPrivileges) {
|
||||
// 创建 fullPrivilege 对应的分组
|
||||
CalcOrderPriceBO.ItemGroup itemGroup = new CalcOrderPriceBO.ItemGroup()
|
||||
.setActivity(fullPrivilege)
|
||||
.setItems(new ArrayList<>());
|
||||
// 筛选商品到分组中
|
||||
for (Iterator<CalcOrderPriceBO.Item> iterator = items.iterator(); iterator.hasNext(); ) {
|
||||
CalcOrderPriceBO.Item item = iterator.next();
|
||||
if (!isSpuMatchFullPrivilege(item.getSpu().getId(), fullPrivilege)) {
|
||||
continue;
|
||||
}
|
||||
itemGroup.getItems().add(item);
|
||||
iterator.remove();
|
||||
}
|
||||
// 如果匹配到,则添加到 itemGroups 中
|
||||
if (!itemGroup.getItems().isEmpty()) {
|
||||
itemGroups.add(itemGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理未参加活动的商品,形成一个分组
|
||||
if (!items.isEmpty()) {
|
||||
itemGroups.add(new CalcOrderPriceBO.ItemGroup().setItems(items));
|
||||
}
|
||||
// 计算每个分组的价格
|
||||
for (CalcOrderPriceBO.ItemGroup itemGroup : itemGroups) {
|
||||
itemGroup.setActivityDiscountTotal(calcSkuPriceByFullPrivilege(itemGroup));
|
||||
}
|
||||
// 返回结果
|
||||
return itemGroups;
|
||||
}
|
||||
|
||||
private Integer modifyPriceByCouponCard(Integer userId, Integer couponCardId, List<CalcOrderPriceBO.ItemGroup> itemGroups) {
|
||||
Assert.isTrue(couponCardId != null, "优惠劵编号不能为空");
|
||||
// 查询优惠劵
|
||||
CouponCardDetailBO couponCard = couponService.getCouponCardDetail(userId, couponCardId);
|
||||
// 获得匹配的商品
|
||||
List<CalcOrderPriceBO.Item> items = new ArrayList<>();
|
||||
if (RangeTypeEnum.ALL.getValue().equals(couponCard.getRangeType())) {
|
||||
// totalPrice = spus.stream().mapToInt(spu -> spu.getPrice() * spu.getQuantity()).sum();
|
||||
itemGroups.forEach(itemGroup -> items.addAll(itemGroup.getItems()));
|
||||
} else if (RangeTypeEnum.PRODUCT_INCLUDE_PART.getValue().equals(couponCard.getRangeType())) {
|
||||
itemGroups.forEach(itemGroup -> items.forEach(item -> {
|
||||
if (couponCard.getRangeValues().contains(item.getSpu().getId())) {
|
||||
items.add(item);
|
||||
}
|
||||
}));
|
||||
} else if (RangeTypeEnum.PRODUCT_EXCLUDE_PART.getValue().equals(couponCard.getRangeType())) {
|
||||
itemGroups.forEach(itemGroup -> items.forEach(item -> {
|
||||
if (!couponCard.getRangeValues().contains(item.getSpu().getId())) {
|
||||
items.add(item);
|
||||
}
|
||||
}));
|
||||
} else if (RangeTypeEnum.CATEGORY_INCLUDE_PART.getValue().equals(couponCard.getRangeType())) {
|
||||
itemGroups.forEach(itemGroup -> items.forEach(item -> {
|
||||
if (couponCard.getRangeValues().contains(item.getSpu().getCid())) {
|
||||
items.add(item);
|
||||
}
|
||||
}));
|
||||
} else if (RangeTypeEnum.CATEGORY_EXCLUDE_PART.getValue().equals(couponCard.getRangeType())) {
|
||||
itemGroups.forEach(itemGroup -> items.forEach(item -> {
|
||||
if (!couponCard.getRangeValues().contains(item.getSpu().getCid())) {
|
||||
items.add(item);
|
||||
}
|
||||
}));
|
||||
}
|
||||
// 判断是否符合条件
|
||||
int originalTotal = items.stream().mapToInt(CalcOrderPriceBO.Item::getPresentTotal).sum(); // 此处,指的是以优惠劵视角的原价
|
||||
if (originalTotal == 0 || originalTotal < couponCard.getPriceAvailable()) {
|
||||
throw ServiceExceptionUtil.exception(PromotionErrorCodeConstants.COUPON_CARD_NOT_MATCH.getCode()); // 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指定 SKU 在限时折扣下的价格
|
||||
*
|
||||
* @param sku SKU
|
||||
* @param timeLimitedDiscount 限时折扣促销。
|
||||
* 传入的该活动,要保证该 SKU 在该促销下一定有优惠。
|
||||
* @return 计算后的价格
|
||||
*/
|
||||
private Integer calcSkuPriceByTimeLimitDiscount(ProductSkuBO sku, PromotionActivityBO timeLimitedDiscount) {
|
||||
if (timeLimitedDiscount == null) {
|
||||
return sku.getPrice();
|
||||
}
|
||||
// 获得对应的优惠项
|
||||
PromotionActivityBO.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.getPrice() - item.getPreferentialValue();
|
||||
return presentPrice >= 0 ? presentPrice : sku.getPrice(); // 如果计算优惠价格小于 0 ,则说明无法使用优惠。
|
||||
}
|
||||
if (PreferentialTypeEnum.DISCOUNT.getValue().equals(item.getPreferentialType())) { // 打折
|
||||
return sku.getPrice() * item.getPreferentialValue() / 100;
|
||||
}
|
||||
throw new IllegalArgumentException(String.format("折扣活动(%s) 的优惠类型不正确", timeLimitedDiscount.toString()));
|
||||
}
|
||||
|
||||
private Integer calcSkuPriceByFullPrivilege(CalcOrderPriceBO.ItemGroup itemGroup) {
|
||||
if (itemGroup.getActivity() == null) {
|
||||
return null;
|
||||
}
|
||||
PromotionActivityBO activity = itemGroup.getActivity();
|
||||
Assert.isTrue(PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue().equals(activity.getActivityType()),
|
||||
"传入的必须的满减送活动必须是满减送");
|
||||
// 获得优惠信息
|
||||
List<CalcOrderPriceBO.Item> items = itemGroup.getItems().stream().filter(CalcOrderPriceBO.Item::getSelected)
|
||||
.collect(Collectors.toList());
|
||||
Integer itemCnt = items.stream().mapToInt(CalcOrderPriceBO.Item::getBuyQuantity).sum();
|
||||
Integer originalTotal = items.stream().mapToInt(CalcOrderPriceBO.Item::getPresentTotal).sum();
|
||||
List<PromotionActivityBO.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;
|
||||
}
|
||||
// 获得到优惠信息,进行价格计算
|
||||
PromotionActivityBO.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
|
||||
// for (int i = 0; i < items.size(); i++) {
|
||||
// CalcOrderPriceBO.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());
|
||||
// @Override
|
||||
// @SuppressWarnings("Duplicates")
|
||||
// public CalcSkuPriceBO calcSkuPrice(Integer skuId) {
|
||||
// // 查询 SKU 是否合法
|
||||
// ProductSkuBO sku = productSpuService.getProductSku(skuId);
|
||||
// if (sku == null
|
||||
// || CommonStatusEnum.DISABLE.getValue().equals(sku.getStatus())) { // sku 被禁用
|
||||
// throw ServiceExceptionUtil.exception(OrderErrorCodeEnum.CARD_ITEM_SKU_NOT_FOUND.getCode());
|
||||
// }
|
||||
splitDiscountPriceToItems(items, discountTotal, presentTotal);
|
||||
// 返回优惠金额
|
||||
return originalTotal - presentTotal;
|
||||
}
|
||||
// // 查询促销活动
|
||||
// List<PromotionActivityBO> activityList = promotionActivityService.getPromotionActivityListBySpuId(sku.getSpuId(),
|
||||
// Arrays.asList(PromotionActivityStatusEnum.WAIT.getValue(), PromotionActivityStatusEnum.RUN.getValue()));
|
||||
// if (activityList.isEmpty()) { // 如果无促销活动,则直接返回默认结果即可
|
||||
// return new CalcSkuPriceBO().setOriginalPrice(sku.getPrice()).setBuyPrice(sku.getPrice());
|
||||
// }
|
||||
// // 如果有促销活动,则开始做计算 TODO 芋艿,因为现在暂时只有限时折扣 + 满减送。所以写的比较简单先
|
||||
// PromotionActivityBO fullPrivilege = findPromotionActivityByType(activityList, PromotionActivityTypeEnum.FULL_PRIVILEGE);
|
||||
// PromotionActivityBO timeLimitedDiscount = findPromotionActivityByType(activityList, PromotionActivityTypeEnum.TIME_LIMITED_DISCOUNT);
|
||||
// Integer presentPrice = calcSkuPriceByTimeLimitDiscount(sku, timeLimitedDiscount);
|
||||
// // 返回结果
|
||||
// return new CalcSkuPriceBO().setFullPrivilege(fullPrivilege).setTimeLimitedDiscount(timeLimitedDiscount)
|
||||
// .setOriginalPrice(sku.getPrice()).setBuyPrice(presentPrice);
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// private PromotionActivityBO findPromotionActivityByType(List<PromotionActivityBO> activityList, PromotionActivityTypeEnum type) {
|
||||
// return activityList.stream()
|
||||
// .filter(activity -> type.getValue().equals(activity.getActivityType()))
|
||||
// .findFirst().orElse(null);
|
||||
// }
|
||||
//
|
||||
// private List<PromotionActivityBO> findPromotionActivityListByType(List<PromotionActivityBO> activityList, PromotionActivityTypeEnum type) {
|
||||
// return activityList.stream()
|
||||
// .filter(activity -> type.getValue().equals(activity.getActivityType()))
|
||||
// .collect(Collectors.toList());
|
||||
// }
|
||||
|
||||
private void splitDiscountPriceToItems(List<CalcOrderPriceBO.Item> items, Integer discountTotal, Integer presentTotal) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
CalcOrderPriceBO.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());
|
||||
}
|
||||
}
|
||||
|
||||
private PromotionActivityBO findPromotionActivityByType(List<PromotionActivityBO> activityList, PromotionActivityTypeEnum type) {
|
||||
return activityList.stream()
|
||||
.filter(activity -> type.getValue().equals(activity.getActivityType()))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private List<PromotionActivityBO> findPromotionActivityListByType(List<PromotionActivityBO> activityList, PromotionActivityTypeEnum type) {
|
||||
return activityList.stream()
|
||||
.filter(activity -> type.getValue().equals(activity.getActivityType()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean isSpuMatchFullPrivilege(Integer spuId, PromotionActivityBO activity) {
|
||||
Assert.isTrue(PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue().equals(activity.getActivityType()),
|
||||
"传入的必须的促销活动必须是满减送");
|
||||
PromotionActivityBO.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);
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("促销活动(%s) 可用范围的类型是不正确", activity.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package cn.iocoder.mall.productservice.enums.sku;
|
||||
|
||||
import cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuRespDTO;
|
||||
|
||||
/**
|
||||
* 商品 SKU 明细的字段枚举
|
||||
*
|
||||
* @see ProductSkuRespDTO
|
||||
*/
|
||||
public enum ProductSkuDetailFieldEnum {
|
||||
|
||||
SPU("spu"),
|
||||
ATTR("attr");
|
||||
|
||||
/**
|
||||
* 字段
|
||||
*/
|
||||
private final String field;
|
||||
|
||||
ProductSkuDetailFieldEnum(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package cn.iocoder.mall.productservice.rpc.sku.dto;
|
||||
|
||||
import cn.iocoder.mall.productservice.enums.sku.ProductSkuDetailFieldEnum;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 商品 SKU 列表查询 DTO
|
||||
|
@ -26,5 +28,11 @@ public class ProductSkuListQueryReqDTO implements Serializable {
|
|||
*/
|
||||
private Integer productSpuId;
|
||||
|
||||
/**
|
||||
* 额外返回字段
|
||||
*
|
||||
* @see ProductSkuDetailFieldEnum
|
||||
*/
|
||||
private Collection<String> fields = Collections.emptySet();
|
||||
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public interface ProductSpuRpc {
|
|||
* @param productSpuIds 商品 SPU 编号列表
|
||||
* @return 商品 SPU 列表
|
||||
*/
|
||||
CommonResult<List<ProductSpuRespDTO>> listProductSpus(List<Integer> productSpuIds);
|
||||
CommonResult<List<ProductSpuRespDTO>> listProductSpus(Collection<Integer> productSpuIds);
|
||||
|
||||
/**
|
||||
* 获得商品 SPU 分页
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.mall.productservice.manager.sku;
|
||||
|
||||
import cn.iocoder.common.framework.util.CollectionUtils;
|
||||
import cn.iocoder.mall.productservice.convert.sku.ProductSkuConvert;
|
||||
import cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuListQueryReqDTO;
|
||||
import cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuRespDTO;
|
||||
|
@ -8,6 +9,7 @@ import cn.iocoder.mall.productservice.service.sku.bo.ProductSkuBO;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -37,8 +39,13 @@ public class ProductSkuManager {
|
|||
* @return 商品 SKU列表
|
||||
*/
|
||||
public List<ProductSkuRespDTO> listProductSkus(ProductSkuListQueryReqDTO queryReqDTO) {
|
||||
// 获得商品 SKU 列表
|
||||
List<ProductSkuBO> productSkuBOs = productSkuService.listProductSkus(
|
||||
ProductSkuConvert.INSTANCE.convert(queryReqDTO));
|
||||
if (CollectionUtils.isEmpty(productSkuBOs)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
//
|
||||
return ProductSkuConvert.INSTANCE.convertList03(productSkuBOs);
|
||||
}
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ public class ProductSpuManager {
|
|||
* @param productSpuIds 商品 SPU编号列表
|
||||
* @return 商品 SPU列表
|
||||
*/
|
||||
public List<ProductSpuRespDTO> listProductSpus(List<Integer> productSpuIds) {
|
||||
public List<ProductSpuRespDTO> listProductSpus(Collection<Integer> productSpuIds) {
|
||||
List<ProductSpuBO> productSpuBOs = productSpuService.listProductSpus(productSpuIds);
|
||||
return ProductSpuConvert.INSTANCE.convertList02(productSpuBOs);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public class ProductSpuRpcImpl implements ProductSpuRpc {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<List<ProductSpuRespDTO>> listProductSpus(List<Integer> productSpuIds) {
|
||||
public CommonResult<List<ProductSpuRespDTO>> listProductSpus(Collection<Integer> productSpuIds) {
|
||||
return success(productSpuManager.listProductSpus(productSpuIds));
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.mall.productservice.enums.ProductErrorCodeConstants.PRODUCT_SPU_NOT_EXISTS;
|
||||
|
@ -76,7 +77,7 @@ public class ProductSpuService {
|
|||
* @param productSpuIds 商品 SPU编号列表
|
||||
* @return 商品 SPU列表
|
||||
*/
|
||||
public List<ProductSpuBO> listProductSpus(List<Integer> productSpuIds) {
|
||||
public List<ProductSpuBO> listProductSpus(Collection<Integer> productSpuIds) {
|
||||
List<ProductSpuDO> productSpuDOs = productSpuMapper.selectBatchIds(productSpuIds);
|
||||
return ProductSpuConvert.INSTANCE.convertList(productSpuDOs);
|
||||
}
|
||||
|
|
|
@ -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 ==========
|
||||
|
||||
/**
|
|
@ -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;
|
||||
/**
|
||||
* 生效日期类型
|
||||
*
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import java.util.List;
|
|||
* 商品价格计算 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors
|
||||
@Accessors(chain = true)
|
||||
public class PriceProductCalcReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
/**
|
||||
* 原始单价,单位:分。
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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, ",");
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
package cn.iocoder.mall.promotionservice.rpc;
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -41,7 +41,7 @@ dubbo:
|
|||
version: 1.0.0
|
||||
ProductSkuRpc:
|
||||
version: 1.0.0
|
||||
ProductSpuService:
|
||||
ProductSpuRpc:
|
||||
version: 1.0.0
|
||||
|
||||
# RocketMQ 配置项
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
package cn.iocoder.mall.promotionservice.manager;
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -72,6 +72,12 @@
|
|||
<artifactId>order-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 营销服务 -->
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>promotion-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 系统服务 -->
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
|
|
|
@ -2,6 +2,7 @@ package cn.iocoder.mall.shopweb.controller.order;
|
|||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.security.user.core.context.UserSecurityContextHolder;
|
||||
import cn.iocoder.mall.shopweb.controller.order.vo.cart.CartDetailVO;
|
||||
import cn.iocoder.mall.shopweb.manager.order.cart.CartManager;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
|
@ -40,4 +41,10 @@ public class CartController {
|
|||
return success(cartManager.sumCartItemQuantity(UserSecurityContextHolder.getUserId()));
|
||||
}
|
||||
|
||||
@GetMapping("/get-detail")
|
||||
@ApiOperation("查询用户的购物车的商品列表")
|
||||
public CommonResult<CartDetailVO> getCartDetail() {
|
||||
return success(cartManager.getCartDetail());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
package cn.iocoder.mall.shopweb.controller.order.vo.cart;
|
||||
|
||||
import cn.iocoder.mall.promotion.api.rpc.activity.dto.PromotionActivityRespDTO;
|
||||
import cn.iocoder.mall.shopweb.controller.product.vo.attr.ProductAttrKeyValueRespVO;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ApiModel(value = "用户的购物车明细 VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class CartDetailVO {
|
||||
|
||||
/**
|
||||
* 商品分组数组
|
||||
*/
|
||||
private List<ItemGroup> itemGroups;
|
||||
/**
|
||||
* 费用
|
||||
*/
|
||||
private Fee fee;
|
||||
|
||||
/**
|
||||
* 商品分组
|
||||
*
|
||||
* 多个商品,参加同一个活动,从而形成分组。
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class ItemGroup {
|
||||
|
||||
/**
|
||||
* 优惠活动
|
||||
*/
|
||||
private PromotionActivityRespDTO activity; // TODO 芋艿,偷懒
|
||||
/**
|
||||
* 促销减少的金额
|
||||
*
|
||||
* 1. 若未参与促销活动,或不满足促销条件,返回 null
|
||||
* 2. 该金额,已经分摊到每个 Item 的 discountTotal ,需要注意。
|
||||
*/
|
||||
private Integer activityDiscountTotal;
|
||||
/**
|
||||
* 商品数组
|
||||
*/
|
||||
private List<Sku> items;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Sku {
|
||||
|
||||
// SKU 自带信息
|
||||
/**
|
||||
* sku 编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* SPU 信息
|
||||
*/
|
||||
private Spu spu;
|
||||
/**
|
||||
* 图片地址
|
||||
*/
|
||||
private String picURL;
|
||||
/**
|
||||
* 规格值数组
|
||||
*/
|
||||
private List<ProductAttrKeyValueRespVO> attrs; // TODO 后面改下
|
||||
/**
|
||||
* 价格,单位:分
|
||||
*/
|
||||
private Integer price;
|
||||
/**
|
||||
* 库存数量
|
||||
*/
|
||||
private Integer quantity;
|
||||
|
||||
// 非 SKU 自带信息
|
||||
|
||||
/**
|
||||
* 购买数量
|
||||
*/
|
||||
private Integer buyQuantity;
|
||||
/**
|
||||
* 是否选中
|
||||
*/
|
||||
private Boolean selected;
|
||||
/**
|
||||
* 优惠活动
|
||||
*/
|
||||
private PromotionActivityRespDTO activity; // TODO 芋艿,偷懒
|
||||
/**
|
||||
* 原始单价,单位:分。
|
||||
*/
|
||||
private Integer originPrice;
|
||||
/**
|
||||
* 购买单价,单位:分
|
||||
*/
|
||||
private Integer buyPrice;
|
||||
/**
|
||||
* 最终价格,单位:分。
|
||||
*/
|
||||
private Integer presentPrice;
|
||||
/**
|
||||
* 购买总金额,单位:分
|
||||
*
|
||||
* 用途类似 {@link #presentTotal}
|
||||
*/
|
||||
private Integer buyTotal;
|
||||
/**
|
||||
* 优惠总金额,单位:分。
|
||||
*/
|
||||
private Integer discountTotal;
|
||||
/**
|
||||
* 最终总金额,单位:分。
|
||||
*
|
||||
* 注意,presentPrice * quantity 不一定等于 presentTotal 。
|
||||
* 因为,存在无法整除的情况。
|
||||
* 举个例子,presentPrice = 8.33 ,quantity = 3 的情况,presentTotal 有可能是 24.99 ,也可能是 25 。
|
||||
* 所以,需要存储一个该字段。
|
||||
*/
|
||||
private Integer presentTotal;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Spu {
|
||||
|
||||
/**
|
||||
* SPU 编号
|
||||
*/
|
||||
private Integer id;
|
||||
|
||||
// ========== 基本信息 =========
|
||||
/**
|
||||
* SPU 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 分类编号
|
||||
*/
|
||||
private Integer cid;
|
||||
/**
|
||||
* 商品主图地址
|
||||
*
|
||||
* 数组,以逗号分隔
|
||||
*
|
||||
* 建议尺寸:800*800像素,你可以拖拽图片调整顺序,最多上传15张
|
||||
*/
|
||||
private List<String> picUrls;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用(合计)
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Fee {
|
||||
|
||||
/**
|
||||
* 购买总价
|
||||
*/
|
||||
private Integer buyTotal;
|
||||
/**
|
||||
* 优惠总价
|
||||
*
|
||||
* 注意,满多少元包邮,不算在优惠中。
|
||||
*/
|
||||
private Integer discountTotal;
|
||||
/**
|
||||
* 邮费
|
||||
*/
|
||||
private Integer postageTotal;
|
||||
/**
|
||||
* 最终价格
|
||||
*
|
||||
* 计算公式 = 总价 - 优惠总价 + 邮费
|
||||
*/
|
||||
private Integer presentTotal;
|
||||
|
||||
public Fee() {
|
||||
}
|
||||
|
||||
public Fee(Integer buyTotal, Integer discountTotal, Integer postageTotal, Integer presentTotal) {
|
||||
this.buyTotal = buyTotal;
|
||||
this.discountTotal = discountTotal;
|
||||
this.postageTotal = postageTotal;
|
||||
this.presentTotal = presentTotal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮费信息 TODO 芋艿,未完成
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Postage {
|
||||
|
||||
/**
|
||||
* 需要满足多少钱,可以包邮。单位:分
|
||||
*/
|
||||
private Integer threshold;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package cn.iocoder.mall.shopweb.convert.order;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface CartConvert {
|
||||
|
||||
CartConvert INSTANCE = Mappers.getMapper(CartConvert.class);
|
||||
|
||||
}
|
|
@ -1,11 +1,22 @@
|
|||
package cn.iocoder.mall.shopweb.manager.order.cart;
|
||||
|
||||
import cn.iocoder.common.framework.util.CollectionUtils;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.orderservice.rpc.cart.CartRpc;
|
||||
import cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemAddReqDTO;
|
||||
import cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemListReqDTO;
|
||||
import cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemRespDTO;
|
||||
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.shopweb.controller.order.vo.cart.CartDetailVO;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 购物车 Manager
|
||||
*/
|
||||
|
@ -14,6 +25,8 @@ public class CartManager {
|
|||
|
||||
@DubboReference(version = "${dubbo.consumer.ProductCategoryRpc.version}")
|
||||
private CartRpc cartRpc;
|
||||
@DubboReference(version = "${dubbo.consumer.PriceRpc.version}")
|
||||
private PriceRpc priceRpc;
|
||||
|
||||
/**
|
||||
* 添加商品到购物车
|
||||
|
@ -40,4 +53,33 @@ public class CartManager {
|
|||
return sumCartItemQuantityResult.getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户的购物车的商品列表
|
||||
*
|
||||
* @return 商品列表
|
||||
*/
|
||||
public CartDetailVO getCartDetail(Integer userId) {
|
||||
// 获得购物车中选中的
|
||||
CommonResult<List<CartItemRespDTO>> listCartItemsResult = cartRpc.listCartItems(new CartItemListReqDTO().setUserId(userId));
|
||||
listCartItemsResult.checkError();
|
||||
// 购物车为空时,构造空的 UsersOrderConfirmCreateVO 返回
|
||||
if (CollectionUtils.isEmpty(listCartItemsResult.getData())) {
|
||||
CartDetailVO result = new CartDetailVO();
|
||||
result.setItemGroups(Collections.emptyList());
|
||||
result.setFee(new CartDetailVO.Fee(0, 0, 0, 0));
|
||||
return result;
|
||||
}
|
||||
// 计算选中的商品价格
|
||||
CommonResult<PriceProductCalcRespDTO> calcProductPriceResult = priceRpc.calcProductPrice(new PriceProductCalcReqDTO().setUserId(userId)
|
||||
.setItems(listCartItemsResult.getData().stream()
|
||||
.filter(CartItemRespDTO::getSelected)
|
||||
.map(cartItem -> new PriceProductCalcReqDTO.Item(cartItem.getSkuId(), cartItem.getQuantity()))
|
||||
.collect(Collectors.toList())));
|
||||
calcProductPriceResult.checkError();
|
||||
// 拼接结果
|
||||
|
||||
// 执行数据拼装
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ dubbo:
|
|||
version: 1.0.0
|
||||
SearchProductRpc:
|
||||
version: 1.0.0
|
||||
PriceRpc:
|
||||
version: 1.0.0
|
||||
|
||||
# Swagger 配置项
|
||||
swagger:
|
||||
|
|
Loading…
Reference in New Issue