Commit 74a5e40d8d4306d48adaea4968a81e8f58a4ef92

Authored by owen
2 parents df16278a 7b727d5c

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

# Conflicts:
#	yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java
Showing 59 changed files with 1164 additions and 495 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
... ... @@ -240,7 +240,8 @@ public class CollectionUtils {
240 240 return valueFunc.apply(t);
241 241 }
242 242  
243   - public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc, BinaryOperator<V> accumulator) {
  243 + public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
  244 + BinaryOperator<V> accumulator) {
244 245 if (CollUtil.isEmpty(from)) {
245 246 return null;
246 247 }
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
... ... @@ -56,7 +56,7 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
56 56 }
57 57  
58 58 default Long selectCount() {
59   - return selectCount(new QueryWrapper<T>());
  59 + return selectCount(new QueryWrapper<>());
60 60 }
61 61  
62 62 default Long selectCount(String field, Object value) {
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
... ... @@ -4,7 +4,6 @@ import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCr
4 4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
5 5  
6 6 import javax.validation.Valid;
7   -import java.time.LocalDateTime;
8 7  
9 8 // TODO @芋艿:后面也再撸撸这几个接口
10 9  
... ... @@ -18,19 +17,21 @@ public interface CombinationRecordApi {
18 17 /**
19 18 * 校验是否满足拼团条件
20 19 *
21   - * @param activityId 活动编号
22 20 * @param userId 用户编号
  21 + * @param activityId 活动编号
  22 + * @param headId 团长编号
23 23 * @param skuId sku 编号
24 24 * @param count 数量
25 25 */
26   - void validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count);
  26 + void validateCombinationRecord(Long userId, Long activityId, Long headId, Long skuId, Integer count);
27 27  
28 28 /**
29 29 * 创建开团记录
30 30 *
31 31 * @param reqDTO 请求 DTO
  32 + * @return 开团记录编号
32 33 */
33   - void createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
  34 + Long createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
34 35  
35 36 /**
36 37 * 查询拼团记录是否成功
... ... @@ -42,14 +43,6 @@ public interface CombinationRecordApi {
42 43 boolean isCombinationRecordSuccess(Long userId, Long orderId);
43 44  
44 45 /**
45   - * 更新拼团状态为【成功】
46   - *
47   - * @param userId 用户编号
48   - * @param orderId 订单编号
49   - */
50   - void updateRecordStatusToSuccess(Long userId, Long orderId);
51   -
52   - /**
53 46 * 更新拼团状态为【失败】
54 47 *
55 48 * @param userId 用户编号
... ... @@ -58,25 +51,18 @@ public interface CombinationRecordApi {
58 51 void updateRecordStatusToFailed(Long userId, Long orderId);
59 52  
60 53 /**
61   - * 更新拼团状态为 进行中
62   - *
63   - * @param userId 用户编号
64   - * @param orderId 订单编号
65   - * @param startTime 开始时间
66   - */
67   - void updateRecordStatusToInProgress(Long userId, Long orderId, LocalDateTime startTime);
68   -
69   - /**
70 54 * 【下单前】校验是否满足拼团活动条件
71 55 *
72 56 * 如果校验失败,则抛出业务异常
73 57 *
74   - * @param activityId 活动编号
75 58 * @param userId 用户编号
  59 + * @param activityId 活动编号
  60 + * @param headId 团长编号
76 61 * @param skuId sku 编号
77 62 * @param count 数量
78 63 * @return 拼团信息
79 64 */
80   - CombinationValidateJoinRespDTO validateJoinCombination(Long activityId, Long userId, Long skuId, Integer count);
  65 + CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId,
  66 + Long skuId, Integer count);
