Commit 0b2943ba8b41e0734727dd43717fc39ba3f9dd96

Authored by puhui999
1 parent 07d6ab58

拼团活动:完善拼团下单金额结算

Showing 13 changed files with 154 additions and 34 deletions
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
1 1 package cn.iocoder.yudao.module.promotion.api.combination;
2 2  
3 3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
  4 +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
4 5  
5 6 import javax.validation.Valid;
6 7 import java.time.LocalDateTime;
... ... @@ -65,4 +66,17 @@ public interface CombinationRecordApi {
65 66 */
66 67 void updateRecordStatusToInProgress(Long userId, Long orderId, LocalDateTime startTime);
67 68  
  69 + /**
  70 + * 【下单前】校验是否满足拼团活动条件
  71 + *
  72 + * 如果校验失败,则抛出业务异常
  73 + *
  74 + * @param activityId 活动编号
  75 + * @param userId 用户编号
  76 + * @param skuId sku 编号
  77 + * @param count 数量
  78 + * @return 拼团信息
  79 + */
  80 + CombinationValidateJoinRespDTO validateJoinCombination(Long activityId, Long userId, Long skuId, Integer count);
  81 +
68 82 }
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordRespDTO.java renamed to yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationValidateJoinRespDTO.java
1 1 package cn.iocoder.yudao.module.promotion.api.combination.dto;
2 2  
3   -import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
4 3 import lombok.Data;
5 4  
6 5 /**
7   - * 拼团记录 Response DTO
  6 + * 校验参与拼团 Response DTO
8 7 *
9 8 * @author HUIHUI
10 9 */
11 10 @Data
12   -public class CombinationRecordRespDTO {
  11 +public class CombinationValidateJoinRespDTO {
13 12  
14 13 /**
15   - * 拼团活动编号
  14 + * 砍价活动编号
16 15 */
17 16 private Long activityId;
18 17 /**
19   - * SPU 编号
  18 + * 砍价活动名称
20 19 */
21   - private Long spuId;
22   - /**
23   - * SKU 编号
24   - */
25   - private Long skuId;
26   - /**
27   - * 用户编号
28   - */
29   - private Long userId;
30   - /**
31   - * 订单编号
32   - */
33   - private Long orderId;
  20 + private String name;
  21 +
34 22 /**
35   - * 开团状态
36   - *
37   - * 枚举 {@link CombinationRecordStatusEnum}
  23 + * 拼团金额
38 24 */
39   - private Integer status;
  25 + private Integer combinationPrice;
40 26  
41 27 }
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
... ... @@ -55,7 +55,7 @@ public interface ErrorCodeConstants {
55 55 ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_003, "秒杀活动已关闭,不能修改");
56 56 ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除");
57 57 ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_005, "秒杀活动已关闭,不能重复关闭");
58   - ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_008_006, "秒杀失败,原因秒杀库存不足");
  58 + ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_008_006, "秒杀失败,原因:秒杀库存不足");
59 59 ErrorCode SECKILL_JOIN_ACTIVITY_TIME_ERROR = new ErrorCode(1_013_008_007, "秒杀失败,原因:不在活动时间范围内");
60 60 ErrorCode SECKILL_JOIN_ACTIVITY_STATUS_CLOSED = new ErrorCode(1_013_008_008, "秒杀失败,原因:秒杀活动已关闭");
61 61 ErrorCode SECKILL_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_008_009, "秒杀失败,原因:单次限购超出");
... ... @@ -72,6 +72,8 @@ public interface ErrorCodeConstants {
72 72 ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE_NOT_UPDATE = new ErrorCode(1_013_010_002, "拼团活动已关闭不能修改");
73 73 ErrorCode COMBINATION_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_010_003, "拼团活动未关闭或未结束,不能删除");
74 74 ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE = new ErrorCode(1_013_010_004, "拼团失败,原因:拼团活动已关闭");
  75 + ErrorCode COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_010_005, "拼团失败,原因:拼团活动商品不存在");
  76 + ErrorCode COMBINATION_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_010_006, "拼团失败,原因:拼团活动商品库存不足");
