Commit 383439f9029df85733fc7391670cadd98aaa0722

Authored by owen
2 parents 4f33a0c9 a76760c4

Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

Showing 36 changed files with 612 additions and 245 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/controller/app/activity/AppActivityController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.activity;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
3 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 6 import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO;
  7 +import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
  8 +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
  9 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
  10 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
  11 +import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
  12 +import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
  13 +import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
5 14 import io.swagger.v3.oas.annotations.Operation;
6 15 import io.swagger.v3.oas.annotations.Parameter;
7 16 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -11,8 +20,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
11 20 import org.springframework.web.bind.annotation.RequestParam;
12 21 import org.springframework.web.bind.annotation.RestController;
13 22  
14   -import java.time.LocalDateTime;
15   -import java.util.*;
  23 +import javax.annotation.Resource;
  24 +import java.util.ArrayList;
  25 +import java.util.HashMap;
  26 +import java.util.List;
  27 +import java.util.Map;
16 28  
17 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
18 30  
... ... @@ -22,47 +34,66 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
22 34 @Validated
23 35 public class AppActivityController {
24 36  
25   - // TODO @puhui999:可以实现下
  37 + @Resource
  38 + private CombinationActivityService combinationActivityService;
  39 + @Resource
  40 + private SeckillActivityService seckillActivityService;
  41 + @Resource
  42 + private BargainActivityService bargainActivityService;
  43 +
26 44 @GetMapping("/list-by-spu-id")
27 45 @Operation(summary = "获得单个商品,近期参与的每个活动") // 每种活动,只返回一个
28 46 @Parameter(name = "spuId", description = "商品编号", required = true)
29 47 public CommonResult<List<AppActivityRespVO>> getActivityListBySpuId(@RequestParam("spuId") Long spuId) {
30   - // TODO 芋艿,实现
31   - List<AppActivityRespVO> randomList = new ArrayList<>();
32   - Random random = new Random();
33   - for (int i = 0; i < 3; i++) { // 生成5个随机对象
34   - AppActivityRespVO vo = new AppActivityRespVO();
35   - vo.setId(random.nextLong()); // 随机生成一个长整型 ID
36   - vo.setType(i + 1); // 随机生成一个介于0到2之间的整数,对应枚举类型的三种类型之一
37   - vo.setName(String.format("活动%d", random.nextInt(100))); // 随机生成一个类似于“活动XX”的活动名称,XX为0到99之间的随机整数
38   - vo.setStartTime(LocalDateTime.now()); // 随机生成一个在过去的一年内的开始时间(以毫秒为单位)
39   - vo.setEndTime(LocalDateTime.now()); // 随机生成一个在未来的一年内的结束时间(以毫秒为单位)
40   - randomList.add(vo);
41   - }
42   - return success(randomList);
  48 + return success(getAppActivityRespVOList(spuId));
43 49 }
44 50  
45   - // TODO @puhui999:可以实现下
46 51 @GetMapping("/list-by-spu-ids")
47 52 @Operation(summary = "获得多个商品,近期参与的每个活动") // 每种活动,只返回一个;key 为 SPU 编号
48 53 @Parameter(name = "spuIds", description = "商品编号数组", required = true)
49 54 public CommonResult<Map<Long, List<AppActivityRespVO>>> getActivityListBySpuIds(@RequestParam("spuIds") List<Long> spuIds) {
50   - // TODO 芋艿,实现
51   - List<AppActivityRespVO> randomList = new ArrayList<>();
52   - Random random = new Random();
53   - for (int i = 0; i < 5; i++) { // 生成5个随机对象
54   - AppActivityRespVO vo = new AppActivityRespVO();
55   - vo.setId(random.nextLong()); // 随机生成一个长整型 ID
56   - vo.setType(random.nextInt(3)); // 随机生成一个介于0到2之间的整数,对应枚举类型的三种类型之一
57   - vo.setName(String.format("活动%d", random.nextInt(100))); // 随机生成一个类似于“活动XX”的活动名称,XX为0到99之间的随机整数
58   - vo.setStartTime(LocalDateTime.now()); // 随机生成一个在过去的一年内的开始时间(以毫秒为单位)
59   - vo.setEndTime(LocalDateTime.now()); // 随机生成一个在未来的一年内的结束时间(以毫秒为单位)
60   - randomList.add(vo);
  55 +
  56 + if (CollUtil.isEmpty(spuIds)) {
  57 + return success(MapUtil.empty());
61 58 }
62   - Map<Long, List<AppActivityRespVO>> map = new HashMap<>();
63   - map.put(109L, randomList);
64   - map.put(2L, randomList);
  59 +
  60 + Map<Long, List<AppActivityRespVO>> map = new HashMap<>(spuIds.size());
  61 + spuIds.forEach(spuId -> {
  62 + map.put(spuId, getAppActivityRespVOList(spuId));
  63 + });
65 64 return success(map);
66 65 }
67 66  
  67 + private List<AppActivityRespVO> getAppActivityRespVOList(Long spuId) {
  68 + List<AppActivityRespVO> respList = new ArrayList<>();
  69 + CombinationActivityDO combination = combinationActivityService.getCombinationActivityBySpuId(spuId);
  70 + if (combination != null) {
  71 + respList.add(new AppActivityRespVO()
  72 + .setId(combination.getId())
  73 + .setType(PromotionTypeEnum.COMBINATION_ACTIVITY.getType())
  74 + .setName(combination.getName())
  75 + .setStartTime(combination.getStartTime())
  76 + .setEndTime(combination.getEndTime()));
  77 + }
  78 + SeckillActivityDO seckill = seckillActivityService.getSeckillActivityBySpuId(spuId);
  79 + if (seckill != null) {
  80 + respList.add(new AppActivityRespVO()
  81 + .setId(seckill.getId())
  82 + .setType(PromotionTypeEnum.SECKILL_ACTIVITY.getType())
  83 + .setName(seckill.getName())
  84 + .setStartTime(seckill.getStartTime())
  85 + .setEndTime(seckill.getEndTime()));
  86 + }
  87 + BargainActivityDO bargain = bargainActivityService.getBargainActivityBySpuId(spuId);
  88 + if (bargain != null) {
  89 + respList.add(new AppActivityRespVO()
  90 + .setId(bargain.getId())
  91 + .setType(PromotionTypeEnum.BARGAIN_ACTIVITY.getType())
  92 + .setName(bargain.getName())
  93 + .setStartTime(bargain.getStartTime())
  94 + .setEndTime(bargain.getEndTime()));
  95 + }
  96 + return respList;
  97 + }
  98 +
68 99 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/AppBargainRecordController.java
... ... @@ -92,40 +92,28 @@ public class AppBargainRecordController {
92 92 // 1. 查询砍价记录 + 砍价活动
93 93 Assert.isTrue(id != null || activityId != null, "砍价记录编号和活动编号不能同时为空");
94 94 BargainRecordDO record = id != null ? bargainRecordService.getBargainRecord(id)
95   - : bargainRecordService.getInProgressBargainRecord(getLoginUserId(), activityId);
  95 + : bargainRecordService.getLastBargainRecord(getLoginUserId(), activityId);
96 96 if (activityId == null || record != null) {
97 97 activityId = record.getActivityId();
98 98 }
99 99 // 2. 查询助力记录
100   - Integer helpAction = getHelpAction(record, activityId);
  100 + Long userId = getLoginUserId();
  101 + Integer helpAction = getHelpAction(userId, record, activityId);
101 102 // 3. 如果是自己的订单,则查询订单信息
  103 + TradeOrderRespDTO order = record != null && record.getOrderId() != null && record.getUserId().equals(getLoginUserId())
  104 + ? tradeOrderApi.getOrder(record.getOrderId()) : null;
102 105 // TODO 继续查询别的字段
103 106  
104 107 // 拼接返回
105   - return success(BargainRecordConvert.INSTANCE.convert02(record, helpAction));
106   -//
107   -// AppBargainRecordDetailRespVO detail = new AppBargainRecordDetailRespVO();
108   -// detail.setId(1L);
109   -// detail.setUserId(1L);
110   -// detail.setSpuId(1L);
111   -// detail.setSkuId(1L);
112   -// detail.setPrice(500);
113   -// detail.setActivityId(1L);
114   -// detail.setBargainPrice(150);
115   -// detail.setPrice(200);
116   -// detail.setPayPrice(180);
117   -// detail.setStatus(1);
118   -// detail.setExpireTime(LocalDateTimeUtils.addTime(Duration.ofDays(2)));
119   -// return success(detail);
  108 + return success(BargainRecordConvert.INSTANCE.convert02(record, helpAction, order));
120 109 }
121 110  
122   - private Integer getHelpAction(BargainRecordDO record, Long activityId) {
  111 + private Integer getHelpAction(Long userId, BargainRecordDO record, Long activityId) {
123 112 // 0.1 如果没有活动,无法帮砍
124 113 if (activityId == null) {
125 114 return null;
126 115 }
127 116 // 0.2 如果是自己的砍价记录,无法帮砍
128   - Long userId = getLoginUserId();
129 117 if (record != null && record.getUserId().equals(userId)) {
130 118 return null;
131 119 }
... ... @@ -141,7 +129,7 @@ public class AppBargainRecordController {
141 129 && bargainHelpService.getBargainHelpCountByActivity(activityId, userId) >= activity.getBargainCount()) {
142 130 return AppBargainRecordDetailRespVO.HELP_ACTION_FULL;
143 131 }
144   -
  132 + // 3. 允许助力
145 133 return AppBargainRecordDetailRespVO.HELP_ACTION_NONE;
146 134 }
147 135  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationActivityController.java
... ... @@ -14,6 +14,8 @@ import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivity
14 14 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
15 15 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
16 16 import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
  17 +import com.google.common.cache.CacheLoader;
  18 +import com.google.common.cache.LoadingCache;
17 19 import io.swagger.v3.oas.annotations.Operation;
18 20 import io.swagger.v3.oas.annotations.Parameter;
19 21 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -24,10 +26,13 @@ import org.springframework.web.bind.annotation.RequestParam;
24 26 import org.springframework.web.bind.annotation.RestController;
25 27  
26 28 import javax.annotation.Resource;
  29 +import java.time.Duration;
27 30 import java.util.Collections;
28 31 import java.util.List;
29 32  
  33 +import static cn.hutool.core.util.ObjectUtil.defaultIfNull;
30 34 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  35 +import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
31 36 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
32 37  
33 38 @Tag(name = "用户 APP - 拼团活动")
... ... @@ -36,24 +41,40 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
36 41 @Validated
37 42 public class AppCombinationActivityController {
38 43  
  44 + /**
  45 + * {@link AppCombinationActivityRespVO} 缓存,通过它异步刷新 {@link #getCombinationActivityList0(Integer)} 所要的首页数据
  46 + */
  47 + private final LoadingCache<Integer, List<AppCombinationActivityRespVO>> combinationActivityListCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
  48 + new CacheLoader<Integer, List<AppCombinationActivityRespVO>>() {
  49 +
  50 + @Override
  51 + public List<AppCombinationActivityRespVO> load(Integer count) {
  52 + return getCombinationActivityList0(count);
  53 + }
  54 +
  55 + });
  56 +
39 57 @Resource
40 58 private CombinationActivityService activityService;
41 59 @Resource
42 60 private ProductSpuApi spuApi;
43 61  
44   - // TODO 芋艿:增加 Spring Cache
45 62 @GetMapping("/list")
46 63 @Operation(summary = "获得拼团活动列表", description = "用于小程序首页")
47 64 @Parameter(name = "count", description = "需要展示的数量", example = "6")
48 65 public CommonResult<List<AppCombinationActivityRespVO>> getCombinationActivityList(
49 66 @RequestParam(name = "count", defaultValue = "6") Integer count) {
50   - List<CombinationActivityDO> list = activityService.getCombinationActivityListByCount(6);
  67 + return success(combinationActivityListCache.getUnchecked(count));
  68 + }
  69 +
  70 + private List<AppCombinationActivityRespVO> getCombinationActivityList0(Integer count) {
  71 + List<CombinationActivityDO> list = activityService.getCombinationActivityListByCount(defaultIfNull(count, 6));
51 72 if (CollUtil.isEmpty(list)) {
52   - return success(Collections.emptyList());
  73 + return Collections.emptyList();
53 74 }
54 75 // 拼接返回
55 76 List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(list, CombinationActivityDO::getSpuId));
56   - return success(CombinationActivityConvert.INSTANCE.convertAppList(list, spuList));
  77 + return CombinationActivityConvert.INSTANCE.convertAppList(list, spuList);
57 78 }
58 79  
59 80 @GetMapping("/page")
... ... @@ -75,7 +96,7 @@ public class AppCombinationActivityController {
75 96 // 1、获取活动
76 97 CombinationActivityDO combinationActivity = activityService.getCombinationActivity(id);
77 98 if (combinationActivity == null
78   - || ObjectUtil.equal(combinationActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
  99 + || ObjectUtil.equal(combinationActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
79 100 return success(null);
80 101 }
81 102 // 2、获取活动商品
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.combination;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
  5 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 6 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordDetailRespVO;
6 7 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordRespVO;
7 8 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordSummaryRespVO;
  9 +import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
  10 +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
  11 +import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
8 12 import io.swagger.v3.oas.annotations.Operation;
9 13 import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.Parameters;
10 15 import io.swagger.v3.oas.annotations.tags.Tag;
11 16 import org.springframework.validation.annotation.Validated;
12 17 import org.springframework.web.bind.annotation.GetMapping;
... ... @@ -14,13 +19,14 @@ import org.springframework.web.bind.annotation.RequestMapping;
14 19 import org.springframework.web.bind.annotation.RequestParam;
15 20 import org.springframework.web.bind.annotation.RestController;
16 21  
  22 +import javax.annotation.Resource;
17 23 import javax.validation.constraints.Max;
18   -import java.time.Duration;
19   -import java.time.LocalDateTime;
20   -import java.util.ArrayList;
  24 +import java.util.Collections;
21 25 import java.util.List;
22 26  
23 27 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  29 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
24 30  
25 31 @Tag(name = "用户 APP - 拼团活动")
26 32 @RestController
... ... @@ -28,81 +34,78 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
28 34 @Validated
29 35 public class AppCombinationRecordController {
30 36  
  37 + @Resource
  38 + private CombinationRecordService combinationRecordService;
  39 +
31 40 @GetMapping("/get-summary")
32 41 @Operation(summary = "获得拼团记录的概要信息", description = "用于小程序首页")
33 42 // TODO 芋艿:增加 @Cache 缓存,1 分钟过期
34 43 public CommonResult<AppCombinationRecordSummaryRespVO> getCombinationRecordSummary() {
  44 + // 获取所有拼团记录
  45 + Long count = combinationRecordService.getRecordsCount();
35 46 AppCombinationRecordSummaryRespVO summary = new AppCombinationRecordSummaryRespVO();
36   - summary.setUserCount(1024);
37   - summary.setAvatars(new ArrayList<>());
38   - summary.getAvatars().add("https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLjFK35Wvia9lJKHoXfQuHhk0qZbvpPNxrAiaEKF7aL2k4I8kuqrdTWwliamdPHeyAA7DjAg725X2GIQ/132");
39   - summary.getAvatars().add("https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTK1pXgdj5DvBMwrbe8v3tFibSWeQATEsAibt3fllD8XwJ460P2r6KS3WCQvDefuv1bVpDhNCle6CTCA/132");
40   - summary.getAvatars().add("https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTL7KRGHBE62N0awFyBesmmxiaCicf1fJ7E7UCh6zA8GWlT1QC1zT01gG4OxI7BWDESkdPZ5o7tno4hA/132");
41   - summary.getAvatars().add("https://thirdwx.qlogo.cn/mmopen/vi_32/ouwtwJycbic2JrCoZjETict0klxd1uRuicRneKk00ewMcCClxVcVHQT91Sh9MJGtwibf1fOicD1WpwSP4icJM6eQq1AA/132");
42   - summary.getAvatars().add("https://thirdwx.qlogo.cn/mmopen/vi_32/RpUrhwens58qc99OcGs993xL4M5QPOe05ekqF9Eia440kRicAlicicIdQWicHBmy2bzLgHzHguWEzHHxnIgeictL7bLA/132");
43   - summary.getAvatars().add("https://thirdwx.qlogo.cn/mmopen/vi_32/S4tfqmxc8GZGsKc1K4mnhpvtG16gtMrLnTQfDibhr7jJich9LRI5RQKZDoqEjZM3azMib5nic7F4ZXKMEgYyLO08KA/132");
44   - summary.getAvatars().add("https://static.iocoder.cn/mall/132.jpeg");
  47 + if (count == null || count == 0L) {
  48 + summary.setAvatars(Collections.emptyList());
  49 + summary.setUserCount(count);
  50 + return success(summary);
  51 + }
  52 +
  53 + summary.setUserCount(count);
  54 + // TODO 只返回最近的 7 个
  55 + int num = 7;
  56 + summary.setAvatars(convertList(combinationRecordService.getLatestRecordList(num), CombinationRecordDO::getAvatar));
45 57 return success(summary);
46 58 }
47 59  
48 60 @GetMapping("/get-head-list")
49 61 @Operation(summary = "获得最近 n 条拼团记录(团长发起的)")
50   - // TODO @芋艿:注解要补全
  62 + @Parameters({
  63 + @Parameter(name = "activityId", description = "拼团活动编号"),
  64 + @Parameter(name = "status", description = "状态"),
  65 + @Parameter(name = "count", description = "数量")
  66 + })
51 67 public CommonResult<List<AppCombinationRecordRespVO>> getHeadCombinationRecordList(
52 68 @RequestParam(value = "activityId", required = false) Long activityId,
53 69 @RequestParam("status") Integer status,
54 70 @RequestParam(value = "count", defaultValue = "20") @Max(20) Integer count) {
55   - List<AppCombinationRecordRespVO> list = new ArrayList<>();
56   - for (int i = 1; i <= count; i++) {
57   - AppCombinationRecordRespVO record = new AppCombinationRecordRespVO();
58   - record.setId((long) i);
59   - record.setNickname("用户" + i);
60   - record.setAvatar("头像" + i);
61   - record.setExpireTime(LocalDateTime.now());
62   - record.setUserSize(10);
63   - record.setUserCount(i);
64   - record.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg");
65   - record.setActivityId(1L);
66   - record.setSpuName("活动:" + i);
67   - list.add(record);
68   - }
69   - return success(list);
  71 + return success(CombinationActivityConvert.INSTANCE.convertList3(
  72 + combinationRecordService.getRecordListWithHead(activityId, status, count)));
70 73 }
71 74  
72 75 @GetMapping("/get-detail")
73 76 @Operation(summary = "获得拼团记录明细")
74 77 @Parameter(name = "id", description = "拼团记录编号", required = true, example = "1024")
75 78 public CommonResult<AppCombinationRecordDetailRespVO> getCombinationRecordDetail(@RequestParam("id") Long id) {
  79 + // 1、查询这条记录
  80 + CombinationRecordDO record = combinationRecordService.getRecordById(id);
  81 + if (record == null) {
  82 + return success(null);
  83 + }
  84 +
76 85 AppCombinationRecordDetailRespVO detail = new AppCombinationRecordDetailRespVO();
77   - // 团长
78   - AppCombinationRecordRespVO headRecord = new AppCombinationRecordRespVO();
79   - headRecord.setId(1L);
80   - headRecord.setNickname("用户" + 1);
81   - headRecord.setAvatar("头像" + 1);
82   - headRecord.setExpireTime(LocalDateTimeUtils.addTime(Duration.ofDays(1)));
83   - headRecord.setUserSize(10);
84   - headRecord.setUserCount(3);
85   - headRecord.setStatus(1);
86   - headRecord.setActivityId(10L);
87   - headRecord.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg");
88   - headRecord.setCombinationPrice(100);
89   - detail.setHeadRecord(headRecord);
90   - // 团员
91   - List<AppCombinationRecordRespVO> list = new ArrayList<>();
92   - for (int i = 1; i <= 2; i++) {
93   - AppCombinationRecordRespVO record = new AppCombinationRecordRespVO();
94   - record.setId((long) i);
95   - record.setNickname("用户" + i);
96   - record.setAvatar("头像" + i);
97   - record.setExpireTime(LocalDateTime.now());
98   - record.setUserSize(10);
99   - record.setUserCount(i);
100   - record.setStatus(1);
101   - list.add(record);
  86 + List<CombinationRecordDO> records;
  87 + // 2、判断是否为团长
  88 + if (record.getHeadId() == null) {
  89 + detail.setHeadRecord(CombinationActivityConvert.INSTANCE.convert(record));
  90 + // 2.1、查找团员拼团记录
  91 + records = combinationRecordService.getRecordListByHeadId(record.getId());
  92 + } else {
  93 + // 2.2、查找团长拼团记录
  94 + CombinationRecordDO headRecord = combinationRecordService.getRecordById(record.getHeadId());
  95 + if (headRecord == null) {
  96 + return success(null);
  97 + }
  98 +
  99 + detail.setHeadRecord(CombinationActivityConvert.INSTANCE.convert(headRecord));
  100 + // 2.3、查找团员拼团记录
  101 + records = combinationRecordService.getRecordListByHeadId(headRecord.getId());
  102 +
102 103 }
103   - detail.setMemberRecords(list);
104   - // 订单编号
105   - detail.setOrderId(100L);
  104 + detail.setMemberRecords(CombinationActivityConvert.INSTANCE.convertList3(records));
  105 +
  106 + // 3、获取当前用户参团记录订单编号
  107 + CombinationRecordDO userRecord = CollectionUtils.findFirst(records, r -> ObjectUtil.equal(r.getUserId(), getLoginUserId()));
  108 + detail.setOrderId(userRecord == null ? null : userRecord.getOrderId()); // 如果没参团,返回 null
106 109 return success(detail);
107 110 }
108 111  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/vo/record/AppCombinationRecordDetailRespVO.java
... ... @@ -16,7 +16,6 @@ public class AppCombinationRecordDetailRespVO {
16 16 private List<AppCombinationRecordRespVO> memberRecords;
17 17  
18 18 @Schema(description = "当前用户参团记录对应的订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
19   - // 如果没参团,返回 null
20 19 private Long orderId;
21 20  
22 21 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/vo/record/AppCombinationRecordSummaryRespVO.java
... ... @@ -10,9 +10,9 @@ import java.util.List;
10 10 public class AppCombinationRecordSummaryRespVO {
11 11  
12 12 @Schema(description = "拼团用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13   - private Integer userCount;
  13 + private Long userCount;
14 14  
15   - @Schema(description = "拼团用户头像列表", requiredMode = Schema.RequiredMode.REQUIRED) // 只返回最近的 7 个
  15 + @Schema(description = "拼团用户头像列表", requiredMode = Schema.RequiredMode.REQUIRED)
16 16 private List<String> avatars;
17 17  
18 18 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/bargain/BargainRecordConvert.java
... ... @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityD
13 13 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainRecordDO;
14 14 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
15 15 import org.mapstruct.Mapper;
  16 +import org.mapstruct.Mapping;
16 17 import org.mapstruct.factory.Mappers;
17 18  
18 19 import java.util.List;
... ... @@ -83,6 +84,9 @@ public interface BargainRecordConvert {
83 84 return summary;
84 85 }
85 86  
86   - AppBargainRecordDetailRespVO convert02(BargainRecordDO record, Integer helpAction);
  87 + @Mapping(source = "record.id", target = "id")
  88 + @Mapping(source = "record.userId", target = "userId")
  89 + @Mapping(source = "record.status", target = "status")
  90 + AppBargainRecordDetailRespVO convert02(BargainRecordDO record, Integer helpAction, TradeOrderRespDTO order);
87 91  
88 92 }
... ...
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;
... ... @@ -15,6 +14,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product
15 14 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductRespVO;
16 15 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.activity.AppCombinationActivityDetailRespVO;
17 16 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.activity.AppCombinationActivityRespVO;
  17 +import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordRespVO;
18 18 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
19 19 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
20 20 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
... ... @@ -108,8 +108,6 @@ public interface CombinationActivityConvert {
108 108 .setPicUrl(sku.getPicUrl());
109 109 }
110 110  
111   - List<CombinationRecordRespDTO> convert(List<CombinationRecordDO> bean);
112   -
113 111 List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list);
114 112  
115 113 default List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list, List<ProductSpuRespDTO> spuList) {
... ... @@ -144,4 +142,8 @@ public interface CombinationActivityConvert {
144 142 return convert2(combinationActivity).setProducts(convertList1(products));
145 143 }
146 144  
  145 + List<AppCombinationRecordRespVO> convertList3(List<CombinationRecordDO> records);
  146 +
  147 + AppCombinationRecordRespVO convert(CombinationRecordDO record);
  148 +
147 149 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java
... ... @@ -83,4 +83,11 @@ public interface BargainActivityMapper extends BaseMapperX&lt;BargainActivityDO&gt; {
83 83 .last("LIMIT " + count));
84 84 }
85 85  
  86 + default BargainActivityDO selectOne(Long spuId) {
  87 + return selectOne(new LambdaQueryWrapperX<BargainActivityDO>()
  88 + .eq(BargainActivityDO::getSpuId, spuId)
  89 + .orderByDesc(BargainActivityDO::getCreateTime)
  90 + , false);
  91 + }
  92 +
86 93 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainRecordMapper.java
... ... @@ -42,6 +42,14 @@ public interface BargainRecordMapper extends BaseMapperX&lt;BargainRecordDO&gt; {
42 42 .eq(BargainRecordDO::getStatus, status));
43 43 }
44 44  
  45 + default BargainRecordDO selectLastByUserIdAndActivityId(Long userId, Long activityId) {
  46 + return selectOne(new LambdaQueryWrapper<>(BargainRecordDO.class)
  47 + .eq(BargainRecordDO::getUserId, userId)
  48 + .eq(BargainRecordDO::getActivityId, activityId)
  49 + .orderByDesc(BargainRecordDO::getId)
  50 + .last("LIMIT 1"));
  51 + }
  52 +
45 53 default Long selectCountByUserIdAndActivityIdAndStatus(
46 54 Long userId, Long activityId, Integer status) {
47 55 return selectCount(new LambdaQueryWrapper<>(BargainRecordDO.class)
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java
... ... @@ -40,4 +40,11 @@ public interface CombinationActivityMapper extends BaseMapperX&lt;CombinationActivi
40 40 .last("LIMIT " + count));
41 41 }
42 42  
  43 + default CombinationActivityDO selectOne(Long spuId) {
  44 + return selectOne(new LambdaQueryWrapperX<CombinationActivityDO>()
  45 + .eq(CombinationActivityDO::getSpuId, spuId)
  46 + .orderByDesc(CombinationActivityDO::getCreateTime)
  47 + , false);
  48 + }
  49 +
43 50 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationRecordMapper.java
... ... @@ -29,6 +29,7 @@ public interface CombinationRecordMapper extends BaseMapperX&lt;CombinationRecordDO
29 29 .eq(CombinationRecordDO::getUserId, userId)
30 30 .eq(CombinationRecordDO::getStatus, status));
31 31 }
  32 +
32 33 /**
33 34 * 查询拼团记录
34 35 *
... ... @@ -47,10 +48,6 @@ public interface CombinationRecordMapper extends BaseMapperX&lt;CombinationRecordDO
47 48 .eq(CombinationRecordDO::getStatus, status));
48 49 }
49 50  
50   - default List<CombinationRecordDO> selectListByStatus(Integer status) {
51   - return selectList(CombinationRecordDO::getStatus, status);
52   - }
53   -
54 51 /**
55 52 * 查询拼团记录
56 53 *
... ... @@ -63,4 +60,34 @@ public interface CombinationRecordMapper extends BaseMapperX&lt;CombinationRecordDO
63 60 .eq(CombinationRecordDO::getUserId, userId)
64 61 .eq(CombinationRecordDO::getActivityId, activityId));
65 62 }
  63 +
  64 + /**
  65 + * 获取最近的 count 条数据
  66 + *
  67 + * @param count 数量
  68 + * @return 拼团记录列表
  69 + */
  70 + default List<CombinationRecordDO> selectLatestList(int count) {
  71 + return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
  72 + .orderByDesc(CombinationRecordDO::getCreateTime)
  73 + .last("LIMIT " + count));
  74 + }
  75 +
  76 + /**
  77 + * 获得最近 count 条拼团记录(团长发起的)
  78 + *
  79 + * @param activityId 拼团活动编号
  80 + * @param status 记录状态
  81 + * @param count 数量
  82 + * @return 拼团记录列表
  83 + */
  84 + default List<CombinationRecordDO> selectList(Long activityId, Integer status, Integer count) {
  85 + return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
  86 + .eqIfPresent(CombinationRecordDO::getActivityId, activityId)
  87 + .eqIfPresent(CombinationRecordDO::getStatus, status)
  88 + .eq(CombinationRecordDO::getHeadId, null) // TODO 团长的 headId 是不是 null 还是自己的记录编号来着?
  89 + .orderByDesc(CombinationRecordDO::getCreateTime)
  90 + .last("LIMIT " + count));
  91 + }
  92 +
66 93 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
... ... @@ -56,4 +56,11 @@ public interface SeckillActivityMapper extends BaseMapperX&lt;SeckillActivityDO&gt; {
56 56 .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0"));
57 57 }
58 58  
  59 + default SeckillActivityDO selectOne(Long spuId) {
  60 + return selectOne(new LambdaQueryWrapperX<SeckillActivityDO>()
  61 + .eq(SeckillActivityDO::getSpuId, spuId)
  62 + .orderByDesc(SeckillActivityDO::getCreateTime)
  63 + , false);
  64 + }
  65 +
59 66 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java
... ... @@ -98,4 +98,12 @@ public interface BargainActivityService {
98 98 */
99 99 List<BargainActivityDO> getBargainActivityListByCount(Integer count);
100 100  
  101 + /**
  102 + * 获取指定 spu 编号的活动
  103 + *
  104 + * @param spuId spu 编号
  105 + * @return 砍价活动
  106 + */
  107 + BargainActivityDO getBargainActivityBySpuId(Long spuId);
  108 +
101 109 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
... ... @@ -175,4 +175,9 @@ public class BargainActivityServiceImpl implements BargainActivityService {
175 175 return bargainActivityMapper.selectList(count, CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now());
176 176 }
177 177  
  178 + @Override
  179 + public BargainActivityDO getBargainActivityBySpuId(Long spuId) {
  180 + return bargainActivityMapper.selectOne(spuId);
  181 + }
  182 +
178 183 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainRecordService.java
... ... @@ -74,15 +74,13 @@ public interface BargainRecordService {
74 74 BargainRecordDO getBargainRecord(Long id);
75 75  
76 76 /**
77   - * 获得用户当前正在【砍价中】+ 指定活动的砍价记录
78   - *
79   - * 因为一个用户,在一个砍价活动,【砍价中】只存在一条
  77 + * 获得用户在当前砍价活动中的最后一条砍价记录
80 78 *
81 79 * @param userId 用户编号
82 80 * @param activityId 砍价记录编号
83 81 * @return 砍价记录
84 82 */
85   - BargainRecordDO getInProgressBargainRecord(Long userId, Long activityId);
  83 + BargainRecordDO getLastBargainRecord(Long userId, Long activityId);
86 84  
87 85 /**
88 86 * 获得砍价人数 Map
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainRecordServiceImpl.java
... ... @@ -115,10 +115,8 @@ public class BargainRecordServiceImpl implements BargainRecordService {
115 115 }
116 116  
117 117 @Override
118   - public BargainRecordDO getInProgressBargainRecord(Long userId, Long activityId) {
119   - List<BargainRecordDO> list = bargainRecordMapper.selectListByUserIdAndActivityIdAndStatus(
120   - userId, activityId, BargainRecordStatusEnum.IN_PROGRESS.getStatus());
121   - return CollUtil.getFirst(list);
  118 + public BargainRecordDO getLastBargainRecord(Long userId, Long activityId) {
  119 + return bargainRecordMapper.selectLastByUserIdAndActivityId(userId, activityId);
122 120 }
123 121  
124 122 @Override
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java
... ... @@ -100,4 +100,21 @@ 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 +
  112 + /**
  113 + * 获取指定 spu 编号的活动
  114 + *
  115 + * @param spuId spu 编号
  116 + * @return 拼团活动
  117 + */
  118 + CombinationActivityDO getCombinationActivityBySpuId(Long spuId);
  119 +
103 120 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java
... ... @@ -216,4 +216,16 @@ 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 +
  226 + @Override
  227 + public CombinationActivityDO getCombinationActivityBySpuId(Long spuId) {
  228 + return combinationActivityMapper.selectOne(spuId);
  229 + }
  230 +
219 231 }
... ...
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;
... ... @@ -16,8 +20,8 @@ public interface CombinationRecordService {
16 20 /**
17 21 * 更新拼团状态
18 22 *
19   - * @param status 状态
20   - * @param userId 用户编号
  23 + * @param status 状态
  24 + * @param userId 用户编号
21 25 * @param orderId 订单编号
22 26 */
23 27 void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId);
... ... @@ -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 * 创建拼团记录
... ... @@ -43,9 +48,9 @@ public interface CombinationRecordService {
43 48 /**
44 49 * 更新拼团状态和开始时间
45 50 *
46   - * @param status 状态
47   - * @param userId 用户编号
48   - * @param orderId 订单编号
  51 + * @param status 状态
  52 + * @param userId 用户编号
  53 + * @param orderId 订单编号
49 54 * @param startTime 开始时间
50 55 */
51 56 void updateRecordStatusAndStartTimeByUserIdAndOrderId(Integer status, Long userId, Long orderId, LocalDateTime startTime);
... ... @@ -68,4 +73,58 @@ 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 +
  89 + /**
  90 + * 获取所有拼团记录数
  91 + *
  92 + * @return 记录数
  93 + */
  94 + Long getRecordsCount();
  95 +
  96 + /**
  97 + * 获取最近的 count 条拼团记录
  98 + *
  99 + * @param count 限制数量
  100 + * @return 拼团记录列表
  101 + */
  102 + List<CombinationRecordDO> getLatestRecordList(int count);
  103 +
  104 + /**
  105 + * 获得最近 n 条拼团记录(团长发起的)
  106 + *
  107 + * @param activityId 拼团活动编号
  108 + * @param status 状态
  109 + * @param count 数量
  110 + * @return 拼团记录列表
  111 + */
  112 + List<CombinationRecordDO> getRecordListWithHead(Long activityId, Integer status, Integer count);
  113 +
  114 + /**
  115 + * 获取指定编号的拼团记录
  116 + *
  117 + * @param id 拼团记录编号
  118 + * @return 拼团记录
  119 + */
  120 + CombinationRecordDO getRecordById(Long id);
  121 +
  122 + /**
  123 + * 获取指定团长编号的拼团记录
  124 + *
  125 + * @param headId 团长编号
  126 + * @return 拼团记录列表
  127 + */
  128 + List<CombinationRecordDO> getRecordListByHeadId(Long headId);
  129 +
71 130 }
... ...
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;
... ... @@ -62,35 +65,35 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
62 65 @Transactional(rollbackFor = Exception.class)
63 66 public void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId) {
64 67 // 校验拼团是否存在
65   - CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
  68 + CombinationRecordDO record = validateCombinationRecord(userId, orderId);
66 69  
67 70 // 更新状态
68   - recordDO.setStatus(status);
69   - recordMapper.updateById(recordDO);
  71 + record.setStatus(status);
  72 + recordMapper.updateById(record);
70 73 }
71 74  
72 75 @Override
73 76 @Transactional(rollbackFor = Exception.class)
74 77 public void updateRecordStatusAndStartTimeByUserIdAndOrderId(Integer status, Long userId, Long orderId, LocalDateTime startTime) {
75   - CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
  78 + CombinationRecordDO record = validateCombinationRecord(userId, orderId);
76 79 // 更新状态
77   - recordDO.setStatus(status);
  80 + record.setStatus(status);
78 81 // 更新开始时间
79   - recordDO.setStartTime(startTime);
80   - recordMapper.updateById(recordDO);
  82 + record.setStartTime(startTime);
  83 + recordMapper.updateById(record);
81 84  
82 85 // 更新拼团参入人数
83   - List<CombinationRecordDO> recordDOs = recordMapper.selectListByHeadIdAndStatus(recordDO.getHeadId(), status);
84   - if (CollUtil.isNotEmpty(recordDOs)) {
85   - recordDOs.forEach(item -> {
86   - item.setUserCount(recordDOs.size());
  86 + List<CombinationRecordDO> records = recordMapper.selectListByHeadIdAndStatus(record.getHeadId(), status);
  87 + if (CollUtil.isNotEmpty(records)) {
  88 + records.forEach(item -> {
  89 + item.setUserCount(records.size());
87 90 // 校验拼团是否满足要求
88   - if (ObjectUtil.equal(recordDOs.size(), recordDO.getUserSize())) {
  91 + if (ObjectUtil.equal(records.size(), record.getUserSize())) {
89 92 item.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus());
90 93 }
91 94 });
92 95 }
93   - recordMapper.updateBatch(recordDOs);
  96 + recordMapper.updateBatch(records);
94 97 }
95 98  
96 99 private CombinationRecordDO validateCombinationRecord(Long userId, Long orderId) {
... ... @@ -104,7 +107,8 @@ 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(
  111 + Long activityId, Long userId, Long skuId, Integer count) {
108 112 // 1.1 校验拼团活动是否存在
109 113 CombinationActivityDO activity = combinationActivityService.validateCombinationActivityExists(activityId);
110 114 // 1.2 校验活动是否开启
... ... @@ -115,10 +119,24 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
115 119 if (count > activity.getSingleLimitCount()) {
116 120 throw exception(COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED);
117 121 }
  122 + // 2.1、校验活动商品是否存在
  123 + CombinationProductDO product = combinationActivityService.selectByActivityIdAndSkuId(activityId, skuId);
  124 + if (product == null) {
  125 + throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
  126 + }
  127 + // 2.2、校验 sku 是否存在
  128 + ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
  129 + if (sku == null) {
  130 + throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
  131 + }
  132 + // 2.3、 校验库存是否充足
  133 + if (count > sku.getStock()) {
  134 + throw exception(COMBINATION_ACTIVITY_UPDATE_STOCK_FAIL);
  135 + }
118 136 // 3、校验是否有拼团记录
119 137 List<CombinationRecordDO> recordList = getRecordListByUserIdAndActivityId(userId, activityId);
120 138 if (CollUtil.isEmpty(recordList)) {
121   - return;
  139 + return new KeyValue<>(activity, product);
122 140 }
123 141 // 4、校验是否超出总限购数量
124 142 Integer sumValue = getSumValue(convertList(recordList, CombinationRecordDO::getCount,
... ... @@ -129,7 +147,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
129 147 // 5、校验拼团记录是否存在未支付的订单(如果存在未支付的订单则不允许发起新的拼团)
130 148 CombinationRecordDO record = findFirst(recordList, item -> ObjectUtil.equals(item.getStatus(), null));
131 149 if (record == null) {
132   - return;
  150 + return new KeyValue<>(activity, product);
133 151 }
134 152 // 5.1、查询关联的订单是否已经支付
135 153 // 当前 activityId 已经有未支付的订单,不允许在发起新的;要么支付,要么去掉先;
... ... @@ -138,57 +156,45 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
138 156 if (ObjectUtil.equal(orderStatus, TradeOrderStatusEnum.UNPAID.getStatus())) {
139 157 throw exception(COMBINATION_RECORD_FAILED_ORDER_STATUS_UNPAID);
140 158 }
  159 +
  160 + return new KeyValue<>(activity, product);
141 161 }
142 162  
143 163 // TODO 芋艿:在详细 review 下;
144 164 @Override
145 165 @Transactional(rollbackFor = Exception.class)
146 166 public void createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
147   -
148   - // 1.1、 校验拼团活动
149   - CombinationActivityDO activity = combinationActivityService.validateCombinationActivityExists(reqDTO.getActivityId());
150   - // 1.2 校验是否超出单次限购数量
151   - if (reqDTO.getCount() > activity.getSingleLimitCount()) {
152   - throw exception(COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED);
153   - }
154   - // 1.3、校验是否有拼团记录
155   - List<CombinationRecordDO> records = getRecordListByUserIdAndActivityId(reqDTO.getUserId(), reqDTO.getActivityId());
156   - if (CollUtil.isEmpty(records)) {
157   - return;
158   - }
159   - // 1.4、校验是否超出总限购数量
160   - Integer sumValue = getSumValue(convertList(records, CombinationRecordDO::getCount,
161   - item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus())), i -> i, Integer::sum);
162   - if ((sumValue + reqDTO.getCount()) > activity.getTotalLimitCount()) {
163   - throw exception(COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED);
164   - }
165   - // 2、 校验用户是否参加了其它拼团
  167 + // 1、校验拼团活动
  168 + KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(
  169 + reqDTO.getActivityId(), reqDTO.getUserId(), reqDTO.getSkuId(), reqDTO.getCount());
  170 + CombinationActivityDO activity = keyValue.getKey();
  171 + // 2、校验用户是否参加了其它拼团
166 172 List<CombinationRecordDO> recordDOList = recordMapper.selectListByUserIdAndStatus(reqDTO.getUserId(), CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
167 173 if (CollUtil.isNotEmpty(recordDOList)) {
168 174 throw exception(COMBINATION_RECORD_FAILED_HAVE_JOINED);
169 175 }
170   - // 3、 校验活动是否开启
  176 + // 3、校验活动是否开启
171 177 if (LocalDateTime.now().isAfter(activity.getStartTime())) {
172 178 throw exception(COMBINATION_RECORD_FAILED_TIME_NOT_START);
173 179 }
174   - // 4、 校验当前活动是否过期
  180 + // 4、校验当前活动是否过期
175 181 if (LocalDateTime.now().isAfter(activity.getEndTime())) {
176 182 throw exception(COMBINATION_RECORD_FAILED_TIME_END);
177 183 }
178   - // 5、 父拼团是否存在,是否已经满了
  184 + // 5、父拼团是否存在,是否已经满了
179 185 if (reqDTO.getHeadId() != null) {
180   - // 查询进行中的父拼团
  186 + // 5.1、查询进行中的父拼团
181 187 CombinationRecordDO record = recordMapper.selectOneByHeadId(reqDTO.getHeadId(), CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
182 188 if (record == null) {
183 189 throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS);
184 190 }
185   - // 校验拼团是否满足要求
  191 + // 5.2、校验拼团是否满足要求
186 192 if (ObjectUtil.equal(record.getUserCount(), record.getUserSize())) {
187 193 throw exception(COMBINATION_RECORD_USER_FULL);
188 194 }
189 195 }
190 196  
191   - // 2. 创建拼团记录
  197 + // 6. 创建拼团记录
192 198 MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId());
193 199 ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId());
194 200 ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId());
... ... @@ -205,13 +211,38 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
205 211 return recordMapper.selectListByUserIdAndActivityId(userId, activityId);
206 212 }
207 213  
208   - /**
209   - * APP 端获取开团记录
210   - *
211   - * @return 开团记录
212   - */
213   - public List<CombinationRecordDO> getRecordListByStatus(Integer status) {
214   - return recordMapper.selectListByStatus(status);
  214 + @Override
  215 + public CombinationValidateJoinRespDTO validateJoinCombination(Long activityId, Long userId, Long skuId, Integer count) {
  216 + KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(activityId, userId, skuId, count);
  217 + return new CombinationValidateJoinRespDTO()
  218 + .setActivityId(keyValue.getKey().getId())
  219 + .setName(keyValue.getKey().getName())
  220 + .setCombinationPrice(keyValue.getValue().getCombinationPrice());
  221 + }
  222 +
  223 + @Override
  224 + public Long getRecordsCount() {
  225 + return recordMapper.selectCount();
  226 + }
  227 +
  228 + @Override
  229 + public List<CombinationRecordDO> getLatestRecordList(int count) {
  230 + return recordMapper.selectLatestList(count);
  231 + }
  232 +
  233 + @Override
  234 + public List<CombinationRecordDO> getRecordListWithHead(Long activityId, Integer status, Integer count) {
  235 + return recordMapper.selectList(activityId, status, count);
  236 + }
  237 +
  238 + @Override
  239 + public CombinationRecordDO getRecordById(Long id) {
  240 + return recordMapper.selectById(id);
  241 + }
  242 +
  243 + @Override
  244 + public List<CombinationRecordDO> getRecordListByHeadId(Long headId) {
  245 + return recordMapper.selectList(CombinationRecordDO::getHeadId, headId);
215 246 }
216 247  
217 248 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
... ... @@ -119,4 +119,12 @@ public interface SeckillActivityService {
119 119 */
120 120 SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);
121 121  
  122 + /**
  123 + * 获取指定 spu 编号的活动
  124 + *
  125 + * @param spuId spu 编号
  126 + * @return 秒杀活动
  127 + */
  128 + SeckillActivityDO getSeckillActivityBySpuId(Long spuId);
  129 +
122 130 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
... ... @@ -310,4 +310,9 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
310 310 return SeckillActivityConvert.INSTANCE.convert02(activity, product);
311 311 }
312 312  
  313 + @Override
  314 + public SeckillActivityDO getSeckillActivityBySpuId(Long spuId) {
  315 + return seckillActivityMapper.selectOne(spuId);
  316 + }
  317 +
313 318 }
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java
... ... @@ -20,6 +20,14 @@ public interface TradeOrderApi {
20 20 */
21 21 List<TradeOrderRespDTO> getOrderList(Collection<Long> ids);
22 22  
  23 + /**
  24 + * 获得订单
  25 + *
  26 + * @param id 订单编号
  27 + * @return 订单
  28 + */
  29 + TradeOrderRespDTO getOrder(Long id);
  30 +
23 31 // TODO 芋艿:看看是不是可以删除掉;
24 32 /**
25 33 * 获取订单状态
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderOperateTypeEnum.java
... ... @@ -16,6 +16,7 @@ public enum TradeOrderOperateTypeEnum {
16 16 MEMBER_CREATE(1, "用户下单"),
17 17 ADMIN_UPDATE_PRICE(2, "订单价格 {oldPayPrice} 修改,实际支付金额为 {newPayPrice} 元"),
18 18 MEMBER_PAY(10, "用户付款成功"),
  19 + SYSTEM_UP_ADDRESS(11, "订单 {orderId} 收货地址修改"),
19 20 ADMIN_DELIVERY(20, "已发货,快递公司:{deliveryName},快递单号:{logisticsNo}"),
20 21 MEMBER_RECEIVE(30, "用户已收货"),
21 22 SYSTEM_RECEIVE(31, "到期未收货,系统自动确认收货"),
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java
... ... @@ -32,6 +32,11 @@ public class TradeOrderApiImpl implements TradeOrderApi {
32 32 }
33 33  
34 34 @Override
  35 + public TradeOrderRespDTO getOrder(Long id) {
  36 + return TradeOrderConvert.INSTANCE.convert(tradeOrderQueryService.getOrder(id));
  37 + }
  38 +
  39 + @Override
35 40 public Integer getOrderStatus(Long id) {
36 41 TradeOrderDO order = tradeOrderQueryService.getOrder(id);
37 42 if (order == null) {
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
... ... @@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDeta
16 16 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
17 17 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
18 18 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
  19 +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
19 20 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
20 21 import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO;
21 22 import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
... ... @@ -38,6 +39,7 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
38 39 import org.mapstruct.Mapper;
39 40 import org.mapstruct.Mapping;
40 41 import org.mapstruct.Mappings;
  42 +import org.mapstruct.Named;
41 43 import org.mapstruct.factory.Mappers;
42 44  
43 45 import java.util.ArrayList;
... ... @@ -261,6 +263,19 @@ public interface TradeOrderConvert {
261 263 return bo;
262 264 }
263 265  
  266 + @Named("convertList04")
264 267 List<TradeOrderRespDTO> convertList04(List<TradeOrderDO> list);
265 268  
  269 + @Mappings({
  270 + @Mapping(target = "activityId", source = "order.combinationActivityId"),
  271 + @Mapping(target = "spuId", source = "item.spuId"),
  272 + @Mapping(target = "skuId", source = "item.skuId"),
  273 + @Mapping(target = "count", source = "item.count"),
  274 + @Mapping(target = "orderId", source = "order.id"),
  275 + @Mapping(target = "userId", source = "order.userId"),
  276 + @Mapping(target = "headId", source = "order.combinationHeadId"),
  277 + @Mapping(target = "combinationPrice", source = "item.payPrice"),
  278 + })
  279 + CombinationRecordCreateReqDTO convert(TradeOrderDO order, TradeOrderItemDO item);
  280 +
266 281 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
... ... @@ -7,7 +7,10 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
7 7 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
8 8 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
9 9 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
10   -import cn.iocoder.yudao.module.trade.enums.order.*;
  10 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum;
  11 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
  12 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
  13 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
11 14 import com.baomidou.mybatisplus.annotation.KeySequence;
12 15 import com.baomidou.mybatisplus.annotation.TableName;
13 16 import lombok.*;
... ... @@ -308,4 +311,17 @@ public class TradeOrderDO extends BaseDO {
308 311 */
309 312 private Long bargainRecordId;
310 313  
  314 + /**
  315 + * 拼团活动编号
  316 + *
  317 + * 关联 CombinationActivityDO 的 id 字段
  318 + */
  319 + private Long combinationActivityId;
  320 + /**
  321 + * 团长编号
  322 + *
  323 + * 关联 CombinationRecordDO 的 id 字段
  324 + */
  325 + private Long combinationHeadId;
  326 +
311 327 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
... ... @@ -738,16 +738,19 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
738 738 }
739 739  
740 740 @Override
  741 + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_UP_ADDRESS)
741 742 public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) {
742 743 // 校验交易订单
743   - validateOrderExists(reqVO.getId());
  744 + TradeOrderDO order = validateOrderExists(reqVO.getId());
744 745 // TODO @puhui999:是否需要校验订单是否发货
745 746 // TODO 发货后是否支持修改收货地址
746 747  
747 748 // 更新
748 749 tradeOrderMapper.updateById(TradeOrderConvert.INSTANCE.convert(reqVO));
749 750  
750   - // TODO @puhui999:操作日志
  751 + // 记录订单日志
  752 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus(),
  753 + MapUtil.<String, Object>builder().put("orderId", order.getId()).build());
751 754 }
752 755  
753 756 @Override
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java
1 1 package cn.iocoder.yudao.module.trade.service.order.handler;
2 2  
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.ObjectUtil;
3 5 import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
  6 +import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
  7 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
  8 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
  9 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
4 10 import org.springframework.stereotype.Component;
5 11  
6 12 import javax.annotation.Resource;
  13 +import java.util.List;
7 14  
8 15 /**
9 16 * 拼团订单 handler 接口实现类
... ... @@ -16,38 +23,43 @@ public class TradeCombinationHandler implements TradeOrderHandler {
16 23 @Resource
17 24 private CombinationRecordApi combinationRecordApi;
18 25  
19   -// @Override
20   -// public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
21   -// // 如果不是拼团订单则结束
22   -// if (ObjectUtil.notEqual(TradeOrderTypeEnum.COMBINATION.getType(), reqBO.getOrderType())) {
23   -// return;
24   -// }
25   -//
26   -// // 获取商品信息
27   -// TradeBeforeOrderCreateReqBO.Item item = reqBO.getItems().get(0);
28   -// // 校验是否满足拼团活动相关限制
29   -// combinationRecordApi.validateCombinationRecord(reqBO.getCombinationActivityId(), reqBO.getUserId(), item.getSkuId(), item.getCount());
30   -// }
31   -
32   -// @Override
33   -// public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {
34   -// if (reqBO.getCombinationActivityId() == null) {
35   -// return;
36   -// }
37   -//
38   -// // 创建砍价记录
39   -// combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(reqBO));
40   -// }
41   -
42   -// @Override
43   -// public void afterPayOrder(TradeAfterPayOrderReqBO reqBO) {
44   -// // 如果不是拼团订单则结束
45   -// if (ObjectUtil.notEqual(TradeOrderTypeEnum.COMBINATION.getType(), reqBO.getOrderType())) {
46   -// return;
47   -// }
48   -//
49   -// // 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录
50   -// combinationRecordApi.updateRecordStatusToInProgress(reqBO.getUserId(), reqBO.getOrderId(), reqBO.getPayTime());
51   -// }
  26 + @Override
  27 + public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
  28 + // 如果不是拼团订单则结束
  29 + if (ObjectUtil.notEqual(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
  30 + return;
  31 + }
  32 + Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
  33 +
  34 + // 获取商品信息
  35 + TradeOrderItemDO item = orderItems.get(0);
  36 + // 校验是否满足拼团活动相关限制
  37 + combinationRecordApi.validateCombinationRecord(order.getCombinationActivityId(), order.getUserId(), item.getSkuId(), item.getCount());
  38 + }
  39 +
  40 + @Override
  41 + public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
  42 + // 如果不是拼团订单则结束
  43 + if (ObjectUtil.notEqual(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
  44 + return;
  45 + }
  46 + Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
  47 +
  48 + // 获取商品信息
  49 + TradeOrderItemDO item = orderItems.get(0);
  50 + // 创建砍价记录
  51 + combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, item));
  52 + }
  53 +
  54 + @Override
  55 + public void afterPayOrder(TradeOrderDO order) {
  56 + // 如果不是拼团订单则结束
  57 + if (ObjectUtil.notEqual(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
  58 + return;
  59 + }
  60 +
  61 + // 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录
  62 + combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), order.getPayTime());
  63 + }
52 64  
53 65 }
... ...
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;
... ...