81 67  
82 68 }
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordCreateReqDTO.java
... ... @@ -4,7 +4,6 @@ import lombok.Data;
4 4  
5 5 import javax.validation.constraints.NotNull;
6 6  
7   -// TODO @芋艿:这块要在看看
8 7 /**
9 8 * 拼团记录的创建 Request DTO
10 9 *
... ... @@ -46,7 +45,6 @@ public class CombinationRecordCreateReqDTO {
46 45 /**
47 46 * 团长编号
48 47 */
49   - @NotNull(message = "团长编号不能为空")
50 48 private Long headId;
51 49 /**
52 50 * 拼团商品单价
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
... ... @@ -80,7 +80,7 @@ public interface ErrorCodeConstants {
80 80 ErrorCode COMBINATION_RECORD_EXISTS = new ErrorCode(1_013_011_001, "拼团失败,已参与过该拼团");
81 81 ErrorCode COMBINATION_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1_013_011_002, "拼团失败,父拼团不存在");
82 82 ErrorCode COMBINATION_RECORD_USER_FULL = new ErrorCode(1_013_011_003, "拼团失败,拼团人数已满");
83   - ErrorCode COMBINATION_RECORD_FAILED_HAVE_JOINED = new ErrorCode(1_013_011_004, "拼团失败,已参与其它拼团");
  83 + ErrorCode COMBINATION_RECORD_FAILED_HAVE_JOINED = new ErrorCode(1_013_011_004, "拼团失败,原因:存在该活动正在进行的拼团记录");
84 84 ErrorCode COMBINATION_RECORD_FAILED_TIME_NOT_START = new ErrorCode(1_013_011_005, "拼团失败,活动未开始");
85 85 ErrorCode COMBINATION_RECORD_FAILED_TIME_END = new ErrorCode(1_013_011_006, "拼团失败,活动已经结束");
86 86 ErrorCode COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_011_007, "拼团失败,原因:单次限购超出");
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/combination/CombinationRecordStatusEnum.java
... ... @@ -40,4 +40,12 @@ public enum CombinationRecordStatusEnum implements IntArrayValuable {
40 40 return ObjectUtil.equal(status, SUCCESS.getStatus());
41 41 }
42 42  
  43 + public static boolean isInProgress(Integer status) {
  44 + return ObjectUtil.equal(status, IN_PROGRESS.getStatus());
  45 + }
  46 +
  47 + public static boolean isFailed(Integer status) {
  48 + return ObjectUtil.equal(status, FAILED.getStatus());
  49 + }
  50 +
43 51 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
... ... @@ -2,12 +2,15 @@ package cn.iocoder.yudao.module.promotion.api.combination;
2 2  
3 3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
4 4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
  5 +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
5 6 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
6 7 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
7 8 import org.springframework.stereotype.Service;
8 9  
9 10 import javax.annotation.Resource;
10   -import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  13 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COMBINATION_RECORD_NOT_EXISTS;
11 14  
12 15 /**
13 16 * 拼团活动 API 实现类
... ... @@ -21,23 +24,22 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
21 24 private CombinationRecordService recordService;
22 25  
23 26 @Override
24   - public void validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count) {
25   - recordService.validateCombinationRecord(activityId, userId, skuId, count);
  27 + public void validateCombinationRecord(Long userId, Long activityId, Long headId, Long skuId, Integer count) {
  28 + recordService.validateCombinationRecord(userId, activityId, headId, skuId, count);
26 29 }
27 30  
28 31 @Override
29   - public void createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
30   - recordService.createCombinationRecord(reqDTO);
  32 + public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
  33 + return recordService.createCombinationRecord(reqDTO);
31 34 }
32 35  
33 36 @Override
34 37 public boolean isCombinationRecordSuccess(Long userId, Long orderId) {
35   - return CombinationRecordStatusEnum.isSuccess(recordService.getCombinationRecord(userId, orderId).getStatus());
36   - }
37   -
38   - @Override
39   - public void updateRecordStatusToSuccess(Long userId, Long orderId) {
40   - recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.SUCCESS.getStatus(), userId, orderId);
  38 + CombinationRecordDO record = recordService.getCombinationRecord(userId, orderId);
  39 + if (record == null) {
  40 + throw exception(COMBINATION_RECORD_NOT_EXISTS);
  41 + }
  42 + return CombinationRecordStatusEnum.isSuccess(record.getStatus());
41 43 }
42 44  
43 45 @Override
... ... @@ -46,14 +48,8 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
46 48 }
47 49  
48 50 @Override
49   - public void updateRecordStatusToInProgress(Long userId, Long orderId, LocalDateTime startTime) {
50   - recordService.updateRecordStatusAndStartTimeByUserIdAndOrderId(CombinationRecordStatusEnum.IN_PROGRESS.getStatus(),
51   - userId, orderId, startTime);
52   - }
53   -
54   - @Override
55   - public CombinationValidateJoinRespDTO validateJoinCombination(Long activityId, Long userId, Long skuId, Integer count) {
56   - return recordService.validateJoinCombination(activityId, userId, skuId, count);
  51 + public CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId, Long skuId, Integer count) {
  52 + return recordService.validateJoinCombination(userId, activityId, headId, skuId, count);
57 53 }
58 54  
59 55 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationActivityController.java
... ... @@ -5,14 +5,14 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
7 7 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
8   -import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
9   -import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
10   -import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityRespVO;
11   -import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
  8 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.*;
12 9 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
13 10 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
14 11 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
  12 +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
  13 +import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
15 14 import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
  15 +import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
16 16 import io.swagger.v3.oas.annotations.Operation;
17 17 import io.swagger.v3.oas.annotations.Parameter;
18 18 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -23,6 +23,8 @@ import org.springframework.web.bind.annotation.*;
23 23 import javax.annotation.Resource;
24 24 import javax.validation.Valid;
25 25 import java.util.List;
  26 +import java.util.Map;
  27 +import java.util.Set;
26 28  
27 29 import static cn.hutool.core.collection.CollectionUtil.newArrayList;
28 30 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -36,6 +38,8 @@ public class CombinationActivityController {
36 38  
37 39 @Resource
38 40 private CombinationActivityService combinationActivityService;
  41 + @Resource
  42 + private CombinationRecordService combinationRecordService;
39 43  
40 44 @Resource
41 45 private ProductSpuApi productSpuApi;
... ... @@ -70,14 +74,14 @@ public class CombinationActivityController {
70 74 @PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
71 75 public CommonResult<CombinationActivityRespVO> getCombinationActivity(@RequestParam("id") Long id) {
72 76 CombinationActivityDO activity = combinationActivityService.getCombinationActivity(id);
73   - List<CombinationProductDO> products = combinationActivityService.getCombinationProductsByActivityIds(newArrayList(id));
  77 + List<CombinationProductDO> products = combinationActivityService.getCombinationProductListByActivityIds(newArrayList(id));
74 78 return success(CombinationActivityConvert.INSTANCE.convert(activity, products));
75 79 }
76 80  
77 81 @GetMapping("/page")
78 82 @Operation(summary = "获得拼团活动分页")
79 83 @PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
80   - public CommonResult<PageResult<CombinationActivityRespVO>> getCombinationActivityPage(
  84 + public CommonResult<PageResult<CombinationActivityPageItemRespVO>> getCombinationActivityPage(
81 85 @Valid CombinationActivityPageReqVO pageVO) {
82 86 // 查询拼团活动
83 87 PageResult<CombinationActivityDO> pageResult = combinationActivityService.getCombinationActivityPage(pageVO);
... ... @@ -85,12 +89,21 @@ public class CombinationActivityController {
85 89 return success(PageResult.empty(pageResult.getTotal()));
86 90 }
87 91  
  92 + // 统计数据
  93 + Set<Long> activityIds = convertSet(pageResult.getList(), CombinationActivityDO::getId);
  94 + Map<Long, Integer> groupCountMap = combinationRecordService.getCombinationRecordCountMapByActivity(
  95 + activityIds, null, CombinationRecordDO.HEAD_ID_GROUP);
  96 + Map<Long, Integer> groupSuccessCountMap = combinationRecordService.getCombinationRecordCountMapByActivity(
  97 + activityIds, CombinationRecordStatusEnum.SUCCESS.getStatus(), CombinationRecordDO.HEAD_ID_GROUP);
  98 + Map<Long, Integer> recordCountMap = combinationRecordService.getCombinationRecordCountMapByActivity(
  99 + activityIds, null, null);
88 100 // 拼接数据
89   - List<CombinationProductDO> products = combinationActivityService.getCombinationProductsByActivityIds(
  101 + List<CombinationProductDO> products = combinationActivityService.getCombinationProductListByActivityIds(
90 102 convertSet(pageResult.getList(), CombinationActivityDO::getId));
91 103 List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(
92 104 convertSet(pageResult.getList(), CombinationActivityDO::getSpuId));
93   - return success(CombinationActivityConvert.INSTANCE.convertPage(pageResult, products, spus));
  105 + return success(CombinationActivityConvert.INSTANCE.convertPage(pageResult, products,
  106 + groupCountMap, groupSuccessCountMap, recordCountMap, spus));
94 107 }
95 108  
96 109 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationRecordController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.combination;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordPageItemRespVO;
  6 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
  7 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordSummaryVO;
  8 +import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
  9 +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
  10 +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
  11 +import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
  12 +import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
  13 +import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
  14 +import io.swagger.v3.oas.annotations.Operation;
  15 +import io.swagger.v3.oas.annotations.tags.Tag;
  16 +import org.springframework.context.annotation.Lazy;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.GetMapping;
  20 +import org.springframework.web.bind.annotation.RequestMapping;
  21 +import org.springframework.web.bind.annotation.RestController;
  22 +
  23 +import javax.annotation.Resource;
  24 +import javax.validation.Valid;
  25 +import java.util.List;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  29 +
  30 +@Tag(name = "管理后台 - 拼团记录")
  31 +@RestController
  32 +@RequestMapping("/promotion/combination-record")
  33 +@Validated
  34 +public class CombinationRecordController {
  35 +
  36 + @Resource
  37 + private CombinationActivityService combinationActivityService;
  38 + @Resource
  39 + @Lazy
  40 + private CombinationRecordService combinationRecordService;
  41 +
  42 + @GetMapping("/page")
  43 + @Operation(summary = "获得拼团记录分页")
  44 + @PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
  45 + public CommonResult<PageResult<CombinationRecordPageItemRespVO>> getBargainRecordPage(@Valid CombinationRecordReqPageVO pageVO) {
  46 + PageResult<CombinationRecordDO> recordPage = combinationRecordService.getCombinationRecordPage(pageVO);
  47 + List<CombinationActivityDO> activities = combinationActivityService.getCombinationActivityListByIds(
  48 + convertSet(recordPage.getList(), CombinationRecordDO::getActivityId));
  49 + return success(CombinationActivityConvert.INSTANCE.convert(recordPage, activities));
  50 + }
  51 +
  52 + @GetMapping("/get-summary")
  53 + @Operation(summary = "获得拼团记录的概要信息", description = "用于拼团记录页面展示")
  54 + @PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
  55 + public CommonResult<CombinationRecordSummaryVO> getCombinationRecordSummary() {
  56 + CombinationRecordSummaryVO summaryVO = new CombinationRecordSummaryVO();
  57 + summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null)); // 获取所有拼团记录
  58 + summaryVO.setSuccessCount(combinationRecordService.getCombinationRecordCount( // 获取成团记录
  59 + CombinationRecordStatusEnum.SUCCESS.getStatus(), null));
  60 + summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(null, Boolean.TRUE));// 获取虚拟成团记录
  61 + return success(summaryVO);
  62 + }
  63 +
  64 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityBaseVO.java
... ... @@ -48,6 +48,10 @@ public class CombinationActivityBaseVO {
48 48 @NotNull(message = "开团人数不能为空")
49 49 private Integer userSize;
50 50  
  51 + @Schema(description = "虚拟成团", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  52 + @NotNull(message = "虚拟成团不能为空")
  53 + private Boolean virtualGroup;
  54 +
51 55 @Schema(description = "限制时长(小时)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
52 56 @NotNull(message = "限制时长不能为空")
53 57 private Integer limitDuration;
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityPageItemRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
  2 +
  3 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductRespVO;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import java.time.LocalDateTime;
  10 +import java.util.List;
  11 +
  12 +@Schema(description = "管理后台 - 拼团活动的分页项 Response VO")
  13 +@Data
  14 +@EqualsAndHashCode(callSuper = true)
  15 +@ToString(callSuper = true)
  16 +public class CombinationActivityPageItemRespVO extends CombinationActivityBaseVO {
  17 +
  18 + @Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
  19 + private Long id;
  20 +
  21 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  22 + private LocalDateTime createTime;
  23 +
  24 + @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  25 + private Integer status;
  26 +
  27 + @Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
  28 + private List<CombinationProductRespVO> products;
  29 +
  30 + // ========== 商品字段 ==========
  31 +
  32 + @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 name 读取
  33 + example = "618大促")
  34 + private String spuName;
  35 + @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 picUrl 读取
  36 + example = "https://www.iocoder.cn/xx.png")
  37 + private String picUrl;
  38 + @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 marketPrice 读取
  39 + example = "50")
  40 + private Integer marketPrice;
  41 +
  42 + // ========== 统计字段 ==========
  43 +
  44 + @Schema(description = "开团组数", requiredMode = Schema.RequiredMode.REQUIRED, example = "33")
  45 + private Integer groupCount;
  46 +
  47 + @Schema(description = "成团组数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
  48 + private Integer groupSuccessCount;
  49 +
  50 + @Schema(description = "购买次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  51 + private Integer recordCount;
  52 +
  53 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityPageReqVO.java
... ... @@ -15,8 +15,7 @@ public class CombinationActivityPageReqVO extends PageParam {
15 15 @Schema(description = "拼团名称", example = "赵六")
16 16 private String name;
17 17  
18   - @Schema(description = "活动状态:0开启 1关闭", example = "0")
  18 + @Schema(description = "活动状态", example = "0")
19 19 private Integer status;
20 20  
21   -
22 21 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityRespVO.java
... ... @@ -6,8 +6,6 @@ import lombok.Data;
6 6 import lombok.EqualsAndHashCode;
7 7 import lombok.ToString;
8 8  
9   -import javax.validation.Valid;
10   -import javax.validation.constraints.NotNull;
11 9 import java.time.LocalDateTime;
12 10 import java.util.List;
13 11  
... ... @@ -20,30 +18,12 @@ public class CombinationActivityRespVO extends CombinationActivityBaseVO {
20 18 @Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
21 19 private Long id;
22 20  
23   - @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618 大促")
24   - private String spuName;
25   -
26   - @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
27   - private String picUrl;
28   -
29 21 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
30 22 private LocalDateTime createTime;
31 23  
32 24 @Schema(description = "开团人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
33 25 private Integer userSize;
34 26  
35   - @Schema(description = "开团组数", requiredMode = Schema.RequiredMode.REQUIRED, example = "33")
36   - private Integer totalCount;
37   -
38   - @Schema(description = "成团组数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
39   - private Integer successCount;
40   -
41   - @Schema(description = "虚拟成团", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
42   - private Integer virtualGroup;
43   -
44   - @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
45   - private Integer status;
46   -
47 27 @Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
48 28 private List<CombinationProductRespVO> products;
49 29  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/recrod/CombinationRecordBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +/**
  13 + * 拼团记录 Base VO,提供给添加、修改、详细的子 VO 使用
  14 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  15 + *
  16 + * @author HUIHUI
  17 + */
  18 +@Data
  19 +public class CombinationRecordBaseVO {
  20 +
  21 + @Schema(description = "拼团记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  22 + private Long id;
  23 +
  24 + @Schema(description = "拼团活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  25 + private Long activityId;
  26 +
  27 + @Schema(description = "团长编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  28 + private Long headId;
  29 +
  30 + // ========== 用户相关 ==========
  31 +
  32 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9430")
  33 + @NotNull(message = "用户编号不能为空")
  34 + private Long userId;
  35 +
  36 + @Schema(description = "用户昵称", example = "老芋艿")
  37 + private String nickname;
  38 +
  39 + @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg")
  40 + private String avatar;
  41 +
  42 + // ========== 商品相关 ==========
  43 +
  44 + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23622")
  45 + @NotNull(message = "商品 SPU 编号不能为空")
  46 + private Long spuId;
  47 +
  48 + @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29950")
  49 + @NotNull(message = "商品 SKU 编号不能为空")
  50 + private Long skuId;
  51 +
  52 + @Schema(description = "商品名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是大黄豆")
  53 + private String spuName;
  54 +
  55 + @Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
  56 + private String picUrl;
  57 +
  58 + @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
  59 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  60 + private LocalDateTime expireTime;
  61 +
  62 + @Schema(description = "可参团人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  63 + private Integer userSize;
  64 +
  65 + @Schema(description = "已参团人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  66 + private Integer userCount;
  67 +
  68 + @Schema(description = "拼团状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  69 + private Integer status;
  70 +
  71 + @Schema(description = "是否虚拟成团", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  72 + private Boolean virtualGroup;
  73 +
  74 + @Schema(description = "开始时间 (订单付款后开始的时间)", requiredMode = Schema.RequiredMode.REQUIRED)
  75 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  76 + private LocalDateTime startTime;
  77 +
  78 + @Schema(description = "结束时间(成团时间/失败时间)", requiredMode = Schema.RequiredMode.REQUIRED)
  79 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  80 + private LocalDateTime endTime;
  81 +
  82 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/recrod/CombinationRecordPageItemRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod;
  2 +
  3 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityRespVO;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +@Schema(description = "管理后台 - 拼团记录的分页项 Response VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class CombinationRecordPageItemRespVO extends CombinationRecordBaseVO {
  14 +
  15 + // ========== 活动相关 ==========
  16 +
  17 + private CombinationActivityRespVO activity;
  18 +
  19 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/recrod/CombinationRecordReqPageVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.promotion.enums.bargain.BargainRecordStatusEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +import lombok.ToString;
  10 +import org.springframework.format.annotation.DateTimeFormat;
  11 +
  12 +import java.time.LocalDateTime;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  15 +
  16 +@Schema(description = "管理后台 - 拼团记录分页 Request VO")
  17 +@Data
  18 +@EqualsAndHashCode(callSuper = true)
  19 +@ToString(callSuper = true)
  20 +public class CombinationRecordReqPageVO extends PageParam {
  21 +
  22 + @Schema(description = "活动状态", example = "1")
  23 + @InEnum(BargainRecordStatusEnum.class)
  24 + private Integer status;
  25 +
  26 + @Schema(description = "创建时间")
  27 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  28 + private LocalDateTime[] createTime;
  29 +
  30 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/recrod/CombinationRecordSummaryVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - 拼团记录信息统计 Response VO")
  7 +@Data
  8 +public class CombinationRecordSummaryVO {
  9 +
  10 + @Schema(description = "所有拼团记录", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  11 + private Long userCount;
  12 +
  13 + @Schema(description = "成团记录", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long successCount;
  15 +
  16 + @Schema(description = "虚拟成团记录", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + private Long virtualGroupCount;
  18 +
  19 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityRespVO.java
... ... @@ -18,16 +18,6 @@ public class SeckillActivityRespVO extends SeckillActivityBaseVO {
18 18 @Schema(description = "秒杀活动 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
19 19 private Long id;
20 20  
21   - @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 name 读取
22   - example = "618大促")
23   - private String spuName;
24   - @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 picUrl 读取
25   - example = "https://www.iocoder.cn/xx.png")
26   - private String picUrl;
27   - @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 marketPrice 读取
28   - example = "50")
29   - private Integer marketPrice;
30   -
31 21 @Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
32 22 private List<SeckillProductRespVO> products;
33 23  
... ... @@ -52,4 +42,16 @@ public class SeckillActivityRespVO extends SeckillActivityBaseVO {
52 42 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
53 43 private LocalDateTime createTime;
54 44  
  45 + // ========== 商品字段 ==========
  46 +
  47 + @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 name 读取
  48 + example = "618大促")
  49 + private String spuName;
  50 + @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 picUrl 读取
  51 + example = "https://www.iocoder.cn/xx.png")
  52 + private String picUrl;
  53 + @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 marketPrice 读取
  54 + example = "50")
  55 + private Integer marketPrice;
  56 +
55 57 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.controller.app.activity;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.map.MapUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 7 import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO;
7 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
... ... @@ -21,12 +22,10 @@ import org.springframework.web.bind.annotation.RequestParam;
21 22 import org.springframework.web.bind.annotation.RestController;
22 23  
23 24 import javax.annotation.Resource;
24   -import java.util.ArrayList;
25   -import java.util.HashMap;
26   -import java.util.List;
27   -import java.util.Map;
  25 +import java.util.*;
28 26  
29 27 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
30 29  
31 30 @Tag(name = "用户 APP - 营销活动") // 用于提供跨多个活动的 HTTP 接口
32 31 @RestController
... ... @@ -42,58 +41,60 @@ public class AppActivityController {
42 41 private BargainActivityService bargainActivityService;
43 42  
44 43 @GetMapping("/list-by-spu-id")
45   - @Operation(summary = "获得单个商品,近期参与的每个活动") // 每种活动,只返回一个
  44 + @Operation(summary = "获得单个商品,近期参与的每个活动")
46 45 @Parameter(name = "spuId", description = "商品编号", required = true)
47 46 public CommonResult<List<AppActivityRespVO>> getActivityListBySpuId(@RequestParam("spuId") Long spuId) {
48   - return success(getAppActivityRespVOList(spuId));
  47 + // 每种活动,只返回一个
  48 + return success(getAppActivityRespVOList(Collections.singletonList(spuId)));
49 49 }
50 50  
51 51 @GetMapping("/list-by-spu-ids")
52   - @Operation(summary = "获得多个商品,近期参与的每个活动") // 每种活动,只返回一个;key 为 SPU 编号
  52 + @Operation(summary = "获得多个商品,近期参与的每个活动")
53 53 @Parameter(name = "spuIds", description = "商品编号数组", required = true)
54 54 public CommonResult<Map<Long, List<AppActivityRespVO>>> getActivityListBySpuIds(@RequestParam("spuIds") List<Long> spuIds) {
55   -
56 55 if (CollUtil.isEmpty(spuIds)) {
57 56 return success(MapUtil.empty());
58 57 }
59   -
60   - Map<Long, List<AppActivityRespVO>> map = new HashMap<>(spuIds.size());
61   - spuIds.forEach(spuId -> {
62   - map.put(spuId, getAppActivityRespVOList(spuId));
63   - });
64   - return success(map);
  58 + // 每种活动,只返回一个;key 为 SPU 编号
  59 + return success(convertMultiMap(getAppActivityRespVOList(spuIds), AppActivityRespVO::getSpuId));
65 60 }
66 61  
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()));
  62 + private List<AppActivityRespVO> getAppActivityRespVOList(Collection<Long> spuIds) {
  63 + if (CollUtil.isEmpty(spuIds)) {
  64 + return new ArrayList<>();
  65 + }
  66 + List<AppActivityRespVO> activityList = new ArrayList<>();
  67 + // 拼团活动
  68 + List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityBySpuIdsAndStatus(
  69 + spuIds, CommonStatusEnum.ENABLE.getStatus());
  70 + if (CollUtil.isNotEmpty(combinationActivities)) {
  71 + combinationActivities.forEach(item -> {
  72 + activityList.add(new AppActivityRespVO().setId(item.getId())
  73 + .setType(PromotionTypeEnum.COMBINATION_ACTIVITY.getType()).setName(item.getName())
  74 + .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
  75 + });
77 76 }
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()));
  77 + // 秒杀活动
  78 + List<SeckillActivityDO> seckillActivities = seckillActivityService.getSeckillActivityBySpuIdsAndStatus(
  79 + spuIds, CommonStatusEnum.ENABLE.getStatus());
  80 + if (CollUtil.isNotEmpty(seckillActivities)) {
  81 + seckillActivities.forEach(item -> {
  82 + activityList.add(new AppActivityRespVO().setId(item.getId())
  83 + .setType(PromotionTypeEnum.SECKILL_ACTIVITY.getType()).setName(item.getName())
  84 + .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
  85 + });
86 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()));
  87 + // 砍价活动
  88 + List<BargainActivityDO> bargainActivities = bargainActivityService.getBargainActivityBySpuIdsAndStatus(
  89 + spuIds, CommonStatusEnum.ENABLE.getStatus());
  90 + if (CollUtil.isNotEmpty(bargainActivities)) {
  91 + bargainActivities.forEach(item -> {
  92 + activityList.add(new AppActivityRespVO().setId(item.getId())
  93 + .setType(PromotionTypeEnum.BARGAIN_ACTIVITY.getType()).setName(item.getName())
  94 + .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
  95 + });
95 96 }
96   - return respList;
  97 + return activityList;
97 98 }
98 99  
99 100 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/vo/AppActivityRespVO.java
... ... @@ -13,12 +13,14 @@ public class AppActivityRespVO {
13 13 private Long id;
14 14  
15 15 @Schema(description = "活动类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
16   - // 对应 PromotionTypeEnum 枚举
17   - private Integer type;
  16 + private Integer type; // 对应 PromotionTypeEnum 枚举
18 17  
19 18 @Schema(description = "活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618 大促")
20 19 private String name;
21 20  
  21 + @Schema(description = "spu 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "618")
  22 + private Long spuId;
  23 +
22 24 @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
23 25 private LocalDateTime startTime;
24 26  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationActivityController.java
... ... @@ -30,7 +30,6 @@ import java.time.Duration;
30 30 import java.util.Collections;
31 31 import java.util.List;
32 32  
33   -import static cn.hutool.core.util.ObjectUtil.defaultIfNull;
34 33 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
35 34 import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
36 35 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
... ... @@ -56,6 +55,7 @@ public class AppCombinationActivityController {
56 55  
57 56 @Resource
58 57 private CombinationActivityService activityService;
  58 +
59 59 @Resource
60 60 private ProductSpuApi spuApi;
61 61  
... ... @@ -68,40 +68,45 @@ public class AppCombinationActivityController {
68 68 }
69 69  
70 70 private List<AppCombinationActivityRespVO> getCombinationActivityList0(Integer count) {
71   - List<CombinationActivityDO> list = activityService.getCombinationActivityListByCount(defaultIfNull(count, 6));
72   - if (CollUtil.isEmpty(list)) {
  71 + List<CombinationActivityDO> activityList = activityService.getCombinationActivityListByCount(count);
  72 + if (CollUtil.isEmpty(activityList)) {
73 73 return Collections.emptyList();
74 74 }
75 75 // 拼接返回
76   - List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(list, CombinationActivityDO::getSpuId));
77   - return CombinationActivityConvert.INSTANCE.convertAppList(list, spuList);
  76 + List<CombinationProductDO> productList = activityService.getCombinationProductListByActivityIds(
  77 + convertList(activityList, CombinationActivityDO::getId));
  78 + List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, CombinationActivityDO::getSpuId));
  79 + return CombinationActivityConvert.INSTANCE.convertAppList(activityList, productList, spuList);
78 80 }
79 81  
80 82 @GetMapping("/page")
81 83 @Operation(summary = "获得拼团活动分页")
82 84 public CommonResult<PageResult<AppCombinationActivityRespVO>> getCombinationActivityPage(PageParam pageParam) {
83   - PageResult<CombinationActivityDO> result = activityService.getCombinationActivityPage(pageParam);
84   - if (CollUtil.isEmpty(result.getList())) {
85   - return success(PageResult.empty(result.getTotal()));
  85 + PageResult<CombinationActivityDO> pageResult = activityService.getCombinationActivityPage(pageParam);
  86 + if (CollUtil.isEmpty(pageResult.getList())) {
  87 + return success(PageResult.empty(pageResult.getTotal()));
86 88 }
87 89 // 拼接返回
88   - List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(result.getList(), CombinationActivityDO::getSpuId));
89   - return success(CombinationActivityConvert.INSTANCE.convertAppPage(result, spuList));
  90 + List<CombinationProductDO> productList = activityService.getCombinationProductListByActivityIds(
  91 + convertList(pageResult.getList(), CombinationActivityDO::getId));
  92 + List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(pageResult.getList(), CombinationActivityDO::getSpuId));
  93 + return success(CombinationActivityConvert.INSTANCE.convertAppPage(pageResult, productList, spuList));
90 94 }
91 95  
92 96 @GetMapping("/get-detail")
93 97 @Operation(summary = "获得拼团活动明细")
94 98 @Parameter(name = "id", description = "活动编号", required = true, example = "1024")
95 99 public CommonResult<AppCombinationActivityDetailRespVO> getCombinationActivityDetail(@RequestParam("id") Long id) {
96   - // 1、获取活动
97   - CombinationActivityDO combinationActivity = activityService.getCombinationActivity(id);
98   - if (combinationActivity == null
99   - || ObjectUtil.equal(combinationActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
  100 + // 1. 获取活动
  101 + CombinationActivityDO activity = activityService.getCombinationActivity(id);
  102 + if (activity == null
  103 + || ObjectUtil.equal(activity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
100 104 return success(null);
101 105 }
102   - // 2、获取活动商品
103   - List<CombinationProductDO> products = activityService.getCombinationProductsByActivityId(combinationActivity.getId());
104   - return success(CombinationActivityConvert.INSTANCE.convert3(combinationActivity, products));
  106 +
  107 + // 2. 获取活动商品
  108 + List<CombinationProductDO> products = activityService.getCombinationProductsByActivityId(activity.getId());
  109 + return success(CombinationActivityConvert.INSTANCE.convert3(activity, products));
105 110 }
106 111  
107 112 }
... ...
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;
4 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 4 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordDetailRespVO;
7 5 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordRespVO;
8 6 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordSummaryRespVO;
9 7 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
10 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
  9 +import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
11 10 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
  11 +import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
12 12 import io.swagger.v3.oas.annotations.Operation;
13 13 import io.swagger.v3.oas.annotations.Parameter;
14 14 import io.swagger.v3.oas.annotations.Parameters;
15 15 import io.swagger.v3.oas.annotations.tags.Tag;
  16 +import org.springframework.context.annotation.Lazy;
16 17 import org.springframework.validation.annotation.Validated;
17 18 import org.springframework.web.bind.annotation.GetMapping;
18 19 import org.springframework.web.bind.annotation.RequestMapping;
... ... @@ -23,6 +24,7 @@ import javax.annotation.Resource;
23 24 import javax.validation.constraints.Max;
24 25 import java.util.Collections;
25 26 import java.util.List;
  27 +import java.util.Objects;
26 28  
27 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
28 30 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
... ... @@ -36,24 +38,27 @@ public class AppCombinationRecordController {
36 38  
37 39 @Resource
38 40 private CombinationRecordService combinationRecordService;
  41 + @Resource
  42 + @Lazy
  43 + private TradeOrderApi tradeOrderApi;
39 44  
40 45 @GetMapping("/get-summary")
41 46 @Operation(summary = "获得拼团记录的概要信息", description = "用于小程序首页")
42   - // TODO 芋艿:增加 @Cache 缓存,1 分钟过期
43 47 public CommonResult<AppCombinationRecordSummaryRespVO> getCombinationRecordSummary() {
44   - // 获取所有拼团记录
45   - Long count = combinationRecordService.getRecordsCount();
46 48 AppCombinationRecordSummaryRespVO summary = new AppCombinationRecordSummaryRespVO();
47   - if (count == null || count == 0L) {
  49 + // 1. 获得拼团记录数量
  50 + Long count = combinationRecordService.getCombinationRecordCount(null, null);
  51 + if (count == 0) {
48 52 summary.setAvatars(Collections.emptyList());
49 53 summary.setUserCount(count);
50 54 return success(summary);
51 55 }
52   -
53 56 summary.setUserCount(count);
54   - // TODO 只返回最近的 7 个
55   - int num = 7;
56   - summary.setAvatars(convertList(combinationRecordService.getLatestRecordList(num), CombinationRecordDO::getAvatar));
  57 +
  58 + // 2. 获得拼团记录头像
  59 + List<CombinationRecordDO> records = combinationRecordService.getLatestCombinationRecordList(
  60 + AppCombinationRecordSummaryRespVO.AVATAR_COUNT);
  61 + summary.setAvatars(convertList(records, CombinationRecordDO::getAvatar));
57 62 return success(summary);
58 63 }
59 64  
... ... @@ -61,7 +66,7 @@ public class AppCombinationRecordController {
61 66 @Operation(summary = "获得最近 n 条拼团记录(团长发起的)")
62 67 @Parameters({
63 68 @Parameter(name = "activityId", description = "拼团活动编号"),
64   - @Parameter(name = "status", description = "状态"),
  69 + @Parameter(name = "status", description = "拼团状态"), // 对应 CombinationRecordStatusEnum 枚举
65 70 @Parameter(name = "count", description = "数量")
66 71 })
67 72 public CommonResult<List<AppCombinationRecordRespVO>> getHeadCombinationRecordList(
... ... @@ -69,44 +74,54 @@ public class AppCombinationRecordController {
69 74 @RequestParam("status") Integer status,
70 75 @RequestParam(value = "count", defaultValue = "20") @Max(20) Integer count) {
71 76 return success(CombinationActivityConvert.INSTANCE.convertList3(
72   - combinationRecordService.getRecordListWithHead(activityId, status, count)));
  77 + combinationRecordService.getHeadCombinationRecordList(activityId, status, count)));
73 78 }
74 79  
75 80 @GetMapping("/get-detail")
76 81 @Operation(summary = "获得拼团记录明细")
77 82 @Parameter(name = "id", description = "拼团记录编号", required = true, example = "1024")
78 83 public CommonResult<AppCombinationRecordDetailRespVO> getCombinationRecordDetail(@RequestParam("id") Long id) {
79   - // 1、查询这条记录
80   - CombinationRecordDO record = combinationRecordService.getRecordById(id);
  84 + // 1. 查找这条拼团记录
  85 + CombinationRecordDO record = combinationRecordService.getCombinationRecordById(id);
81 86 if (record == null) {
82 87 return success(null);
83 88 }
84 89  
85   - AppCombinationRecordDetailRespVO detail = new AppCombinationRecordDetailRespVO();
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   - }
  90 + // 2. 查找该拼团的参团记录
  91 + CombinationRecordDO headRecord;
  92 + List<CombinationRecordDO> memberRecords;
  93 + if (Objects.equals(record.getHeadId(), CombinationRecordDO.HEAD_ID_GROUP)) { // 情况一:团长
  94 + headRecord = record;
  95 + memberRecords = combinationRecordService.getCombinationRecordListByHeadId(record.getId());
  96 + } else { // 情况二:团员
  97 + headRecord = combinationRecordService.getCombinationRecordById(record.getHeadId());
  98 + memberRecords = combinationRecordService.getCombinationRecordListByHeadId(headRecord.getId());
  99 + }
98 100  
99   - detail.setHeadRecord(CombinationActivityConvert.INSTANCE.convert(headRecord));
100   - // 2.3、查找团员拼团记录
101   - records = combinationRecordService.getRecordListByHeadId(headRecord.getId());
  101 + // 3. 拼接数据
  102 + return success(CombinationActivityConvert.INSTANCE.convert(getLoginUserId(), headRecord, memberRecords));
  103 + }
102 104  
  105 + @GetMapping("/cancel")
  106 + @Operation(summary = "取消拼团")
  107 + @Parameter(name = "id", description = "拼团记录编号", required = true, example = "1024")
  108 + public CommonResult<Boolean> cancelCombinationRecord(@RequestParam("id") Long id) {
  109 + Long userId = getLoginUserId();
  110 + // 1、查找这条拼团记录
  111 + CombinationRecordDO record = combinationRecordService.getCombinationRecordByIdAndUser(userId, id);
  112 + if (record == null) {
  113 + return success(Boolean.FALSE);
  114 + }
  115 + // 1.1、需要先校验拼团记录未完成;
  116 + if (!CombinationRecordStatusEnum.isInProgress(record.getStatus())) {
  117 + return success(Boolean.FALSE);
103 118 }
104   - detail.setMemberRecords(CombinationActivityConvert.INSTANCE.convertList3(records));
105 119  
106   - // 3、获取当前用户参团记录订单编号
107   - CombinationRecordDO userRecord = CollectionUtils.findFirst(records, r -> ObjectUtil.equal(r.getUserId(), getLoginUserId()));
108   - detail.setOrderId(userRecord == null ? null : userRecord.getOrderId()); // 如果没参团,返回 null
109   - return success(detail);
  120 + // 2. 取消已支付的订单
  121 + tradeOrderApi.cancelPaidOrder(userId, record.getOrderId());
  122 + // 3. 取消拼团记录
  123 + combinationRecordService.cancelCombinationRecord(userId, record.getId(), record.getHeadId());
  124 + return success(Boolean.TRUE);
110 125 }
111 126  
112 127 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/vo/record/AppCombinationRecordSummaryRespVO.java
... ... @@ -9,6 +9,11 @@ import java.util.List;
9 9 @Data
10 10 public class AppCombinationRecordSummaryRespVO {
11 11  
  12 + /**
  13 + * 加载 {@link #avatars} 的数量
  14 + */
  15 + public static final Integer AVATAR_COUNT = 7;
  16 +
12 17 @Schema(description = "拼团用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13 18 private Long userCount;
14 19  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillActivityController.java
... ... @@ -86,7 +86,8 @@ public class AppSeckillActivityController {
86 86  
87 87 // 2.1 查询满足当前阶段的活动
88 88 List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByConfigIdAndStatus(config.getId(), CommonStatusEnum.ENABLE.getStatus());
89   - List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(convertList(activityList, SeckillActivityDO::getId));
  89 + List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(
  90 + convertList(activityList, SeckillActivityDO::getId));
90 91 // 2.2 获取 spu 信息
91 92 List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, SeckillActivityDO::getSpuId));
92 93 return SeckillActivityConvert.INSTANCE.convert(config, activityList, productList, spuList);
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java
1 1 package cn.iocoder.yudao.module.promotion.convert.combination;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
... ... @@ -8,25 +9,30 @@ import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
8 9 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
9 10 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
10 11 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
  12 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageItemRespVO;
11 13 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityRespVO;
12 14 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
13 15 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductBaseVO;
14 16 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductRespVO;
  17 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordPageItemRespVO;
15 18 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.activity.AppCombinationActivityDetailRespVO;
16 19 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.activity.AppCombinationActivityRespVO;
  20 +import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordDetailRespVO;
17 21 import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordRespVO;
18 22 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
19 23 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
20 24 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
  25 +import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
21 26 import org.mapstruct.Mapper;
22 27 import org.mapstruct.Mapping;
23 28 import org.mapstruct.Mappings;
24 29 import org.mapstruct.factory.Mappers;
25 30  
  31 +import java.time.LocalDateTime;
26 32 import java.util.List;
27 33 import java.util.Map;
28 34  
29   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
30 36 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
31 37  
32 38 /**
... ... @@ -53,20 +59,29 @@ public interface CombinationActivityConvert {
53 59  
54 60 List<CombinationActivityRespVO> convertList(List<CombinationActivityDO> list);
55 61  
56   - PageResult<CombinationActivityRespVO> convertPage(PageResult<CombinationActivityDO> page);
57 62  
58   - default PageResult<CombinationActivityRespVO> convertPage(PageResult<CombinationActivityDO> page,
59   - List<CombinationProductDO> productList,
60   - List<ProductSpuRespDTO> spuList) {
  63 + default PageResult<CombinationActivityPageItemRespVO> convertPage(PageResult<CombinationActivityDO> page,
  64 + List<CombinationProductDO> productList,
  65 + Map<Long, Integer> groupCountMap,
  66 + Map<Long, Integer> groupSuccessCountMap,
  67 + Map<Long, Integer> recordCountMap,
  68 + List<ProductSpuRespDTO> spuList) {
  69 + PageResult<CombinationActivityPageItemRespVO> pageResult = convertPage(page);
61 70 Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
62   - PageResult<CombinationActivityRespVO> pageResult = convertPage(page);
63 71 pageResult.getList().forEach(item -> {
64   - MapUtils.findAndThen(spuMap, item.getSpuId(), spu -> item.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()));
  72 + MapUtils.findAndThen(spuMap, item.getSpuId(), spu -> item.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl())
  73 + .setMarketPrice(spu.getMarketPrice()));
65 74 item.setProducts(convertList2(productList));
  75 + // 设置统计字段
  76 + item.setGroupCount(groupCountMap.getOrDefault(item.getId(), 0))
  77 + .setGroupSuccessCount(groupSuccessCountMap.getOrDefault(item.getId(), 0))
  78 + .setRecordCount(recordCountMap.getOrDefault(item.getId(), 0));
66 79 });
67 80 return pageResult;
68 81 }
69 82  
  83 + PageResult<CombinationActivityPageItemRespVO> convertPage(PageResult<CombinationActivityDO> page);
  84 +
70 85 List<CombinationProductRespVO> convertList2(List<CombinationProductDO> productDOs);
71 86  
72 87 @Mappings({
... ... @@ -97,23 +112,31 @@ public interface CombinationActivityConvert {
97 112 default CombinationRecordDO convert(CombinationRecordCreateReqDTO reqDTO,
98 113 CombinationActivityDO activity, MemberUserRespDTO user,
99 114 ProductSpuRespDTO spu, ProductSkuRespDTO sku) {
100   - return convert(reqDTO)
101   - .setCount(reqDTO.getCount())
102   - .setVirtualGroup(false)
  115 + return convert(reqDTO).setVirtualGroup(false)
  116 + .setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()) // 创建后默认状态为进行中
  117 + .setStartTime(LocalDateTime.now()) // TODO @puhui999:想了下,这个 startTime 应该是团长的;
  118 + // TODO @puhui999:有团长的情况下,expireTime 应该是团长的;
103 119 .setExpireTime(activity.getStartTime().plusHours(activity.getLimitDuration()))
104   - .setUserSize(activity.getUserSize())
105   - .setNickname(user.getNickname())
106   - .setAvatar(user.getAvatar())
107   - .setSpuName(spu.getName())
108   - .setPicUrl(sku.getPicUrl());
  120 + .setUserSize(activity.getUserSize()).setUserCount(1) // 默认就是 1 插入后会接着更新一次所有的拼团记录
  121 + // 用户信息
  122 + .setNickname(user.getNickname()).setAvatar(user.getAvatar())
  123 + // 商品信息
  124 + .setSpuName(spu.getName()).setPicUrl(sku.getPicUrl());
  125 +
109 126 }
110 127  
111 128 List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list);
112 129  
113   - default List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list, List<ProductSpuRespDTO> spuList) {
  130 + default List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list,
  131 + List<CombinationProductDO> productList,
  132 + List<ProductSpuRespDTO> spuList) {
114 133 List<AppCombinationActivityRespVO> activityList = convertAppList(list);
115 134 Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  135 + Map<Long, List<CombinationProductDO>> productMap = convertMultiMap(productList, CombinationProductDO::getActivityId);
116 136 return CollectionUtils.convertList(activityList, item -> {
  137 + // 设置 product 信息
  138 + item.setCombinationPrice(getMinValue(productMap.get(item.getId()), CombinationProductDO::getCombinationPrice));
  139 + // 设置 SPU 信息
117 140 findAndThen(spuMap, item.getSpuId(), spu -> item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
118 141 return item;
119 142 });
... ... @@ -121,13 +144,17 @@ public interface CombinationActivityConvert {
121 144  
122 145 PageResult<AppCombinationActivityRespVO> convertAppPage(PageResult<CombinationActivityDO> result);
123 146  
124   - default PageResult<AppCombinationActivityRespVO> convertAppPage(PageResult<CombinationActivityDO> result, List<ProductSpuRespDTO> spuList) {
  147 + default PageResult<AppCombinationActivityRespVO> convertAppPage(PageResult<CombinationActivityDO> result,
  148 + List<CombinationProductDO> productList,
  149 + List<ProductSpuRespDTO> spuList) {
125 150 PageResult<AppCombinationActivityRespVO> appPage = convertAppPage(result);
126 151 Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  152 + Map<Long, List<CombinationProductDO>> productMap = convertMultiMap(productList, CombinationProductDO::getActivityId);
127 153 List<AppCombinationActivityRespVO> list = CollectionUtils.convertList(appPage.getList(), item -> {
128   - findAndThen(spuMap, item.getSpuId(), spu -> {
129   - item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice());
130   - });
  154 + // 设置 product 信息
  155 + item.setCombinationPrice(getMinValue(productMap.get(item.getId()), CombinationProductDO::getCombinationPrice));
  156 + // 设置 SPU 信息
  157 + findAndThen(spuMap, item.getSpuId(), spu -> item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
131 158 return item;
132 159 });
133 160 appPage.setList(list);
... ... @@ -146,4 +173,31 @@ public interface CombinationActivityConvert {
146 173  
147 174 AppCombinationRecordRespVO convert(CombinationRecordDO record);
148 175  
  176 + PageResult<CombinationRecordPageItemRespVO> convert(PageResult<CombinationRecordDO> result);
  177 +
  178 + default PageResult<CombinationRecordPageItemRespVO> convert(PageResult<CombinationRecordDO> recordPage, List<CombinationActivityDO> activities) {
  179 + PageResult<CombinationRecordPageItemRespVO> result = convert(recordPage);
  180 + Map<Long, CombinationActivityDO> activityMap = convertMap(activities, CombinationActivityDO::getId);
  181 + result.setList(CollectionUtils.convertList(result.getList(), item -> {
  182 + findAndThen(activityMap, item.getActivityId(), activity -> {
  183 + item.setActivity(convert(activity));
  184 + });
  185 + return item;
  186 + }));
  187 + return result;
  188 + }
  189 +
  190 +
  191 + default AppCombinationRecordDetailRespVO convert(Long userId, CombinationRecordDO headRecord, List<CombinationRecordDO> memberRecords) {
  192 + AppCombinationRecordDetailRespVO respVO = new AppCombinationRecordDetailRespVO()
  193 + .setHeadRecord(convert(headRecord)).setMemberRecords(convertList3(memberRecords));
  194 + // 处理自己参与拼团的 orderId
  195 + CombinationRecordDO userRecord = CollectionUtils.findFirst(memberRecords, r -> ObjectUtil.equal(r.getUserId(), userId));
  196 + if (userRecord == null && ObjectUtil.equal(headRecord.getUserId(), userId)) {
  197 + userRecord = headRecord;
  198 + }
  199 + respVO.setOrderId(userRecord == null ? null : userRecord.getOrderId());
  200 + return respVO;
  201 + }
  202 +
149 203 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationActivityDO.java
... ... @@ -60,21 +60,9 @@ public class CombinationActivityDO extends BaseDO {
60 60 */
61 61 private Integer userSize;
62 62 /**
63   - * 开团组数
64   - */
65   - private Integer totalCount;
66   - /**
67   - * 成团组数
68   - */
69   - private Integer successCount;
70   - /**
71   - * 参与人数
72   - */
73   - private Integer orderUserCount;
74   - /**
75 63 * 虚拟成团
76 64 */
77   - private Integer virtualGroup;
  65 + private Boolean virtualGroup;
78 66 /**
79 67 * 活动状态
80 68 *
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationRecordDO.java
... ... @@ -29,6 +29,11 @@ import java.time.LocalDateTime;
29 29 public class CombinationRecordDO extends BaseDO {
30 30  
31 31 /**
  32 + * 团长编号 - 团长
  33 + */
  34 + public static final Long HEAD_ID_GROUP = 0L;
  35 +
  36 + /**
32 37 * 编号,主键自增
33 38 */
34 39 @TableId
... ... @@ -71,6 +76,7 @@ public class CombinationRecordDO extends BaseDO {
71 76 * 用户编号
72 77 */
73 78 private Long userId;
  79 +
74 80 /**
75 81 * 用户昵称
76 82 */
... ... @@ -84,6 +90,8 @@ public class CombinationRecordDO extends BaseDO {
84 90 * 团长编号
85 91 *
86 92 * 关联 {@link CombinationRecordDO#getId()}
  93 + *
  94 + * 如果是团长,则它的值是 {@link #HEAD_ID_GROUP}
87 95 */
88 96 private Long headId;
89 97 /**
... ... @@ -108,6 +116,9 @@ public class CombinationRecordDO extends BaseDO {
108 116 private Integer userCount;
109 117 /**
110 118 * 是否虚拟成团
  119 + *
  120 + * 默认为 false。
  121 + * 拼团过期都还没有成功,如果 {@link CombinationActivityDO#getVirtualGroup()} 为 true,则执行虚拟成团的逻辑,才会更新该字段为 true
111 122 */
112 123 private Boolean virtualGroup;
113 124  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java
... ... @@ -8,8 +8,10 @@ import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.activity.Ba
8 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
9 9 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
10 10 import org.apache.ibatis.annotations.Mapper;
  11 +import org.apache.ibatis.annotations.Select;
11 12  
12 13 import java.time.LocalDateTime;
  14 +import java.util.Collection;
13 15 import java.util.List;
14 16  
15 17 /**
... ... @@ -83,11 +85,25 @@ public interface BargainActivityMapper extends BaseMapperX&lt;BargainActivityDO&gt; {
83 85 .last("LIMIT " + count));
84 86 }
85 87  
86   - default BargainActivityDO selectOne(Long spuId) {
87   - return selectOne(new LambdaQueryWrapperX<BargainActivityDO>()
88   - .eq(BargainActivityDO::getSpuId, spuId)
89   - .orderByDesc(BargainActivityDO::getCreateTime)
90   - , false);
91   - }
  88 + // TODO @puhui999:一个商品,在统一时间,不会参与多个活动;so 是不是不用 inner join 哈?
  89 + // PS:如果可以参与多个,其实可以这样写 select * from promotion_bargain_activity group by spu_id ORDER BY create_time DESC;通过 group 来过滤
  90 + /**
  91 + * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
  92 + *
  93 + * @param spuIds spu 编号
  94 + * @param status 状态
  95 + * @return 砍价活动列表
  96 + */
  97 + @Select("SELECT p1.* " +
  98 + "FROM promotion_bargain_activity p1 " +
  99 + "INNER JOIN ( " +
  100 + " SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +
  101 + " FROM promotion_bargain_activity " +
  102 + " WHERE spu_id IN #{spuIds} " +
  103 + " GROUP BY spu_id " +
  104 + ") p2 " +
  105 + "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +
  106 + "ORDER BY p1.create_time DESC;")
  107 + List<BargainActivityDO> selectListBySpuIds(Collection<Long> spuIds, Integer status);
92 108  
93 109 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java
... ... @@ -7,7 +7,10 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 7 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
8 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
9 9 import org.apache.ibatis.annotations.Mapper;
  10 +import org.apache.ibatis.annotations.Param;
  11 +import org.apache.ibatis.annotations.Select;
10 12  
  13 +import java.util.Collection;
11 14 import java.util.List;
12 15  
13 16 /**
... ... @@ -40,11 +43,24 @@ public interface CombinationActivityMapper extends BaseMapperX&lt;CombinationActivi
40 43 .last("LIMIT " + count));
41 44 }
42 45  
43   - default CombinationActivityDO selectOne(Long spuId) {
44   - return selectOne(new LambdaQueryWrapperX<CombinationActivityDO>()
45   - .eq(CombinationActivityDO::getSpuId, spuId)
46   - .orderByDesc(CombinationActivityDO::getCreateTime)
47   - , false);
48   - }
  46 + // TODO @puhui999:类似 BargainActivityMapper
  47 + /**
  48 + * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
  49 + *
  50 + * @param spuIds spu 编号
  51 + * @param status 状态
  52 + * @return 拼团活动列表
  53 + */
  54 + @Select("SELECT p1.* " +
  55 + "FROM promotion_combination_activity p1 " +
  56 + "INNER JOIN ( " +
  57 + " SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +
  58 + " FROM promotion_combination_activity " +
  59 + " WHERE spu_id IN #{spuIds} " +
  60 + " GROUP BY spu_id " +
  61 + ") p2 " +
  62 + "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +
  63 + "ORDER BY p1.create_time DESC;")
  64 + List<CombinationActivityDO> selectListBySpuIds(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status);
49 65  
50 66 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationRecordMapper.java
1 1 package cn.iocoder.yudao.module.promotion.dal.mysql.combination;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
3 7 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 8 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  9 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
5 10 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
  11 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
6 12 import org.apache.ibatis.annotations.Mapper;
7 13  
  14 +import java.util.Collection;
  15 +import java.util.Collections;
8 16 import java.util.List;
  17 +import java.util.Map;
9 18  
10 19 /**
11 20 * 拼团记录 Mapper
... ... @@ -20,34 +29,18 @@ public interface CombinationRecordMapper extends BaseMapperX&lt;CombinationRecordDO
20 29 CombinationRecordDO::getOrderId, orderId);
21 30 }
22 31  
23   - default List<CombinationRecordDO> selectListByUserId(Long userId) {
24   - return selectList(CombinationRecordDO::getUserId, userId);
25   - }
26   -
27   - default List<CombinationRecordDO> selectListByUserIdAndStatus(Long userId, Integer status) {
28   - return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
29   - .eq(CombinationRecordDO::getUserId, userId)
30   - .eq(CombinationRecordDO::getStatus, status));
31   - }
32   -
33 32 /**
34 33 * 查询拼团记录
35 34 *
36 35 * @param headId 团长编号
37 36 * @return 拼团记录
38 37 */
39   - default CombinationRecordDO selectOneByHeadId(Long headId, Integer status) {
  38 + default CombinationRecordDO selectByHeadId(Long headId, Integer status) {
40 39 return selectOne(new LambdaQueryWrapperX<CombinationRecordDO>()
41 40 .eq(CombinationRecordDO::getId, headId)
42 41 .eq(CombinationRecordDO::getStatus, status));
43 42 }
44 43  
45   - default List<CombinationRecordDO> selectListByHeadIdAndStatus(Long headId, Integer status) {
46   - return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
47   - .eq(CombinationRecordDO::getHeadId, headId)
48   - .eq(CombinationRecordDO::getStatus, status));
49   - }
50   -
51 44 /**
52 45 * 查询拼团记录
53 46 *
... ... @@ -69,25 +62,51 @@ public interface CombinationRecordMapper extends BaseMapperX&lt;CombinationRecordDO
69 62 */
70 63 default List<CombinationRecordDO> selectLatestList(int count) {
71 64 return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
72   - .orderByDesc(CombinationRecordDO::getCreateTime)
  65 + .orderByDesc(CombinationRecordDO::getId)
73 66 .last("LIMIT " + count));
74 67 }
75 68  
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) {
  69 + default List<CombinationRecordDO> selectListByActivityIdAndStatusAndHeadId(Long activityId, Integer status,
  70 + Long headId, Integer count) {
85 71 return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
86 72 .eqIfPresent(CombinationRecordDO::getActivityId, activityId)
87 73 .eqIfPresent(CombinationRecordDO::getStatus, status)
88   - .eq(CombinationRecordDO::getHeadId, null) // TODO 团长的 headId 是不是 null 还是自己的记录编号来着?
89   - .orderByDesc(CombinationRecordDO::getCreateTime)
  74 + .eq(CombinationRecordDO::getHeadId, headId)
  75 + .orderByDesc(CombinationRecordDO::getId)
90 76 .last("LIMIT " + count));
91 77 }
92 78  
  79 + default Map<Long, Integer> selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(Collection<Long> activityIds,
  80 + Integer status, Long headId) {
  81 + // SQL count 查询
  82 + List<Map<String, Object>> result = selectMaps(new QueryWrapper<CombinationRecordDO>()
  83 + .select("COUNT(DISTINCT(user_id)) AS recordCount, activity_id AS activityId")
  84 + .in("activity_id", activityIds)
  85 + .eq(status != null, "status", status)
  86 + .eq(headId != null, "head_id", headId)
  87 + .groupBy("activity_id"));
  88 + if (CollUtil.isEmpty(result)) {
  89 + return Collections.emptyMap();
  90 + }
  91 + // 转换数据
  92 + return CollectionUtils.convertMap(result,
  93 + record -> MapUtil.getLong(record, "activityId"),
  94 + record -> MapUtil.getInt(record, "recordCount"));
  95 + }
  96 +
  97 + default PageResult<CombinationRecordDO> selectPage(CombinationRecordReqPageVO pageVO) {
  98 + return selectPage(pageVO, new LambdaQueryWrapperX<CombinationRecordDO>()
  99 + .eqIfPresent(CombinationRecordDO::getStatus, pageVO.getStatus())
  100 + .betweenIfPresent(CombinationRecordDO::getCreateTime, pageVO.getCreateTime()));
  101 + }
  102 +
  103 + // TODO @puhui999:这个最好把 headId 也作为一个参数;因为有个要求 userCount,它要 DISTINCT 下;整体可以参考 selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId
  104 + default Long selectCountByHeadAndStatusAndVirtualGroup(Integer status, Boolean virtualGroup) {
  105 + return selectCount(new LambdaQueryWrapperX<CombinationRecordDO>()
  106 + .eq(status != null || virtualGroup != null,
  107 + CombinationRecordDO::getHeadId, CombinationRecordDO.HEAD_ID_GROUP) // 统计团信息则指定团长
  108 + .eqIfPresent(CombinationRecordDO::getStatus, status)
  109 + .eqIfPresent(CombinationRecordDO::getVirtualGroup, virtualGroup));
  110 + }
  111 +
93 112 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
... ... @@ -9,7 +9,9 @@ import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppS
9 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
10 10 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
11 11 import org.apache.ibatis.annotations.Mapper;
  12 +import org.apache.ibatis.annotations.Select;
12 13  
  14 +import java.util.Collection;
13 15 import java.util.List;
14 16  
15 17 /**
... ... @@ -56,11 +58,24 @@ public interface SeckillActivityMapper extends BaseMapperX&lt;SeckillActivityDO&gt; {
56 58 .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0"));
57 59 }
58 60  
59   - default SeckillActivityDO selectOne(Long spuId) {
60   - return selectOne(new LambdaQueryWrapperX<SeckillActivityDO>()
61   - .eq(SeckillActivityDO::getSpuId, spuId)
62   - .orderByDesc(SeckillActivityDO::getCreateTime)
63   - , false);
64   - }
  61 + // TODO @puhui999:类似 BargainActivityMapper
  62 + /**
  63 + * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
  64 + *
  65 + * @param spuIds spu 编号
  66 + * @param status 状态
  67 + * @return 秒杀活动列表
  68 + */
  69 + @Select("SELECT p1.* " +
  70 + "FROM promotion_seckill_activity p1 " +
  71 + "INNER JOIN ( " +
  72 + " SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +
  73 + " FROM promotion_seckill_activity " +
  74 + " WHERE spu_id IN #{spuIds} " +
  75 + " GROUP BY spu_id " +
  76 + ") p2 " +
  77 + "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +
  78 + "ORDER BY p1.create_time DESC;")
  79 + List<SeckillActivityDO> selectListBySpuIds(Collection<Long> spuIds, Integer status);
65 80  
66 81 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java
... ... @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.activity.Ba
8 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
9 9  
10 10 import javax.validation.Valid;
  11 +import java.util.Collection;
11 12 import java.util.List;
12 13 import java.util.Set;
13 14  
... ... @@ -99,11 +100,12 @@ public interface BargainActivityService {
99 100 List<BargainActivityDO> getBargainActivityListByCount(Integer count);
100 101  
101 102 /**
102   - * 获取指定 spu 编号的活动
  103 + * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
103 104 *
104   - * @param spuId spu 编号
105   - * @return 砍价活动
  105 + * @param spuIds spu 编号
  106 + * @param status 状态
  107 + * @return 砍价活动列表
106 108 */
107   - BargainActivityDO getBargainActivityBySpuId(Long spuId);
  109 + List<BargainActivityDO> getBargainActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status);
108 110  
109 111 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
... ... @@ -20,6 +20,7 @@ import org.springframework.validation.annotation.Validated;
20 20  
21 21 import javax.annotation.Resource;
22 22 import java.time.LocalDateTime;
  23 +import java.util.Collection;
23 24 import java.util.List;
24 25 import java.util.Set;
25 26  
... ... @@ -176,8 +177,8 @@ public class BargainActivityServiceImpl implements BargainActivityService {
176 177 }
177 178  
178 179 @Override
179   - public BargainActivityDO getBargainActivityBySpuId(Long spuId) {
180   - return bargainActivityMapper.selectOne(spuId);
  180 + public List<BargainActivityDO> getBargainActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
  181 + return bargainActivityMapper.selectListBySpuIds(spuIds, status);
181 182 }
182 183  
183 184 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java
... ... @@ -73,7 +73,7 @@ public interface CombinationActivityService {
73 73 * @return 拼团活动的商品列表
74 74 */
75 75 default List<CombinationProductDO> getCombinationProductsByActivityId(Long activityId) {
76   - return getCombinationProductsByActivityIds(Collections.singletonList(activityId));
  76 + return getCombinationProductListByActivityIds(Collections.singletonList(activityId));
77 77 }
78 78  
79 79 /**
... ... @@ -82,7 +82,15 @@ public interface CombinationActivityService {
82 82 * @param activityIds 拼团活动 ids
83 83 * @return 拼团活动的商品列表
84 84 */
85   - List<CombinationProductDO> getCombinationProductsByActivityIds(Collection<Long> activityIds);
  85 + List<CombinationProductDO> getCombinationProductListByActivityIds(Collection<Long> activityIds);
  86 +
  87 + /**
  88 + * 获得拼团活动列表
  89 + *
  90 + * @param ids 拼团活动 ids
  91 + * @return 拼团活动的列表
  92 + */
  93 + List<CombinationActivityDO> getCombinationActivityListByIds(Collection<Long> ids);
86 94  
87 95 /**
88 96 * 获取正在进行的活动分页数据
... ... @@ -101,7 +109,7 @@ public interface CombinationActivityService {
101 109 PageResult<CombinationActivityDO> getCombinationActivityPage(PageParam pageParam);
102 110  
103 111 /**
104   - * 获取指定活动指定 sku 编号的商品
  112 + * 获取指定活动、指定 sku 编号的商品
105 113 *
106 114 * @param activityId 活动编号
107 115 * @param skuId sku 编号
... ... @@ -110,11 +118,12 @@ public interface CombinationActivityService {
110 118 CombinationProductDO selectByActivityIdAndSkuId(Long activityId, Long skuId);
111 119  
112 120 /**
113   - * 获取指定 spu 编号的活动
  121 + * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
114 122 *
115   - * @param spuId spu 编号
116   - * @return 拼团活动
  123 + * @param spuIds spu 编号
  124 + * @param status 状态
  125 + * @return 拼团活动列表
117 126 */
118   - CombinationActivityDO getCombinationActivityBySpuId(Long spuId);
  127 + List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status);
119 128  
120 129 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java
... ... @@ -65,13 +65,11 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
65 65  
66 66 // 插入拼团活动
67 67 CombinationActivityDO activity = CombinationActivityConvert.INSTANCE.convert(createReqVO)
68   - .setStatus(CommonStatusEnum.ENABLE.getStatus())
69   - .setTotalCount(0).setSuccessCount(0).setOrderUserCount(0).setVirtualGroup(0);
  68 + .setStatus(CommonStatusEnum.ENABLE.getStatus());
70 69 combinationActivityMapper.insert(activity);
71 70 // 插入商品
72 71 List<CombinationProductDO> products = CombinationActivityConvert.INSTANCE.convertList(createReqVO.getProducts(), activity);
73 72 combinationProductMapper.insertBatch(products);
74   - // 返回
75 73 return activity.getId();
76 74 }
77 75  
... ... @@ -202,11 +200,16 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
202 200 }
203 201  
204 202 @Override
205   - public List<CombinationProductDO> getCombinationProductsByActivityIds(Collection<Long> activityIds) {
  203 + public List<CombinationProductDO> getCombinationProductListByActivityIds(Collection<Long> activityIds) {
206 204 return combinationProductMapper.selectListByActivityIds(activityIds);
207 205 }
208 206  
209 207 @Override
  208 + public List<CombinationActivityDO> getCombinationActivityListByIds(Collection<Long> ids) {
  209 + return combinationActivityMapper.selectList(CombinationActivityDO::getId, ids);
  210 + }
  211 +
  212 + @Override
210 213 public List<CombinationActivityDO> getCombinationActivityListByCount(Integer count) {
211 214 return combinationActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus(), count);
212 215 }
... ... @@ -224,8 +227,8 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
224 227 }
225 228  
226 229 @Override
227   - public CombinationActivityDO getCombinationActivityBySpuId(Long spuId) {
228   - return combinationActivityMapper.selectOne(spuId);
  230 + public List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
  231 + return combinationActivityMapper.selectListBySpuIds(spuIds, status);
229 232 }
230 233  
231 234 }
... ...
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 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
5 6 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
  7 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
6 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
7 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
8 10 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
9 11  
10   -import java.time.LocalDateTime;
  12 +import javax.annotation.Nullable;
  13 +import java.util.Collection;
11 14 import java.util.List;
  15 +import java.util.Map;
12 16  
13 17 /**
14 18 * 拼团记录 Service 接口
... ... @@ -27,40 +31,34 @@ public interface CombinationRecordService {
27 31 void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId);
28 32  
29 33 /**
30   - * 校验是否满足拼团条件
31   - * 如果不满足,会抛出异常
  34 + * 【下单前】校验是否满足拼团活动条件
  35 + *
  36 + * 如果校验失败,则抛出业务异常
32 37 *
33   - * @param activityId 活动编号
34 38 * @param userId 用户编号
  39 + * @param activityId 活动编号
  40 + * @param headId 团长编号
35 41 * @param skuId sku 编号
36 42 * @param count 数量
37   - * @return 返回拼团活动和拼团活动商品
  43 + * @return 拼团信息
38 44 */
39   - KeyValue<CombinationActivityDO, CombinationProductDO> validateCombinationRecord(Long activityId, Long userId, Long skuId, Integer count);
  45 + KeyValue<CombinationActivityDO, CombinationProductDO> validateCombinationRecord(Long userId, Long activityId, Long headId,
  46 + Long skuId, Integer count);
40 47  
41 48 /**
42 49 * 创建拼团记录
43 50 *
44 51 * @param reqDTO 创建信息
  52 + * @return 开团记录编号
45 53 */
46   - void createCombinationRecord(CombinationRecordCreateReqDTO reqDTO);
  54 + Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO);
47 55  
48 56 /**
49   - * 更新拼团状态和开始时间
50   - *
51   - * @param status 状态
52   - * @param userId 用户编号
53   - * @param orderId 订单编号
54   - * @param startTime 开始时间
55   - */
56   - void updateRecordStatusAndStartTimeByUserIdAndOrderId(Integer status, Long userId, Long orderId, LocalDateTime startTime);
57   -
58   - /**
59   - * 获得拼团状态
  57 + * 获得拼团记录
60 58 *
61 59 * @param userId 用户编号
62 60 * @param orderId 订单编号
63   - * @return 拼团状态
  61 + * @return 拼团记录
64 62 */
65 63 CombinationRecordDO getCombinationRecord(Long userId, Long orderId);
66 64  
... ... @@ -71,27 +69,30 @@ public interface CombinationRecordService {
71 69 * @param activityId 活动 id
72 70 * @return 拼团记录列表
73 71 */
74   - List<CombinationRecordDO> getRecordListByUserIdAndActivityId(Long userId, Long activityId);
  72 + List<CombinationRecordDO> getCombinationRecordListByUserIdAndActivityId(Long userId, Long activityId);
75 73  
76 74 /**
77 75 * 【下单前】校验是否满足拼团活动条件
78 76 *
79 77 * 如果校验失败,则抛出业务异常
80 78 *
81   - * @param activityId 活动编号
82 79 * @param userId 用户编号
  80 + * @param activityId 活动编号
  81 + * @param headId 团长编号
83 82 * @param skuId sku 编号
84 83 * @param count 数量
85 84 * @return 拼团信息
86 85 */
87   - CombinationValidateJoinRespDTO validateJoinCombination(Long activityId, Long userId, Long skuId, Integer count);
  86 + CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId, Long skuId, Integer count);
88 87  
89 88 /**
90   - * 获取所有拼团记录数
  89 + * 获取拼团记录数
91 90 *
  91 + * @param status 状态-允许为空
  92 + * @param virtualGroup 是否虚拟成团-允许为空
92 93 * @return 记录数
93 94 */
94   - Long getRecordsCount();
  95 + Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup);
95 96  
96 97 /**
97 98 * 获取最近的 count 条拼团记录
... ... @@ -99,7 +100,7 @@ public interface CombinationRecordService {
99 100 * @param count 限制数量
100 101 * @return 拼团记录列表
101 102 */
102   - List<CombinationRecordDO> getLatestRecordList(int count);
  103 + List<CombinationRecordDO> getLatestCombinationRecordList(int count);
103 104  
104 105 /**
105 106 * 获得最近 n 条拼团记录(团长发起的)
... ... @@ -109,7 +110,7 @@ public interface CombinationRecordService {
109 110 * @param count 数量
110 111 * @return 拼团记录列表
111 112 */
112   - List<CombinationRecordDO> getRecordListWithHead(Long activityId, Integer status, Integer count);
  113 + List<CombinationRecordDO> getHeadCombinationRecordList(Long activityId, Integer status, Integer count);
113 114  
114 115 /**
115 116 * 获取指定编号的拼团记录
... ... @@ -117,7 +118,7 @@ public interface CombinationRecordService {
117 118 * @param id 拼团记录编号
118 119 * @return 拼团记录
119 120 */
120   - CombinationRecordDO getRecordById(Long id);
  121 + CombinationRecordDO getCombinationRecordById(Long id);
121 122  
122 123 /**
123 124 * 获取指定团长编号的拼团记录
... ... @@ -125,6 +126,46 @@ public interface CombinationRecordService {
125 126 * @param headId 团长编号
126 127 * @return 拼团记录列表
127 128 */
128   - List<CombinationRecordDO> getRecordListByHeadId(Long headId);
  129 + List<CombinationRecordDO> getCombinationRecordListByHeadId(Long headId);
  130 +
  131 + /**
  132 + * 获取拼团记录分页数据
  133 + *
  134 + * @param pageVO 分页请求
  135 + * @return 拼团记录分页数据
  136 + */
  137 + PageResult<CombinationRecordDO> getCombinationRecordPage(CombinationRecordReqPageVO pageVO);
  138 +
  139 + /**
  140 + * 【拼团活动】获得拼团记录数量 Map
  141 + *
  142 + * @param activityIds 活动记录编号数组
  143 + * @param status 拼团状态,允许空
  144 + * @param headId 团长编号,允许空。目的 headId 设置为 {@link CombinationRecordDO#HEAD_ID_GROUP} 时,可以设置
  145 + * @return 拼团记录数量 Map
  146 + */
  147 + Map<Long, Integer> getCombinationRecordCountMapByActivity(Collection<Long> activityIds,
  148 + @Nullable Integer status,
  149 + @Nullable Long headId);
  150 +
  151 +
  152 + /**
  153 + * 获取拼团记录
  154 + *
  155 + * @param userId 用户编号
  156 + * @param id 拼团记录编号
  157 + * @return 拼团记录
  158 + */
  159 + CombinationRecordDO getCombinationRecordByIdAndUser(Long userId, Long id);
  160 +
  161 + /**
  162 + * 取消拼团
  163 + *
  164 + * @param userId 用户编号
  165 + * @param id 拼团记录编号
  166 + * @param headId 团长编号
  167 + */
  168 + void cancelCombinationRecord(Long userId, Long id, Long headId);
  169 +
129 170  
130 171 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java
1 1 package cn.iocoder.yudao.module.promotion.service.combination;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.util.ObjectUtil;
  4 +import cn.hutool.core.util.ObjUtil;
5 5 import cn.iocoder.yudao.framework.common.core.KeyValue;
6 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 8 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
8 9 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
9 10 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
... ... @@ -12,6 +13,7 @@ import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
12 13 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
13 14 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
14 15 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
  16 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
15 17 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
16 18 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
17 19 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
... ... @@ -19,18 +21,20 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationR
19 21 import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecordMapper;
20 22 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
21 23 import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
22   -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
23 24 import org.springframework.context.annotation.Lazy;
24 25 import org.springframework.stereotype.Service;
25 26 import org.springframework.transaction.annotation.Transactional;
26 27 import org.springframework.validation.annotation.Validated;
27 28  
  29 +import javax.annotation.Nullable;
28 30 import javax.annotation.Resource;
29   -import java.time.LocalDateTime;
30   -import java.util.List;
  31 +import java.util.*;
31 32  
32 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
33   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
  36 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.afterNow;
  37 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.beforeNow;
34 38 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
35 39  
36 40 // TODO 芋艿:等拼团记录做完,完整 review 下
... ... @@ -61,6 +65,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
61 65 @Resource
62 66 private TradeOrderApi tradeOrderApi;
63 67  
  68 + // TODO @芋艿:在详细预览下;
64 69 @Override
65 70 @Transactional(rollbackFor = Exception.class)
66 71 public void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId) {
... ... @@ -72,30 +77,6 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
72 77 recordMapper.updateById(record);
73 78 }
74 79  
75   - @Override
76   - @Transactional(rollbackFor = Exception.class)
77   - public void updateRecordStatusAndStartTimeByUserIdAndOrderId(Integer status, Long userId, Long orderId, LocalDateTime startTime) {
78   - CombinationRecordDO record = validateCombinationRecord(userId, orderId);
79   - // 更新状态
80   - record.setStatus(status);
81   - // 更新开始时间
82   - record.setStartTime(startTime);
83   - recordMapper.updateById(record);
84   -
85   - // 更新拼团参入人数
86   - List<CombinationRecordDO> records = recordMapper.selectListByHeadIdAndStatus(record.getHeadId(), status);
87   - if (CollUtil.isNotEmpty(records)) {
88   - records.forEach(item -> {
89   - item.setUserCount(records.size());
90   - // 校验拼团是否满足要求
91   - if (ObjectUtil.equal(records.size(), record.getUserSize())) {
92   - item.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus());
93   - }
94   - });
95   - }
96   - recordMapper.updateBatch(records);
97   - }
98   -
99 80 private CombinationRecordDO validateCombinationRecord(Long userId, Long orderId) {
100 81 // 校验拼团是否存在
101 82 CombinationRecordDO recordDO = recordMapper.selectByUserIdAndOrderId(userId, orderId);
... ... @@ -108,141 +89,247 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
108 89 // TODO @芋艿:在详细预览下;
109 90 @Override
110 91 public KeyValue<CombinationActivityDO, CombinationProductDO> validateCombinationRecord(
111   - Long activityId, Long userId, Long skuId, Integer count) {
112   - // 1.1 校验拼团活动是否存在
  92 + Long userId, Long activityId, Long headId, Long skuId, Integer count) {
  93 + // 1. 校验拼团活动是否存在
113 94 CombinationActivityDO activity = combinationActivityService.validateCombinationActivityExists(activityId);
114   - // 1.2 校验活动是否开启
115   - if (ObjectUtil.equal(activity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
  95 + // 1.1 校验活动是否开启
  96 + if (ObjUtil.equal(activity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
116 97 throw exception(COMBINATION_ACTIVITY_STATUS_DISABLE);
117 98 }
118   - // 2 校验是否超出单次限购数量
  99 + // 1.2. 校验活动开始时间
  100 + if (afterNow(activity.getStartTime())) {
  101 + throw exception(COMBINATION_RECORD_FAILED_TIME_NOT_START);
  102 + }
  103 + // 1.3 校验是否超出单次限购数量
119 104 if (count > activity.getSingleLimitCount()) {
120 105 throw exception(COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED);
121 106 }
122   - // 2.1、校验活动商品是否存在
  107 +
  108 + // 2. 父拼团是否存在,是否已经满了
  109 + if (headId != null) {
  110 + // 2.1. 查询进行中的父拼团
  111 + CombinationRecordDO record = recordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
  112 + if (record == null) {
  113 + throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS);
  114 + }
  115 + // 2.2. 校验拼团是否已满
  116 + if (ObjUtil.equal(record.getUserCount(), record.getUserSize())) {
  117 + throw exception(COMBINATION_RECORD_USER_FULL);
  118 + }
  119 + // 2.3 校验拼团是否过期(有父拼团的时候只校验父拼团的过期时间)
  120 + if (beforeNow(record.getExpireTime())) {
  121 + throw exception(COMBINATION_RECORD_FAILED_TIME_END);
  122 + }
  123 + } else {
  124 + // 3. 校验当前活动是否结束(自己是父拼团的时候才校验活动是否结束)
  125 + if (beforeNow(activity.getEndTime())) {
  126 + throw exception(COMBINATION_RECORD_FAILED_TIME_END);
  127 + }
  128 + }
  129 +
  130 + // 4.1 校验活动商品是否存在
123 131 CombinationProductDO product = combinationActivityService.selectByActivityIdAndSkuId(activityId, skuId);
124 132 if (product == null) {
125 133 throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
126 134 }
127   - // 2.2、校验 sku 是否存在
  135 + // 4.2 校验 sku 是否存在
128 136 ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
129 137 if (sku == null) {
130 138 throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
131 139 }
132   - // 2.3、 校验库存是否充足
  140 + // 4.3 校验库存是否充足
133 141 if (count > sku.getStock()) {
134 142 throw exception(COMBINATION_ACTIVITY_UPDATE_STOCK_FAIL);
135 143 }
136   - // 3、校验是否有拼团记录
137   - List<CombinationRecordDO> recordList = getRecordListByUserIdAndActivityId(userId, activityId);
138   - if (CollUtil.isEmpty(recordList)) {
  144 +
  145 + // 6.1 校验是否有拼团记录
  146 + List<CombinationRecordDO> recordList = recordMapper.selectListByUserIdAndActivityId(userId, activityId);
  147 + recordList.removeIf(record -> CombinationRecordStatusEnum.isFailed(record.getStatus())); // 取消的订单,不算数
  148 + if (CollUtil.isEmpty(recordList)) { // 如果为空,说明可以参与,直接返回
139 149 return new KeyValue<>(activity, product);
140 150 }
141   - // 4、校验是否超出总限购数量
142   - Integer sumValue = getSumValue(convertList(recordList, CombinationRecordDO::getCount,
143   - item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus())), i -> i, Integer::sum);
144   - if ((sumValue + count) > activity.getTotalLimitCount()) {
145   - throw exception(COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED);
146   - }
147   - // 5、校验拼团记录是否存在未支付的订单(如果存在未支付的订单则不允许发起新的拼团)
148   - CombinationRecordDO record = findFirst(recordList, item -> ObjectUtil.equals(item.getStatus(), null));
149   - if (record == null) {
150   - return new KeyValue<>(activity, product);
  151 + // 6.2 校验用户是否有该活动正在进行的拼团
  152 + CombinationRecordDO inProgressRecord = findFirst(recordList,
  153 + record -> CombinationRecordStatusEnum.isInProgress(record.getStatus()));
  154 + if (inProgressRecord != null) {
  155 + throw exception(COMBINATION_RECORD_FAILED_HAVE_JOINED);
151 156 }
152   - // 5.1、查询关联的订单是否已经支付
153   - // 当前 activityId 已经有未支付的订单,不允许在发起新的;要么支付,要么去掉先;
154   - // TODO 芋艿:看看是不是可以删除掉;
155   - Integer orderStatus = tradeOrderApi.getOrderStatus(record.getOrderId());
156   - if (ObjectUtil.equal(orderStatus, TradeOrderStatusEnum.UNPAID.getStatus())) {
157   - throw exception(COMBINATION_RECORD_FAILED_ORDER_STATUS_UNPAID);
  157 + // 6.3 校验是否超出总限购数量
  158 + Integer sumValue = getSumValue(recordList, CombinationRecordDO::getCount, Integer::sum);
  159 + if (sumValue != null && sumValue + count > activity.getTotalLimitCount()) {
  160 + throw exception(COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED);
158 161 }
159   -
160 162 return new KeyValue<>(activity, product);
161 163 }
162 164  
163   - // TODO 芋艿:在详细 review 下;
164 165 @Override
165 166 @Transactional(rollbackFor = Exception.class)
166   - public void createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
167   - // 1、校验拼团活动
168   - KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(
169   - reqDTO.getActivityId(), reqDTO.getUserId(), reqDTO.getSkuId(), reqDTO.getCount());
170   - CombinationActivityDO activity = keyValue.getKey();
171   - // 2、校验用户是否参加了其它拼团
172   - List<CombinationRecordDO> recordDOList = recordMapper.selectListByUserIdAndStatus(reqDTO.getUserId(), CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
173   - if (CollUtil.isNotEmpty(recordDOList)) {
174   - throw exception(COMBINATION_RECORD_FAILED_HAVE_JOINED);
175   - }
176   - // 3、校验活动是否开启
177   - if (LocalDateTime.now().isAfter(activity.getStartTime())) {
178   - throw exception(COMBINATION_RECORD_FAILED_TIME_NOT_START);
179   - }
180   - // 4、校验当前活动是否过期
181   - if (LocalDateTime.now().isAfter(activity.getEndTime())) {
182   - throw exception(COMBINATION_RECORD_FAILED_TIME_END);
183   - }
184   - // 5、父拼团是否存在,是否已经满了
185   - if (reqDTO.getHeadId() != null) {
186   - // 5.1、查询进行中的父拼团
187   - CombinationRecordDO record = recordMapper.selectOneByHeadId(reqDTO.getHeadId(), CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
188   - if (record == null) {
189   - throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS);
190   - }
191   - // 5.2、校验拼团是否满足要求
192   - if (ObjectUtil.equal(record.getUserCount(), record.getUserSize())) {
193   - throw exception(COMBINATION_RECORD_USER_FULL);
194   - }
195   - }
  167 + public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
  168 + // 1. 校验拼团活动
  169 + KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(reqDTO.getUserId(),
  170 + reqDTO.getActivityId(), reqDTO.getHeadId(), reqDTO.getSkuId(), reqDTO.getCount());
196 171  
197   - // 6. 创建拼团记录
  172 + // 2.1 组合数据创建拼团记录
198 173 MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId());
199 174 ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId());
200 175 ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId());
201   - recordMapper.insert(CombinationActivityConvert.INSTANCE.convert(reqDTO, activity, user, spu, sku));
  176 + CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO, keyValue.getKey(), user, spu, sku);
  177 + // 2.2 如果是团长需要设置 headId 为 CombinationRecordDO#HEAD_ID_GROUP
  178 + if (record.getHeadId() == null) {
  179 + record.setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
  180 + }
  181 + recordMapper.insert(record);
  182 +
  183 + if (ObjUtil.equal(CombinationRecordDO.HEAD_ID_GROUP, record.getHeadId())) {
  184 + return record.getId();
  185 + }
  186 +
  187 + // TODO @puhui:是不是这里的更新,放到 order 模块那;支付完成后;
  188 + // 4、更新拼团相关信息到订单
  189 + tradeOrderApi.updateOrderCombinationInfo(record.getOrderId(), record.getActivityId(), record.getId(), record.getHeadId());
  190 + // 4、更新拼团记录
  191 + updateCombinationRecordWhenCreate(reqDTO.getHeadId(), keyValue.getKey());
  192 + return record.getId();
  193 + }
  194 +
  195 + /**
  196 + * 当新增拼团时,更新拼团记录的进展
  197 + *
  198 + * @param headId 团长编号
  199 + * @param activity 活动
  200 + */
  201 + private void updateCombinationRecordWhenCreate(Long headId, CombinationActivityDO activity) {
  202 + // 1. 团长 + 团员
  203 + List<CombinationRecordDO> records = getCombinationRecordListByHeadId(headId);
  204 + if (CollUtil.isEmpty(records)) {
  205 + return;
  206 + }
  207 + CombinationRecordDO headRecord = recordMapper.selectById(headId);
  208 +
  209 + // 2. 批量更新记录
  210 + List<CombinationRecordDO> updateRecords = new ArrayList<>();
  211 + records.add(headRecord); // 加入团长,团长也需要更新
  212 + boolean isFull = records.size() >= activity.getUserSize();
  213 + records.forEach(item -> {
  214 + CombinationRecordDO updateRecord = new CombinationRecordDO();
  215 + updateRecord.setId(item.getId()).setUserCount(records.size());
  216 + if (isFull) {
  217 + updateRecord.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus());
  218 + }
  219 + updateRecords.add(updateRecord);
  220 + });
  221 + recordMapper.updateBatch(updateRecords);
202 222 }
203 223  
204 224 @Override
205 225 public CombinationRecordDO getCombinationRecord(Long userId, Long orderId) {
206   - return validateCombinationRecord(userId, orderId);
  226 + return recordMapper.selectByUserIdAndOrderId(userId, orderId);
207 227 }
208 228  
209 229 @Override
210   - public List<CombinationRecordDO> getRecordListByUserIdAndActivityId(Long userId, Long activityId) {
  230 + public List<CombinationRecordDO> getCombinationRecordListByUserIdAndActivityId(Long userId, Long activityId) {
211 231 return recordMapper.selectListByUserIdAndActivityId(userId, activityId);
212 232 }
213 233  
214 234 @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());
  235 + public CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId,
  236 + Long skuId, Integer count) {
  237 + KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(userId, activityId,
  238 + headId, skuId, count);
  239 + return new CombinationValidateJoinRespDTO().setActivityId(keyValue.getKey().getId())
  240 + .setName(keyValue.getKey().getName()).setCombinationPrice(keyValue.getValue().getCombinationPrice());
221 241 }
222 242  
223 243 @Override
224   - public Long getRecordsCount() {
225   - return recordMapper.selectCount();
  244 + public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup) {
  245 + return recordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup);
226 246 }
227 247  
228 248 @Override
229   - public List<CombinationRecordDO> getLatestRecordList(int count) {
  249 + public List<CombinationRecordDO> getLatestCombinationRecordList(int count) {
230 250 return recordMapper.selectLatestList(count);
231 251 }
232 252  
233 253 @Override
234   - public List<CombinationRecordDO> getRecordListWithHead(Long activityId, Integer status, Integer count) {
235   - return recordMapper.selectList(activityId, status, count);
  254 + public List<CombinationRecordDO> getHeadCombinationRecordList(Long activityId, Integer status, Integer count) {
  255 + return recordMapper.selectListByActivityIdAndStatusAndHeadId(activityId, status,
  256 + CombinationRecordDO.HEAD_ID_GROUP, count);
236 257 }
237 258  
238 259 @Override
239   - public CombinationRecordDO getRecordById(Long id) {
  260 + public CombinationRecordDO getCombinationRecordById(Long id) {
240 261 return recordMapper.selectById(id);
241 262 }
242 263  
243 264 @Override
244   - public List<CombinationRecordDO> getRecordListByHeadId(Long headId) {
  265 + public List<CombinationRecordDO> getCombinationRecordListByHeadId(Long headId) {
245 266 return recordMapper.selectList(CombinationRecordDO::getHeadId, headId);
246 267 }
247 268  
  269 + @Override
  270 + public PageResult<CombinationRecordDO> getCombinationRecordPage(CombinationRecordReqPageVO pageVO) {
  271 + return recordMapper.selectPage(pageVO);
  272 + }
  273 +
  274 + @Override
  275 + public Map<Long, Integer> getCombinationRecordCountMapByActivity(Collection<Long> activityIds,
  276 + @Nullable Integer status, @Nullable Long headId) {
  277 + return recordMapper.selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(activityIds, status, headId);
  278 + }
  279 +
  280 + @Override
  281 + public CombinationRecordDO getCombinationRecordByIdAndUser(Long userId, Long id) {
  282 + return recordMapper.selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getId, id);
  283 + }
  284 +
  285 + @Override
  286 + @Transactional(rollbackFor = Exception.class)
  287 + public void cancelCombinationRecord(Long userId, Long id, Long headId) {
  288 + // 删除记录
  289 + recordMapper.deleteById(id);
  290 +
  291 + // 需要更新的记录
  292 + List<CombinationRecordDO> updateRecords = new ArrayList<>();
  293 + // 如果它是团长,则顺序(下单时间)继承
  294 + if (Objects.equals(headId, CombinationRecordDO.HEAD_ID_GROUP)) { // 情况一:团长
  295 + // 团员
  296 + List<CombinationRecordDO> list = getCombinationRecordListByHeadId(id);
  297 + if (CollUtil.isEmpty(list)) {
  298 + return;
  299 + }
  300 + // 按照创建时间升序排序
  301 + list.sort(Comparator.comparing(CombinationRecordDO::getCreateTime)); // 影响原 list
  302 + CombinationRecordDO newHead = list.get(0); // 新团长继位
  303 + list.forEach(item -> {
  304 + CombinationRecordDO recordDO = new CombinationRecordDO();
  305 + recordDO.setId(item.getId());
  306 + if (ObjUtil.equal(item.getId(), newHead.getId())) { // 新团长
  307 + recordDO.setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
  308 + } else {
  309 + recordDO.setHeadId(newHead.getId());
  310 + }
  311 + recordDO.setUserCount(list.size());
  312 + updateRecords.add(recordDO);
  313 + });
  314 + } else { // 情况二:团员
  315 + // 团长
  316 + CombinationRecordDO recordHead = recordMapper.selectById(headId);
  317 + // 团员
  318 + List<CombinationRecordDO> records = getCombinationRecordListByHeadId(headId);
  319 + if (CollUtil.isEmpty(records)) {
  320 + return;
  321 + }
  322 + records.add(recordHead); // 加入团长,团长数据也需要更新
  323 + records.forEach(item -> {
  324 + CombinationRecordDO recordDO = new CombinationRecordDO();
  325 + recordDO.setId(item.getId());
  326 + recordDO.setUserCount(records.size());
  327 + updateRecords.add(recordDO);
  328 + });
  329 + }
  330 +
  331 + // 更新拼团记录
  332 + recordMapper.updateBatch(updateRecords);
  333 + }
  334 +
248 335 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
... ... @@ -120,11 +120,12 @@ public interface SeckillActivityService {
120 120 SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);
121 121  
122 122 /**
123   - * 获取指定 spu 编号的活动
  123 + * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
124 124 *
125   - * @param spuId spu 编号
126   - * @return 秒杀活动
  125 + * @param spuIds spu 编号
  126 + * @param status 状态
  127 + * @return 秒杀活动列表
127 128 */
128   - SeckillActivityDO getSeckillActivityBySpuId(Long spuId);
  129 + List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status);
129 130  
130 131 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
... ... @@ -311,8 +311,8 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
311 311 }
312 312  
313 313 @Override
314   - public SeckillActivityDO getSeckillActivityBySpuId(Long spuId) {
315   - return seckillActivityMapper.selectOne(spuId);
  314 + public List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
  315 + return seckillActivityMapper.selectListBySpuIds(spuIds, status);
316 316 }
317 317  
318 318 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImplTest.java
... ... @@ -111,9 +111,6 @@ public class CombinationActivityServiceImplTest extends BaseDbUnitTest {
111 111 o.setStartTime(null);
112 112 o.setEndTime(null);
113 113 o.setUserSize(null);
114   - o.setTotalCount(null);
115   - o.setSuccessCount(null);
116   - o.setOrderUserCount(null);
117 114 o.setVirtualGroup(null);
118 115 o.setStatus(null);
119 116 o.setLimitDuration(null);
... ... @@ -134,12 +131,6 @@ public class CombinationActivityServiceImplTest extends BaseDbUnitTest {
134 131 combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setEndTime(null)));
135 132 // 测试 userSize 不匹配
136 133 combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setUserSize(null)));
137   - // 测试 totalNum 不匹配
138   - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setTotalCount(null)));
139   - // 测试 successNum 不匹配
140   - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSuccessCount(null)));
141   - // 测试 orderUserCount 不匹配
142   - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setOrderUserCount(null)));
143 134 // 测试 virtualGroup 不匹配
144 135 combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setVirtualGroup(null)));
145 136 // 测试 status 不匹配
... ... @@ -173,9 +164,6 @@ public class CombinationActivityServiceImplTest extends BaseDbUnitTest {
173 164 o.setStartTime(null);
174 165 o.setEndTime(null);
175 166 o.setUserSize(null);
176   - o.setTotalCount(null);
177   - o.setSuccessCount(null);
178   - o.setOrderUserCount(null);
179 167 o.setVirtualGroup(null);
180 168 o.setStatus(null);
181 169 o.setLimitDuration(null);
... ... @@ -196,12 +184,6 @@ public class CombinationActivityServiceImplTest extends BaseDbUnitTest {
196 184 combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setEndTime(null)));
197 185 // 测试 userSize 不匹配
198 186 combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setUserSize(null)));
199   - // 测试 totalNum 不匹配
200   - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setTotalCount(null)));
201   - // 测试 successNum 不匹配
202   - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSuccessCount(null)));
203   - // 测试 orderUserCount 不匹配
204   - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setOrderUserCount(null)));
205 187 // 测试 virtualGroup 不匹配
206 188 combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setVirtualGroup(null)));
207 189 // 测试 status 不匹配
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java
... ... @@ -28,13 +28,22 @@ public interface TradeOrderApi {
28 28 */
29 29 TradeOrderRespDTO getOrder(Long id);
30 30  
31   - // TODO 芋艿:看看是不是可以删除掉;
32 31 /**
33   - * 获取订单状态
  32 + * 更新拼团相关信息到订单
34 33 *
35   - * @param id 订单编号
36   - * @return 订单状态
  34 + * @param orderId 订单编号
  35 + * @param activityId 拼团活动编号
  36 + * @param combinationRecordId 拼团记录编号
  37 + * @param headId 团长编号
  38 + */
  39 + void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId);
  40 +
  41 + /**
  42 + * 取消支付订单
  43 + *
  44 + * @param userId 用户编号
  45 + * @param orderId 订单编号
37 46 */
38   - Integer getOrderStatus(Long id);
  47 + void cancelPaidOrder(Long userId, Long orderId);