75 77  
76 78 // ========== 拼团记录 1-013-011-000 ==========
77 79 ErrorCode COMBINATION_RECORD_NOT_EXISTS = new ErrorCode(1_013_011_000, "拼团不存在");
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
1 1 package cn.iocoder.yudao.module.promotion.api.combination;
2 2  
3 3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
  4 +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
4 5 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
5 6 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
6 7 import org.springframework.stereotype.Service;
... ... @@ -50,4 +51,9 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
50 51 userId, orderId, startTime);
51 52 }
52 53  
  54 + @Override
  55 + public CombinationValidateJoinRespDTO validateJoinCombination(Long activityId, Long userId, Long skuId, Integer count) {
  56 + return recordService.validateJoinCombination(activityId, userId, skuId, count);
  57 + }
  58 +
53 59 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java
... ... @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
7 7 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
8 8 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
9 9 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
10   -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
11 10 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
12 11 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityRespVO;
13 12 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
... ... @@ -108,8 +107,6 @@ public interface CombinationActivityConvert {
108 107 .setPicUrl(sku.getPicUrl());
109 108 }
110 109  
111   - List<CombinationRecordRespDTO> convert(List<CombinationRecordDO> bean);
112   -
113 110 List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list);
114 111  
115 112 default List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list, List<ProductSpuRespDTO> spuList) {
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java
... ... @@ -100,4 +100,13 @@ public interface CombinationActivityService {
100 100 */
101 101 PageResult<CombinationActivityDO> getCombinationActivityPage(PageParam pageParam);
102 102  
  103 + /**
  104 + * 获取指定活动指定 sku 编号的商品
  105 + *
  106 + * @param activityId 活动编号
  107 + * @param skuId sku 编号
  108 + * @return 活动商品信息
  109 + */
  110 + CombinationProductDO selectByActivityIdAndSkuId(Long activityId, Long skuId);
  111 +
103 112 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java
... ... @@ -216,4 +216,11 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
216 216 return combinationActivityMapper.selectPage(pageParam, CommonStatusEnum.ENABLE.getStatus());
217 217 }
218 218  
  219 + @Override
  220 + public CombinationProductDO selectByActivityIdAndSkuId(Long activityId, Long skuId) {
  221 + return combinationProductMapper.selectOne(
  222 + CombinationProductDO::getActivityId, activityId,
  223 + CombinationProductDO::getSkuId, skuId);
  224 + }
  225 +
219 226 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java
1 1 package cn.iocoder.yudao.module.promotion.service.combination;
2 2  
  3 +import cn.iocoder.yudao.framework.common.core.KeyValue;
3 4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
  5 +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
  6 +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
  7 +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
4 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
5 9  
6 10 import java.time.LocalDateTime;
... ... @@ -30,8 +34,9 @@ public interface CombinationRecordService {
30 34 * @param userId 用户编号
31 35 * @param skuId sku 编号
32 36 * @param count 数量
  37 + * @return 返回拼团活动和拼团活动商品
33 38 */
34   - void validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count);
  39 + KeyValue<CombinationActivityDO, CombinationProductDO> validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count);
35 40  
36 41 /**
37 42 * 创建拼团记录
... ... @@ -68,4 +73,17 @@ public interface CombinationRecordService {
68 73 */
69 74 List<CombinationRecordDO> getRecordListByUserIdAndActivityId(Long userId, Long activityId);
70 75  
  76 + /**
  77 + * 【下单前】校验是否满足拼团活动条件
  78 + *
  79 + * 如果校验失败,则抛出业务异常
  80 + *
  81 + * @param activityId 活动编号
  82 + * @param userId 用户编号
  83 + * @param skuId sku 编号
  84 + * @param count 数量
  85 + * @return 拼团信息
  86 + */
  87 + CombinationValidateJoinRespDTO validateJoinCombination(Long activityId, Long userId, Long skuId, Integer count);
  88 +
71 89 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.service.combination;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjectUtil;
  5 +import cn.iocoder.yudao.framework.common.core.KeyValue;