39 48  
40 49 }
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
... ... @@ -33,6 +33,7 @@ public interface ErrorCodeConstants {
33 33 ErrorCode ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR = new ErrorCode(1_011_000_028, "支付订单调价失败,原因:调整后支付价格不能小于 0.01 元");
34 34 ErrorCode ORDER_DELETE_FAIL_STATUS_NOT_CANCEL = new ErrorCode(1_011_000_029, "交易订单删除失败,订单不是【已取消】状态");
35 35 ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】");
  36 + ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单已发货");
36 37  
37 38 // ========== After Sale 模块 1-011-000-100 ==========
38 39 ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderOperateTypeEnum.java
... ... @@ -16,7 +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 + ADMIN_UPDATE_ADDRESS(11, "收货地址修改"),
20 20 ADMIN_DELIVERY(20, "已发货,快递公司:{deliveryName},快递单号:{logisticsNo}"),
21 21 MEMBER_RECEIVE(30, "用户已收货"),
22 22 SYSTEM_RECEIVE(31, "到期未收货,系统自动确认收货"),
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderTypeEnum.java
1 1 package cn.iocoder.yudao.module.trade.enums.order;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
3 4 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
4 5 import lombok.Getter;
5 6 import lombok.RequiredArgsConstructor;
... ... @@ -37,4 +38,20 @@ public enum TradeOrderTypeEnum implements IntArrayValuable {
37 38 return ARRAYS;
38 39 }
39 40  
  41 + public static boolean isNormal(Integer type) {
  42 + return ObjectUtil.equal(type, NORMAL.getType());
  43 + }
  44 +
  45 + public static boolean isSeckill(Integer type) {
  46 + return ObjectUtil.equal(type, SECKILL.getType());
  47 + }
  48 +
  49 + public static boolean isBargain(Integer type) {
  50 + return ObjectUtil.equal(type, BARGAIN.getType());
  51 + }
  52 +
  53 + public static boolean isCombination(Integer type) {
  54 + return ObjectUtil.equal(type, COMBINATION.getType());
  55 + }
  56 +
40 57 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
4 4 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
5 5 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
6 6 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
  7 +import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
7 8 import org.springframework.stereotype.Service;
8 9 import org.springframework.validation.annotation.Validated;
9 10  
... ... @@ -25,6 +26,8 @@ public class TradeOrderApiImpl implements TradeOrderApi {
25 26  
26 27 @Resource
27 28 private TradeOrderQueryService tradeOrderQueryService;
  29 + @Resource
  30 + private TradeOrderUpdateService tradeOrderUpdateService;
28 31  
29 32 @Override
30 33 public List<TradeOrderRespDTO> getOrderList(Collection<Long> ids) {
... ... @@ -37,6 +40,16 @@ public class TradeOrderApiImpl implements TradeOrderApi {
37 40 }
38 41  
39 42 @Override
  43 + public void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId) {
  44 + tradeOrderUpdateService.updateOrderCombinationInfo(orderId, activityId, combinationRecordId, headId);
  45 + }
  46 +
  47 + @Override
  48 + public void cancelPaidOrder(Long userId, Long orderId) {
  49 + tradeOrderUpdateService.cancelPaidOrder(userId, orderId);
  50 + }
  51 +
  52 + @Override
40 53 public Integer getOrderStatus(Long id) {
41 54 TradeOrderDO order = tradeOrderQueryService.getOrder(id);
42 55 if (order == null) {
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
... ... @@ -52,7 +52,6 @@ public class AppTradeOrderSettlementReqVO {
52 52 private Long seckillActivityId;
53 53  
54 54 // ========== 拼团活动相关字段 ==========
55   - // TODO @puhui999:是不是拼团记录的编号哈?
56 55 @Schema(description = "拼团活动编号", example = "1024")
57 56 private Long combinationActivityId;
58 57  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
... ... @@ -207,12 +207,16 @@ public interface TradeOrderConvert {
207 207 default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO,
208 208 List<CartDO> cartList) {
209 209 TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO().setUserId(userId)
  210 + .setItems(new ArrayList<>(settlementReqVO.getItems().size()))
210 211 .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus())
  212 + // 物流信息
211 213 .setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId())
212 214 .setPickUpStoreId(settlementReqVO.getPickUpStoreId())
213   - .setItems(new ArrayList<>(settlementReqVO.getItems().size()))
  215 + // 各种活动
214 216 .setSeckillActivityId(settlementReqVO.getSeckillActivityId())
215   - .setBargainRecordId(settlementReqVO.getBargainRecordId());
  217 + .setBargainRecordId(settlementReqVO.getBargainRecordId())
  218 + .setCombinationActivityId(settlementReqVO.getCombinationActivityId())
  219 + .setCombinationHeadId(settlementReqVO.getCombinationHeadId());
216 220 // 商品项的构建
217 221 Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId);
218 222 for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) {
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
... ... @@ -318,10 +318,16 @@ public class TradeOrderDO extends BaseDO {
318 318 */
319 319 private Long combinationActivityId;
320 320 /**
321   - * 团长编号
  321 + * 拼团团长编号
322 322 *
323   - * 关联 CombinationRecordDO 的 id 字段
  323 + * 关联 CombinationRecordDO 的 headId 字段
324 324 */
325 325 private Long combinationHeadId;
  326 + /**
  327 + * 拼团记录编号
  328 + *
  329 + * 关联 CombinationRecordDO 的 id 字段
  330 + */
  331 + private Long combinationRecordId;
326 332  
327 333 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/RedisKeyConstants.java
... ... @@ -15,4 +15,12 @@ public interface RedisKeyConstants {
15 15 */
16 16 String TRADE_NO = "trade_no:";
17 17  
  18 + /**
  19 + * 交易序号的缓存
  20 + *
  21 + * KEY 格式:express_track:{code-logisticsNo-receiverMobile}
  22 + * VALUE 数据格式 String, 物流信息集合
  23 + */
  24 + String EXPRESS_TRACK = "express_track";
  25 +
18 26 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.service.order;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjectUtil;
5 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.extra.spring.SpringUtil;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 8 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
8 9 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
... ... @@ -13,11 +14,13 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
13 14 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
14 15 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
15 16 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
  17 +import cn.iocoder.yudao.module.trade.dal.redis.RedisKeyConstants;
16 18 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
17 19 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClientFactory;
18 20 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
19 21 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
20 22 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
  23 +import org.springframework.cache.annotation.Cacheable;
21 24 import org.springframework.stereotype.Service;
22 25  
23 26 import javax.annotation.Resource;
... ... @@ -141,7 +144,6 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
141 144 return tradeOrderItemMapper.selectProductSumByOrderId(convertSet(orders, TradeOrderDO::getId));
142 145 }
143 146  
144   - // TODO @puhui999:可以加个 spring 缓存,30 分钟;主要考虑及时性要求不高,但是每次调用需要钱;
145 147 /**
146 148 * 获得订单的物流轨迹
147 149 *
... ... @@ -149,19 +151,34 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
149 151 * @return 物流轨迹
150 152 */
151 153 private List<ExpressTrackRespDTO> getExpressTrackList(TradeOrderDO order) {
152   - // 查询物流公司
153 154 if (order.getLogisticsId() == null) {
154 155 return Collections.emptyList();
155 156 }
  157 + // 查询物流公司
156 158 DeliveryExpressDO express = deliveryExpressService.getDeliveryExpress(order.getLogisticsId());
157 159 if (express == null) {
158 160 throw exception(EXPRESS_NOT_EXISTS);
159 161 }
  162 + // 查询物流轨迹
  163 + return getSelf().getExpressTrackList(express.getCode(), order.getLogisticsNo(), order.getReceiverMobile());
  164 + }
160 165  
  166 + /**
  167 + * 查询物流轨迹
  168 + * 加个 spring 缓存,30 分钟;主要考虑及时性要求不高,但是每次调用需要钱;TODO @艿艿:这个时间不会搞了。。。交给你了哈哈哈
  169 + *
  170 + * @param code 快递公司编码
  171 + * @param logisticsNo 发货快递单号
  172 + * @param receiverMobile 收、寄件人的电话号码
  173 + * @return 物流轨迹
  174 + */
  175 + @Cacheable(cacheNames = RedisKeyConstants.EXPRESS_TRACK, key = "#code + '-' + #logisticsNo + '-' + #receiverMobile",
  176 + condition = "#result != null")
  177 + public List<ExpressTrackRespDTO> getExpressTrackList(String code, String logisticsNo, String receiverMobile) {
161 178 // 查询物流轨迹
162 179 return expressClientFactory.getDefaultExpressClient().getExpressTrackList(
163   - new ExpressTrackQueryReqDTO().setExpressCode(express.getCode()).setLogisticsNo(order.getLogisticsNo())
164   - .setPhone(order.getReceiverMobile()));
  180 + new ExpressTrackQueryReqDTO().setExpressCode(code).setLogisticsNo(logisticsNo)
  181 + .setPhone(receiverMobile));
165 182 }
166 183  
167 184  
... ... @@ -190,4 +207,13 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
190 207 return tradeOrderItemMapper.selectListByOrderId(orderIds);
191 208 }
192 209  
  210 + /**
  211 + * 获得自身的代理对象,解决 AOP 生效问题
  212 + *
  213 + * @return 自己
  214 + */
  215 + private TradeOrderQueryServiceImpl getSelf() {
  216 + return SpringUtil.getBean(getClass());
  217 + }
  218 +
193 219 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
... ... @@ -170,4 +170,23 @@ public interface TradeOrderUpdateService {
170 170 */
171 171 int createOrderItemCommentBySystem();
172 172  
  173 + /**
  174 + * 更新拼团相关信息到订单
  175 + *
  176 + * @param orderId 订单编号
  177 + * @param activityId 拼团活动编号
  178 + * @param combinationRecordId 拼团记录编号
  179 + * @param headId 团长编号
  180 + */
  181 + // TODO 芋艿:再 review 拼团
  182 + void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId);
  183 +
  184 + /**
  185 + * 取消支付订单
  186 + *
  187 + * @param userId 用户编号
  188 + * @param orderId 订单编号
  189 + */
  190 + void cancelPaidOrder(Long userId, Long orderId);
  191 +
173 192 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
... ... @@ -250,7 +250,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
250 250 /**
251 251 * 订单创建前,执行前置逻辑
252 252 *
253   - * @param order 订单
  253 + * @param order 订单
254 254 * @param orderItems 订单项
255 255 */
256 256 private void beforeCreateTradeOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
... ... @@ -267,9 +267,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
267 267 * <p>
268 268 * 例如说:优惠劵的扣减、积分的扣减、支付单的创建等等
269 269 *
270   - * @param order 订单
271   - * @param orderItems 订单项
272   - * @param createReqVO 创建订单请求
  270 + * @param order 订单
  271 + * @param orderItems 订单项
  272 + * @param createReqVO 创建订单请求
273 273 */
274 274 private void afterCreateTradeOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
275 275 AppTradeOrderCreateReqVO createReqVO) {
... ... @@ -331,7 +331,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
331 331 }
332 332  
333 333 // 3、订单支付成功后
334   - tradeOrderHandlers.forEach(handler -> handler.afterPayOrder(order));
  334 + List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
  335 + tradeOrderHandlers.forEach(handler -> handler.afterPayOrder(order, orderItems));
335 336  
336 337 // 4.1 增加用户积分(赠送)
337 338 addUserPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE, order.getId());
... ... @@ -624,12 +625,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
624 625 throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
625 626 }
626 627  
627   - // 2. TODO 活动相关库存回滚需要活动 id,活动 id 怎么获取?app 端能否传过来;回复:从订单里拿呀
628   - tradeOrderHandlers.forEach(handler -> handler.cancelOrder());
629   -
630   - // 3. 回滚库存
631 628 List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
  629 + // 3. 回滚库存
632 630 productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
  631 + // 3.1、 活动相关的回滚
  632 + tradeOrderHandlers.forEach(handler -> handler.cancelOrder(order, orderItems));
633 633  
634 634 // 4. 回滚优惠券
635 635 if (order.getCouponId() != null && order.getCouponId() > 0) {
... ... @@ -738,19 +738,21 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
738 738 }
739 739  
740 740 @Override
741   - @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_UP_ADDRESS)
  741 + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_UPDATE_ADDRESS)
742 742 public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) {
743 743 // 校验交易订单
744 744 TradeOrderDO order = validateOrderExists(reqVO.getId());
745   - // TODO @puhui999:是否需要校验订单是否发货
746   - // TODO 发货后是否支持修改收货地址
  745 + // 发货后,不允许修改;
  746 + // TODO @puhui999:只有待发货,可以执行 update
  747 + if (TradeOrderStatusEnum.isDelivered(order.getStatus())) {
  748 + throw exception(ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED);
  749 + }
747 750  
748 751 // 更新
749 752 tradeOrderMapper.updateById(TradeOrderConvert.INSTANCE.convert(reqVO));
750 753  
751 754 // 记录订单日志
752   - TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus(),
753   - MapUtil.<String, Object>builder().put("orderId", order.getId()).build());
  755 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus());