5 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 7 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
7 8 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
... ... @@ -10,8 +11,10 @@ import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
10 11 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
11 12 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
12 13 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
  14 +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
13 15 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
14 16 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
  17 +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
15 18 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
16 19 import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecordMapper;
17 20 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
... ... @@ -104,7 +107,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
104 107  
105 108 // TODO @芋艿:在详细预览下;
106 109 @Override
107   - public void validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count) {
  110 + public KeyValue<CombinationActivityDO, CombinationProductDO> validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count) {
108 111 // 1.1 校验拼团活动是否存在
109 112 CombinationActivityDO activity = combinationActivityService.validateCombinationActivityExists(activityId);
110 113 // 1.2 校验活动是否开启
... ... @@ -115,10 +118,24 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
115 118 if (count > activity.getSingleLimitCount()) {
116 119 throw exception(COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED);
117 120 }
  121 + // 2.1、校验活动商品是否存在
  122 + CombinationProductDO product = combinationActivityService.selectByActivityIdAndSkuId(activityId, skuId);
  123 + if (product == null) {
  124 + throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
  125 + }
  126 + // 2.2、校验 sku 是否存在
  127 + ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
  128 + if (sku == null) {
  129 + throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
  130 + }
  131 + // 2.3、 校验库存是否充足
  132 + if (count > sku.getStock()) {
  133 + throw exception(COMBINATION_ACTIVITY_UPDATE_STOCK_FAIL);
  134 + }
118 135 // 3、校验是否有拼团记录
119 136 List<CombinationRecordDO> recordList = getRecordListByUserIdAndActivityId(userId, activityId);
120 137 if (CollUtil.isEmpty(recordList)) {
121   - return;
  138 + return new KeyValue<>(activity, product);
122 139 }
123 140 // 4、校验是否超出总限购数量
124 141 Integer sumValue = getSumValue(convertList(recordList, CombinationRecordDO::getCount,
... ... @@ -129,7 +146,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
129 146 // 5、校验拼团记录是否存在未支付的订单(如果存在未支付的订单则不允许发起新的拼团)
130 147 CombinationRecordDO record = findFirst(recordList, item -> ObjectUtil.equals(item.getStatus(), null));
131 148 if (record == null) {
132   - return;
  149 + return new KeyValue<>(activity, product);
133 150 }
134 151 // 5.1、查询关联的订单是否已经支付
135 152 // 当前 activityId 已经有未支付的订单,不允许在发起新的;要么支付,要么去掉先;
... ... @@ -138,13 +155,14 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
138 155 if (ObjectUtil.equal(orderStatus, TradeOrderStatusEnum.UNPAID.getStatus())) {
139 156 throw exception(COMBINATION_RECORD_FAILED_ORDER_STATUS_UNPAID);
140 157 }
  158 +
  159 + return new KeyValue<>(activity, product);
141 160 }
142 161  
143 162 // TODO 芋艿:在详细 review 下;
144 163 @Override
145 164 @Transactional(rollbackFor = Exception.class)
146 165 public void createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
147   -
148 166 // 1.1、 校验拼团活动
149 167 CombinationActivityDO activity = combinationActivityService.validateCombinationActivityExists(reqDTO.getActivityId());
150 168 // 1.2 校验是否超出单次限购数量
... ... @@ -205,6 +223,15 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
205 223 return recordMapper.selectListByUserIdAndActivityId(userId, activityId);
206 224 }
207 225  
  226 + @Override
  227 + public CombinationValidateJoinRespDTO validateJoinCombination(Long activityId, Long userId, Long skuId, Integer count) {
  228 + KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(activityId, userId, skuId, count);
  229 + return new CombinationValidateJoinRespDTO()
  230 + .setActivityId(keyValue.getKey().getId())
  231 + .setName(keyValue.getKey().getName())
  232 + .setCombinationPrice(keyValue.getValue().getCombinationPrice());
  233 + }
  234 +
208 235 /**
209 236 * APP 端获取开团记录
210 237 *
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
... ... @@ -271,7 +271,7 @@ public interface TradeOrderConvert {
271 271 @Mapping(target = "count", source = "item.count"),
272 272 @Mapping(target = "orderId", source = "order.id"),
273 273 @Mapping(target = "userId", source = "order.userId"),
274   - @Mapping(target = "headId", source = "order.combinationRecordHeadId"),
  274 + @Mapping(target = "headId", source = "order.combinationHeadId"),
275 275 @Mapping(target = "combinationPrice", source = "item.payPrice"),
276 276 })
277 277 CombinationRecordCreateReqDTO convert(TradeOrderDO order, TradeOrderItemDO item);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
... ... @@ -312,7 +312,7 @@ public class TradeOrderDO extends BaseDO {
312 312 private Long bargainRecordId;
313 313  
314 314 /**
315   - * 拼团活动编号 TODO puhui999:价格计算后设置
  315 + * 拼团活动编号
316 316 *
317 317 * 关联 CombinationActivityDO 的 id 字段
318 318 */
... ... @@ -322,6 +322,6 @@ public class TradeOrderDO extends BaseDO {
322 322 *
323 323 * 关联 CombinationRecordDO 的 id 字段
324 324 */
325   - private Long combinationRecordHeadId;
  325 + private Long combinationHeadId;
326 326  
327 327 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCombinationActivityPriceCalculator.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.price.calculator;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
  6 +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
  7 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
  8 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
  9 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
  10 +import org.springframework.core.annotation.Order;
  11 +import org.springframework.stereotype.Component;
  12 +
  13 +import javax.annotation.Resource;
  14 +
  15 +/**
  16 + * 拼团活动的 {@link TradePriceCalculator} 实现类
  17 + *
  18 + * @author HUIHUI
  19 + */
  20 +@Component
  21 +@Order(TradePriceCalculator.ORDER_COMBINATION_ACTIVITY)
  22 +public class TradeCombinationActivityPriceCalculator implements TradePriceCalculator {
  23 +
  24 + @Resource
  25 + private CombinationRecordApi combinationRecordApi;
  26 +
  27 + @Override
  28 + public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  29 + // 1. 判断订单类型和是否具有拼团活动编号
  30 + if (param.getCombinationActivityId() == null) {
  31 + return;
  32 + }
  33 +
  34 + Assert.isTrue(param.getItems().size() == 1, "拼团时,只允许选择一个商品");
  35 + // 2. 校验是否可以参与拼团
  36 + TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0);
  37 + CombinationValidateJoinRespDTO combinationActivity = combinationRecordApi.validateJoinCombination(
  38 + param.getCombinationActivityId(), param.getUserId(),
  39 + orderItem.getSkuId(), orderItem.getCount());
  40 +
  41 + // 3.1 记录优惠明细
  42 + Integer discountPrice = orderItem.getPayPrice() - combinationActivity.getCombinationPrice() * orderItem.getCount();
  43 + TradePriceCalculatorHelper.addPromotion(result, orderItem,
  44 + param.getCombinationActivityId(), combinationActivity.getName(), PromotionTypeEnum.COMBINATION_ACTIVITY.getType(),
  45 + StrUtil.format("拼团活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)),
  46 + discountPrice);
  47 + // 3.2 更新 SKU 优惠金额
  48 + orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice);
  49 + TradePriceCalculatorHelper.recountPayPrice(orderItem);
  50 + TradePriceCalculatorHelper.recountAllPrice(result);
  51 + }
  52 +
  53 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculator.java
... ... @@ -17,6 +17,7 @@ public interface TradePriceCalculator {
17 17  
18 18 int ORDER_SECKILL_ACTIVITY = 8;
19 19 int ORDER_BARGAIN_ACTIVITY = 8;
  20 + int ORDER_COMBINATION_ACTIVITY = 8;
20 21  
21 22 int ORDER_DISCOUNT_ACTIVITY = 10;
22 23 int ORDER_REWARD_ACTIVITY = 20;
... ...