754 756 }
755 757  
756 758 @Override
... ... @@ -806,10 +808,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
806 808 // 2.2 如果全部退款,则进行取消订单
807 809 getSelf().cancelOrderByAfterSale(order, orderRefundPrice);
808 810  
809   - // TODO @puhui999:活动相关的回滚
810 811  
811 812 // 3. 回滚库存
812 813 productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem)));
  814 + // 3.1、 活动相关的回滚
  815 + tradeOrderHandlers.forEach(handler -> handler.cancelOrder(order, Collections.singletonList(orderItem)));
813 816  
814 817 // 4.1 回滚积分:扣减用户积分(赠送的)
815 818 reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, orderItem.getAfterSaleId());
... ... @@ -909,6 +912,25 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
909 912 return count;
910 913 }
911 914  
  915 + @Override
  916 + @Transactional(rollbackFor = Exception.class)
  917 + public void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId) {
  918 + tradeOrderMapper.updateById(
  919 + new TradeOrderDO().setId(orderId).setCombinationActivityId(activityId)
  920 + .setCombinationRecordId(combinationRecordId).setCombinationHeadId(headId));
  921 + }
  922 +
  923 + @Override
  924 + @Transactional(rollbackFor = Exception.class)
  925 + public void cancelPaidOrder(Long userId, Long orderId) {
  926 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId);
  927 + if (order == null) {
  928 + throw exception(ORDER_NOT_FOUND);
  929 + }
  930 +
  931 + cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
  932 + }
  933 +
912 934 /**
913 935 * 创建单个订单的评论
914 936 *
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java
1 1 package cn.iocoder.yudao.module.trade.service.order.handler;
2 2  
3   -import cn.hutool.core.util.ObjectUtil;
4 3 import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
5 4 import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi;
6 5 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
... ... @@ -26,9 +25,10 @@ public class TradeBargainHandler implements TradeOrderHandler {
26 25  
27 26 @Override
28 27 public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
29   - if (ObjectUtil.notEqual(TradeOrderTypeEnum.BARGAIN.getType(), order.getType())) {
  28 + if (TradeOrderTypeEnum.isBargain(order.getType())) {
30 29 return;
31 30 }
  31 +
32 32 // 扣减砍价活动的库存
33 33 bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(),
34 34 -orderItems.get(0).getCount());
... ... @@ -36,13 +36,20 @@ public class TradeBargainHandler implements TradeOrderHandler {
36 36  
37 37 @Override
38 38 public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
39   - if (ObjectUtil.notEqual(TradeOrderTypeEnum.BARGAIN.getType(), order.getType())) {
  39 + if (TradeOrderTypeEnum.isBargain(order.getType())) {
40 40 return;
41 41 }
  42 +
42 43 // 记录砍价记录对应的订单编号
43 44 bargainRecordApi.updateBargainRecordOrderId(order.getBargainRecordId(), order.getId());
44 45 }
45 46  
46   - // TODO 芋艿:取消订单时,需要增加库存
  47 + @Override
  48 + public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
  49 + if (TradeOrderTypeEnum.isBargain(order.getType())) {
  50 + return;
  51 + }
  52 + // TODO 芋艿:取消订单时,需要增加库存
  53 + }
47 54  
48 55 }
... ...
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 3 import cn.hutool.core.lang.Assert;
4   -import cn.hutool.core.util.ObjectUtil;
5 4 import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
6 5 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
7 6 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
... ... @@ -26,40 +25,37 @@ public class TradeCombinationHandler implements TradeOrderHandler {
26 25 @Override
27 26 public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
28 27 // 如果不是拼团订单则结束
29   - if (ObjectUtil.notEqual(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
  28 + if (TradeOrderTypeEnum.isCombination(order.getType())) {
30 29 return;
31 30 }
32 31 Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
33 32  
34   - // 获取商品信息
35   - TradeOrderItemDO item = orderItems.get(0);
36 33 // 校验是否满足拼团活动相关限制
37   - combinationRecordApi.validateCombinationRecord(order.getCombinationActivityId(), order.getUserId(), item.getSkuId(), item.getCount());
  34 + TradeOrderItemDO item = orderItems.get(0);
  35 + combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(),
  36 + order.getCombinationHeadId(), item.getSkuId(), item.getCount());
  37 + // TODO @puhui999:这里还要限制下,是不是已经 createOrder;就是还没支付的时候,重复下单了;需要校验下;不然的话,一个拼团可以下多个单子了;
38 38 }
39 39  
40 40 @Override
41   - public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
  41 + public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
42 42 // 如果不是拼团订单则结束
43   - if (ObjectUtil.notEqual(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
  43 + if (TradeOrderTypeEnum.isCombination(order.getType())) {
44 44 return;
45 45 }
46 46 Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
47 47  
48 48 // 获取商品信息
49 49 TradeOrderItemDO item = orderItems.get(0);
50   - // 创建砍价记录
  50 + // 创建拼团记录
51 51 combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, item));
52 52 }
53 53  
54 54 @Override
55   - public void afterPayOrder(TradeOrderDO order) {
56   - // 如果不是拼团订单则结束
57   - if (ObjectUtil.notEqual(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
  55 + public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
  56 + if (TradeOrderTypeEnum.isCombination(order.getType())) {
58 57 return;
59 58 }
60   -
61   - // 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录
62   - combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), order.getPayTime());
63 59 }
64 60  
65 61 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java
... ... @@ -33,12 +33,18 @@ public interface TradeOrderHandler {
33 33 * 支付订单后
34 34 *
35 35 * @param order 订单
  36 + * @param orderItems 订单项
36 37 */
37   - default void afterPayOrder(TradeOrderDO order) {}
  38 + default void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
  39 + }
38 40  
39 41 /**
40 42 * 订单取消
  43 + *
  44 + * @param order 订单
  45 + * @param orderItems 订单项
41 46 */
42   - default void cancelOrder() {}
  47 + default void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
  48 + }
43 49  
44 50 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java
1 1 package cn.iocoder.yudao.module.trade.service.order.handler;
2 2  
3   -import cn.hutool.core.util.ObjectUtil;
4 3 import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
5 4 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
6 5 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
... ... @@ -23,12 +22,21 @@ public class TradeSeckillHandler implements TradeOrderHandler {
23 22  
24 23 @Override
25 24 public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
26   - if (ObjectUtil.notEqual(TradeOrderTypeEnum.SECKILL.getType(), order.getType())) {
  25 + if (TradeOrderTypeEnum.isSeckill(order.getType())) {
27 26 return;
28 27 }
  28 +
29 29 // 扣减秒杀活动的库存
30 30 seckillActivityApi.updateSeckillStock(order.getSeckillActivityId(),
31 31 orderItems.get(0).getSkuId(), orderItems.get(0).getCount());
32 32 }
33 33  
  34 + @Override
  35 + public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
  36 + if (TradeOrderTypeEnum.isSeckill(order.getType())) {
  37 + return;
  38 + }
  39 +
  40 + }
  41 +
34 42 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java
... ... @@ -68,7 +68,6 @@ public class TradePriceCalculateReqBO {
68 68 private Long seckillActivityId;
69 69  
70 70 // ========== 拼团活动相关字段 ==========
71   - // TODO @puhui999:是不是拼团记录的编号哈?
72 71 /**
73 72 * 拼团活动编号
74 73 */
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCombinationActivityPriceCalculator.java
... ... @@ -12,6 +12,8 @@ import org.springframework.stereotype.Component;
12 12  
13 13 import javax.annotation.Resource;
14 14  
  15 +// TODO @puhui999:单测可以后补下
  16 +
15 17 /**
16 18 * 拼团活动的 {@link TradePriceCalculator} 实现类
17 19 *
... ... @@ -30,12 +32,11 @@ public class TradeCombinationActivityPriceCalculator implements TradePriceCalcul
30 32 if (param.getCombinationActivityId() == null) {
31 33 return;
32 34 }
33   -
34 35 Assert.isTrue(param.getItems().size() == 1, "拼团时,只允许选择一个商品");
35 36 // 2. 校验是否可以参与拼团
36 37 TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0);
37 38 CombinationValidateJoinRespDTO combinationActivity = combinationRecordApi.validateJoinCombination(
38   - param.getCombinationActivityId(), param.getUserId(),
  39 + param.getUserId(), param.getCombinationActivityId(), param.getCombinationHeadId(),
39 40 orderItem.getSkuId(), orderItem.getCount());
40 41  
41 42 // 3.1 记录优惠明细
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
... ... @@ -176,7 +176,7 @@ public class DeptServiceImpl implements DeptService {
176 176 }
177 177  
178 178 @Override
179   - @DataPermission(enable = false) // 禁用数据权限,避免简历不正确的缓存
  179 + @DataPermission(enable = false) // 禁用数据权限,避免建立不正确的缓存
180 180 @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id")
181 181 public Set<Long> getChildDeptIdListFromCache(Long id) {
182 182 List<DeptDO> children = getChildDeptList(id);
... ...