Commit 8811ce0200b291977a34635bb26fed8d143ffede

Authored by 芋道源码
Committed by Gitee
2 parents b9018baa b2c608e7

!637 完善优惠券功能

Merge pull request !637 from 疯狂的世界/feature/mall_product
Showing 40 changed files with 633 additions and 354 deletions
sql/mysql/ruoyi-vue-pro.sql
... ... @@ -1188,8 +1188,8 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
1188 1188 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1166, 2, '折扣', '2', 'promotion_discount_type', 0, 'primary', '', '优惠类型 - 折扣', '1', '2022-11-01 12:46:51', '1', '2022-11-01 12:50:08', b'0');
1189 1189 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1167, 1, '固定日期', '1', 'promotion_coupon_template_validity_type', 0, 'default', '', '优惠劵模板的有限期类型 - 固定日期', '1', '2022-11-02 00:07:34', '1', '2022-11-04 00:07:49', b'0');
1190 1190 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1168, 2, '领取之后', '2', 'promotion_coupon_template_validity_type', 0, 'default', '', '优惠劵模板的有限期类型 - 领取之后', '1', '2022-11-02 00:07:54', '1', '2022-11-04 00:07:52', b'0');
1191   -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1169, 1, '通用卷', '1', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 全部商品参与', '1', '2022-11-02 00:28:22', '1', '2023-09-01 23:42:49', b'0');
1192   -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1170, 2, '商品卷', '2', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 指定商品参与', '1', '2022-11-02 00:28:34', '1', '2023-09-01 23:42:54', b'0');
  1191 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1169, 1, '通用券', '1', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 全部商品参与', '1', '2022-11-02 00:28:22', '1', '2023-09-01 23:42:49', b'0');
  1192 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1170, 2, '商品券', '2', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 指定商品参与', '1', '2022-11-02 00:28:34', '1', '2023-09-01 23:42:54', b'0');
1193 1193 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1171, 1, '已领取', '1', 'promotion_coupon_status', 0, 'primary', '', '优惠劵的状态 - 已领取', '1', '2022-11-04 00:15:08', '1', '2022-11-04 19:16:04', b'0');
1194 1194 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1172, 2, '已使用', '2', 'promotion_coupon_status', 0, 'success', '', '优惠劵的状态 - 已使用', '1', '2022-11-04 00:15:21', '1', '2022-11-04 19:16:08', b'0');
1195 1195 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1173, 3, '已过期', '3', 'promotion_coupon_status', 0, 'info', '', '优惠劵的状态 - 已过期', '1', '2022-11-04 00:15:43', '1', '2022-11-04 19:16:12', b'0');
... ... @@ -1284,7 +1284,7 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
1284 1284 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1355, 5, '抽奖奖励', '5', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', '2023-08-22 12:41:01', b'0');
1285 1285 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1356, 1, '快递发货', '1', 'trade_delivery_type', 0, '', '', '', '1', '2023-08-23 00:04:55', '1', '2023-08-23 00:04:55', b'0');
1286 1286 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1357, 2, '用户自提', '2', 'trade_delivery_type', 0, '', '', '', '1', '2023-08-23 00:05:05', '1', '2023-08-23 00:05:05', b'0');
1287   -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1358, 3, '品类卷', '3', 'promotion_product_scope', 0, 'default', '', '', '1', '2023-09-01 23:43:07', '1', '2023-09-01 23:43:07', b'0');
  1287 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1358, 3, '品类券', '3', 'promotion_product_scope', 0, 'default', '', '', '1', '2023-09-01 23:43:07', '1', '2023-09-01 23:43:07', b'0');
1288 1288 COMMIT;
1289 1289  
1290 1290 -- ----------------------------
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java
... ... @@ -68,7 +68,7 @@ public class RewardActivityMatchRespDTO {
68 68 */
69 69 private List<Long> couponIds;
70 70 /**
71   - * 赠送的优惠卷数量的数组
  71 + * 赠送的优惠券数量的数组
72 72 */
73 73 private List<Integer> couponCounts;
74 74  
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java
... ... @@ -15,9 +15,9 @@ import java.util.Arrays;
15 15 @AllArgsConstructor
16 16 public enum PromotionProductScopeEnum implements IntArrayValuable {
17 17  
18   - ALL(1, "通用卷"), // 全部商品
19   - SPU(2, "商品卷"), // 指定商品
20   - CATEGORY(3, "品类卷"), // 指定商品
  18 + ALL(1, "通用券"), // 全部商品
  19 + SPU(2, "商品券"), // 指定商品
  20 + CATEGORY(3, "品类券"), // 指定品类
21 21 ;
22 22  
23 23 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionProductScopeEnum::getScope).toArray();
... ...
yudao-module-mall/yudao-module-promotion-biz/pom.xml
... ... @@ -47,6 +47,10 @@
47 47 </dependency>
48 48 <dependency>
49 49 <groupId>cn.iocoder.boot</groupId>
  50 + <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
  51 + </dependency>
  52 + <dependency>
  53 + <groupId>cn.iocoder.boot</groupId>
50 54 <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
51 55 </dependency>
52 56  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponPageReqVO.java
1 1 package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon;
2 2  
3 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.coupon.CouponStatusEnum;
4 6 import io.swagger.v3.oas.annotations.media.Schema;
5 7 import lombok.Data;
6 8 import lombok.EqualsAndHashCode;
... ... @@ -8,6 +10,7 @@ import lombok.ToString;
8 10 import org.springframework.format.annotation.DateTimeFormat;
9 11  
10 12 import java.time.LocalDateTime;
  13 +import java.util.Collection;
11 14  
12 15 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
13 16  
... ... @@ -21,6 +24,7 @@ public class CouponPageReqVO extends PageParam {
21 24 private Long templateId;
22 25  
23 26 @Schema(description = "优惠码状态", example = "1")
  27 + @InEnum(value = CouponStatusEnum.class, message = "优惠劵状态,必须是 {value}")
24 28 private Integer status;
25 29  
26 30 @Schema(description = "创建时间")
... ... @@ -30,4 +34,7 @@ public class CouponPageReqVO extends PageParam {
30 34 @Schema(description = "用户昵称", example = "芋艿")
31 35 private String nickname;
32 36  
  37 + @Schema(description = "用户编号", example = "1")
  38 + private Collection<Long> userIds;
  39 +
33 40 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplatePageReqVO.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
5 6 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
6 7 import io.swagger.v3.oas.annotations.media.Schema;
7 8 import lombok.Data;
... ... @@ -33,8 +34,14 @@ public class CouponTemplatePageReqVO extends PageParam {
33 34 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
34 35 private LocalDateTime[] createTime;
35 36  
36   - @Schema(description = "可以领取的类型", example = "[1,2, 3]")
  37 + @Schema(description = "可以领取的类型", example = "[1, 2, 3]")
37 38 @InEnum(value = CouponTakeTypeEnum.class, message = "可以领取的类型,必须是 {value}")
38 39 private List<Integer> canTakeTypes;
39 40  
  41 + @Schema(description = "商品范围", example = "1")
  42 + @InEnum(value = PromotionProductScopeEnum.class, message = "商品范围,必须是 {value}")
  43 + private Integer productScope;
  44 +
  45 + @Schema(description = "商品范围编号", example = "1")
  46 + private Long productScopeValue;
40 47 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java
... ... @@ -84,7 +84,7 @@ public class RewardActivityBaseVO {
84 84 @Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3")
85 85 private List<Long> couponIds;
86 86  
87   - @Schema(description = "赠送的优惠卷数量的数组", example = "1,2,3")
  87 + @Schema(description = "赠送的优惠券数量的数组", example = "1,2,3")
88 88 private List<Integer> couponCounts;
89 89  
90 90 @AssertTrue(message = "优惠劵和数量必须一一对应")
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.coupon;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
3 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 7 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
6   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
7   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
8   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
9   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
10   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO;
  8 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.*;
  9 +import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
  10 +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
  11 +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
  12 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
11 13 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
  14 +import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
12 15 import io.swagger.v3.oas.annotations.Operation;
  16 +import io.swagger.v3.oas.annotations.Parameter;
13 17 import io.swagger.v3.oas.annotations.tags.Tag;
14 18 import org.springframework.validation.annotation.Validated;
15 19 import org.springframework.web.bind.annotation.*;
16 20  
17 21 import javax.annotation.Resource;
18   -import java.time.LocalDateTime;
19   -import java.util.ArrayList;
  22 +import javax.validation.Valid;
  23 +import java.util.Collections;
20 24 import java.util.List;
21   -import java.util.Random;
22 25  
23 26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
24 27 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
... ... @@ -31,73 +34,40 @@ public class AppCouponController {
31 34  
32 35 @Resource
33 36 private CouponService couponService;
  37 + @Resource
  38 + private CouponTemplateService couponTemplateService;
34 39  
35   - // TODO 芋艿:待实现
36 40 @PostMapping("/take")
37 41 @Operation(summary = "领取优惠劵")
38   - public CommonResult<Long> takeCoupon(@RequestBody AppCouponTemplatePageReqVO pageReqVO) {
39   - return success(1L);
  42 + @Parameter(name = "templateId", description = "优惠券模板编号", required = true, example = "1024")
  43 + public CommonResult<Boolean> takeCoupon(@Valid @RequestBody AppCouponTakeReqVO reqVO) {
  44 + Long userId = getLoginUserId();
  45 + // 领取
  46 + couponService.takeCoupon(reqVO.getTemplateId(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.USER);
  47 + // 检查是否可以继续领取
  48 + CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(reqVO.getTemplateId());
  49 + boolean canTakeAgain = true;
  50 + if (couponTemplate.getTakeLimitCount() != null && couponTemplate.getTakeLimitCount() > 0) {
  51 + Integer takeCount = MapUtil.getInt(couponService.getTakeCountMapByTemplateIds(
  52 + Collections.singleton(reqVO.getTemplateId()), userId), reqVO.getTemplateId(), 0);
  53 + canTakeAgain = takeCount < couponTemplate.getTakeLimitCount();
  54 + }
  55 + return success(canTakeAgain);
40 56 }
41 57  
42   - // TODO 芋艿:待实现
43 58 @GetMapping("/match-list")
44 59 @Operation(summary = "获得匹配指定商品的优惠劵列表")
45 60 public CommonResult<List<AppCouponMatchRespVO>> getMatchCouponList(AppCouponMatchReqVO matchReqVO) {
46   - List<AppCouponMatchRespVO> list = new ArrayList<>();
47   - Random random = new Random();
48   - for (int i = 0; i < 10; i++) {
49   - AppCouponMatchRespVO vo = new AppCouponMatchRespVO();
50   - vo.setId(i + 1L);
51   - vo.setName("优惠劵" + (i + 1));
52   - vo.setUsePrice(random.nextInt(100) * 100);
53   - vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
54   - vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
55   - vo.setDiscountType(random.nextInt(2) + 1);
56   - if (vo.getDiscountType() == 1) {
57   - vo.setDiscountPercent(null);
58   - vo.setDiscountPrice(random.nextInt(50) * 100);
59   - vo.setDiscountLimitPrice(null);
60   - } else {
61   - vo.setDiscountPercent(random.nextInt(90) + 10);
62   - vo.setDiscountPrice(null);
63   - vo.setDiscountLimitPrice(random.nextInt(200) * 100);
64   - }
65   - vo.setMatch(random.nextBoolean());
66   - if (!vo.getMatch()) {
67   - vo.setDescription("不符合条件噢");
68   - }
69   - list.add(vo);
70   - }
71   - return success(list);
  61 + // todo: 优惠金额倒序
  62 + return success(CouponConvert.INSTANCE.convertList(couponService.getMatchCouponList(getLoginUserId(), matchReqVO)));
72 63 }
73 64  
74   - // TODO 芋艿:待实现
75 65 @GetMapping("/page")
76 66 @Operation(summary = "优惠劵列表", description = "我的优惠劵")
77 67 public CommonResult<PageResult<AppCouponRespVO>> takeCoupon(AppCouponPageReqVO pageReqVO) {
78   - List<AppCouponRespVO> list = new ArrayList<>();
79   - Random random = new Random();
80   - for (int i = 0; i < 10; i++) {
81   - AppCouponRespVO vo = new AppCouponRespVO();
82   - vo.setId(i + 1L);
83   - vo.setName("优惠劵" + (i + 1));
84   - vo.setStatus(pageReqVO.getStatus());
85   - vo.setUsePrice(random.nextInt(100) * 100);
86   - vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
87   - vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
88   - vo.setDiscountType(random.nextInt(2) + 1);
89   - if (vo.getDiscountType() == 1) {
90   - vo.setDiscountPercent(null);
91   - vo.setDiscountPrice(random.nextInt(50) * 100);
92   - vo.setDiscountLimitPrice(null);
93   - } else {
94   - vo.setDiscountPercent(random.nextInt(90) + 10);
95   - vo.setDiscountPrice(null);
96   - vo.setDiscountLimitPrice(random.nextInt(200) * 100);
97   - }
98   - list.add(vo);
99   - }
100   - return success(new PageResult<>(list, 20L));
  68 + PageResult<CouponDO> pageResult = couponService.getCouponPage(
  69 + CouponConvert.INSTANCE.convert(pageReqVO, Collections.singleton(getLoginUserId())));
  70 + return success(CouponConvert.INSTANCE.convertAppPage(pageResult));
101 71 }
102 72  
103 73 @GetMapping(value = "/get-unused-count")
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.coupon;
2 2  
  3 +import cn.hutool.core.util.ObjUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
  7 +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
5 8 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO;
6 9 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplateRespVO;
  10 +import cn.iocoder.yudao.module.promotion.convert.coupon.CouponTemplateConvert;
  11 +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
  12 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
  13 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
  14 +import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
7 15 import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
8 16 import io.swagger.v3.oas.annotations.Operation;
9   -import io.swagger.v3.oas.annotations.Parameter;
10   -import io.swagger.v3.oas.annotations.Parameters;
11 17 import io.swagger.v3.oas.annotations.tags.Tag;
12 18 import org.springframework.validation.annotation.Validated;
13 19 import org.springframework.web.bind.annotation.GetMapping;
14 20 import org.springframework.web.bind.annotation.RequestMapping;
15   -import org.springframework.web.bind.annotation.RequestParam;
16 21 import org.springframework.web.bind.annotation.RestController;
17 22  
18 23 import javax.annotation.Resource;
19   -import java.time.LocalDateTime;
20   -import java.util.ArrayList;
21   -import java.util.List;
22   -import java.util.Random;
  24 +import java.util.*;
23 25  
24 26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  27 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  28 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
25 29  
26 30 @Tag(name = "用户 App - 优惠劵模板")
27 31 @RestController
... ... @@ -31,84 +35,45 @@ public class AppCouponTemplateController {
31 35  
32 36 @Resource
33 37 private CouponTemplateService couponTemplateService;
  38 + @Resource
  39 + private CouponService couponService;
34 40  
35   - // TODO 芋艿:待实现
36   - @GetMapping("/list")
37   - @Operation(summary = "获得优惠劵模版列表")
38   - @Parameters({
39   - @Parameter(name = "spuId", description = "商品 SPU 编号"), // 目前主要给商品详情使用
40   - @Parameter(name = "useType", description = "使用类型"),
41   - @Parameter(name = "count", description = "数量", required = true)
42   - })
43   - public CommonResult<List<AppCouponTemplateRespVO>> getCouponTemplateList(@RequestParam(value = "spuId", required = false) Long spuId,
44   - @RequestParam(value = "useType", required = false) Integer useType,
45   - @RequestParam(value = "count", required = false, defaultValue = "10") Integer count) {
46   - List<AppCouponTemplateRespVO> list = new ArrayList<>();
47   - Random random = new Random();
48   - for (int i = 0; i < 10; i++) {
49   - AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO();
50   - vo.setId(i + 1L);
51   - vo.setName("优惠劵" + (i + 1));
52   - vo.setTakeLimitCount(random.nextInt(10) + 1);
53   - vo.setUsePrice(random.nextInt(100) * 100);
54   - vo.setValidityType(random.nextInt(2) + 1);
55   - if (vo.getValidityType() == 1) {
56   - vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
57   - vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
58   - } else {
59   - vo.setFixedStartTerm(random.nextInt(10));
60   - vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1);
61   - }
62   - vo.setDiscountType(random.nextInt(2) + 1);
63   - if (vo.getDiscountType() == 1) {
64   - vo.setDiscountPercent(null);
65   - vo.setDiscountPrice(random.nextInt(50) * 100);
66   - vo.setDiscountLimitPrice(null);
67   - } else {
68   - vo.setDiscountPercent(random.nextInt(90) + 10);
69   - vo.setDiscountPrice(null);
70   - vo.setDiscountLimitPrice(random.nextInt(200) * 100);
71   - }
72   - vo.setTakeStatus(random.nextBoolean());
73   - list.add(vo);
74   - }
75   - return success(list);
76   - }
  41 + @Resource
  42 + private ProductSpuApi productSpuApi;
77 43  
78   - // TODO 芋艿:待实现;和 getCouponTemplateList 类似
79 44 @GetMapping("/page")
80 45 @Operation(summary = "获得优惠劵模版分页")
81 46 public CommonResult<PageResult<AppCouponTemplateRespVO>> getCouponTemplatePage(AppCouponTemplatePageReqVO pageReqVO) {
82   - List<AppCouponTemplateRespVO> list = new ArrayList<>();
83   - Random random = new Random();
84   - for (int i = 0; i < 10; i++) {
85   - AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO();
86   - vo.setId(i + 1L);
87   - vo.setName("优惠劵" + (i + 1));
88   - vo.setTakeLimitCount(random.nextInt(10) + 1);
89   - vo.setUsePrice(random.nextInt(100) * 100);
90   - vo.setValidityType(random.nextInt(2) + 1);
91   - if (vo.getValidityType() == 1) {
92   - vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
93   - vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
94   - } else {
95   - vo.setFixedStartTerm(random.nextInt(10));
96   - vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1);
97   - }
98   - vo.setDiscountType(random.nextInt(2) + 1);
99   - if (vo.getDiscountType() == 1) {
100   - vo.setDiscountPercent(null);
101   - vo.setDiscountPrice(random.nextInt(50) * 100);
102   - vo.setDiscountLimitPrice(null);
103   - } else {
104   - vo.setDiscountPercent(random.nextInt(90) + 10);
105   - vo.setDiscountPrice(null);
106   - vo.setDiscountLimitPrice(random.nextInt(200) * 100);
107   - }
108   - vo.setTakeStatus(random.nextBoolean());
109   - list.add(vo);
  47 + // 1.1 处理查询条件:商品范围编号
  48 + Long productScopeValue = getaProductScopeValue(pageReqVO);
  49 + // 1.2 处理查询条件:领取方式=直接领取
  50 + List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
  51 + // 2. 分页查询
  52 + PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage(
  53 + CouponTemplateConvert.INSTANCE.convert(pageReqVO, canTakeTypes, pageReqVO.getProductScope(), productScopeValue));
  54 + // 3.1 领取数量
  55 + Map<Long, Integer> couponTakeCountMap = new HashMap<>(0);
  56 + Long userId = getLoginUserId();
  57 + if (userId != null) {
  58 + List<Long> templateIds = convertList(pageResult.getList(), CouponTemplateDO::getId,
  59 + t -> ObjUtil.notEqual(t.getTakeLimitCount(), -1)); // 只查有设置“每人限领个数”的
  60 + couponTakeCountMap = couponService.getTakeCountMapByTemplateIds(templateIds, userId);
  61 + }
  62 + // 3.2 拼接返回
  63 + return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, couponTakeCountMap));
  64 + }
  65 +
  66 + private Long getaProductScopeValue(AppCouponTemplatePageReqVO pageReqVO) {
  67 + Long productScopeValue = pageReqVO.getSpuId();
  68 + if (pageReqVO.getProductScope() == null || Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.ALL.getScope())) {
  69 + // 通用券:清除商品范围
  70 + productScopeValue = null;
  71 + } else if (Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.CATEGORY.getScope()) && pageReqVO.getSpuId() != null) {
  72 + // 品类券:查询商品的品类
  73 + productScopeValue = Optional.ofNullable(productSpuApi.getSpu(pageReqVO.getSpuId()))
  74 + .map(ProductSpuRespDTO::getCategoryId).orElse(null);
110 75 }
111   - return success(new PageResult<>(list, 20L));
  76 + return productScopeValue;
112 77 }
113 78  
114 79 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponMatchReqVO.java
... ... @@ -15,15 +15,15 @@ public class AppCouponMatchReqVO {
15 15 @NotNull(message = "商品金额不能为空")
16 16 private Integer price;
17 17  
18   - @Schema(description = "商品 SPU 编号的数组", required = true, example = "[1, 2]")
  18 + @Schema(description = "商品 SPU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
19 19 @NotEmpty(message = "商品 SPU 编号不能为空")
20 20 private List<Long> spuIds;
21 21  
22   - @Schema(description = "商品 SKU 编号的数组", required = true, example = "[1, 2]")
  22 + @Schema(description = "商品 SKU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
23 23 @NotEmpty(message = "商品 SKU 编号不能为空")
24 24 private List<Long> skuIds;
25 25  
26   - @Schema(description = "分类编号的数组", required = true, example = "[10, 20]")
  26 + @Schema(description = "分类编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[10, 20]")
27 27 @NotEmpty(message = "分类编号不能为空")
28 28 private List<Long> categoryIds;
29 29  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponPageReqVO.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;
2 2  
3 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.coupon.CouponStatusEnum;
4 6 import io.swagger.v3.oas.annotations.media.Schema;
5 7 import lombok.Data;
6 8 import lombok.EqualsAndHashCode;
... ... @@ -13,6 +15,7 @@ import lombok.ToString;
13 15 public class AppCouponPageReqVO extends PageParam {
14 16  
15 17 @Schema(description = "优惠劵状态", example = "1")
  18 + @InEnum(value = CouponStatusEnum.class, message = "优惠劵状态,必须是 {value}")
16 19 private Integer status;
17 20  
18 21 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplatePageReqVO.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template;
2 2  
3 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.common.PromotionProductScopeEnum;
4 6 import io.swagger.v3.oas.annotations.media.Schema;
5 7 import lombok.Data;
6 8 import lombok.EqualsAndHashCode;
... ... @@ -12,8 +14,10 @@ import lombok.ToString;
12 14 @ToString(callSuper = true)
13 15 public class AppCouponTemplatePageReqVO extends PageParam {
14 16  
15   - @Schema(description = "使用类型", example = "1")
16   - // TODO 芋艿:这里要限制下枚举的使用
17   - private Integer useType;
  17 + @Schema(description = "商品范围", example = "1")
  18 + @InEnum(value = PromotionProductScopeEnum.class, message = "商品范围,必须是 {value}")
  19 + private Integer productScope;
18 20  
  21 + @Schema(description = "商品标号", example = "1")
  22 + private Long spuId;
19 23 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
... ... @@ -3,6 +3,10 @@ package cn.iocoder.yudao.module.promotion.convert.coupon;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
5 5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
  6 +import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
  7 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
  8 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
  9 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
6 10 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
7 11 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
8 12 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
... ... @@ -11,6 +15,8 @@ import org.mapstruct.Mapper;
11 15 import org.mapstruct.factory.Mappers;
12 16  
13 17 import java.time.LocalDateTime;
  18 +import java.util.Collection;
  19 +import java.util.List;
14 20  
15 21 /**
16 22 * 优惠劵 Convert
... ... @@ -49,4 +55,10 @@ public interface CouponConvert {
49 55 }
50 56 return couponDO;
51 57 }
  58 +
  59 + CouponPageReqVO convert(AppCouponPageReqVO pageReqVO, Collection<Long> userIds);
  60 +
  61 + PageResult<AppCouponRespVO> convertAppPage(PageResult<CouponDO> pageResult);
  62 +
  63 + List<AppCouponMatchRespVO> convertList(List<CouponDO> list);
52 64 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
1 1 package cn.iocoder.yudao.module.promotion.convert.coupon;
2 2  
  3 +import cn.hutool.core.map.MapUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
  6 +import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
5 7 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateRespVO;
6 8 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
  9 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO;
  10 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplateRespVO;
7 11 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
8 12 import org.mapstruct.Mapper;
9 13 import org.mapstruct.factory.Mappers;
10 14  
  15 +import java.util.List;
  16 +import java.util.Map;
  17 +
11 18 /**
12 19 * 优惠劵模板 Convert
13 20 *
... ... @@ -26,4 +33,25 @@ public interface CouponTemplateConvert {
26 33  
27 34 PageResult<CouponTemplateRespVO> convertPage(PageResult<CouponTemplateDO> page);
28 35  
  36 + CouponTemplatePageReqVO convert(AppCouponTemplatePageReqVO pageReqVO, List<Integer> canTakeTypes, Integer productScope, Long productScopeValue);
  37 +
  38 + PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult);
  39 +
  40 + default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Integer> couponTakeCountMap) {
  41 + PageResult<AppCouponTemplateRespVO> result = convertAppPage(pageResult);
  42 + if (MapUtil.isEmpty(couponTakeCountMap)) {
  43 + return result;
  44 + }
  45 +
  46 + for (AppCouponTemplateRespVO vo : result.getList()) {
  47 + // 每人领取数量无限制
  48 + if (vo.getTakeLimitCount() == -1) {
  49 + vo.setTakeStatus(false);
  50 + continue;
  51 + }
  52 + // 检查已领取数量是否超过限领数量
  53 + vo.setTakeStatus(MapUtil.getInt(couponTakeCountMap, vo.getId(), 0) >= vo.getTakeLimitCount());
  54 + }
  55 + return result;
  56 + }
29 57 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java
... ... @@ -110,7 +110,7 @@ public class RewardActivityDO extends BaseDO {
110 110 */
111 111 private List<Long> couponIds;
112 112 /**
113   - * 赠送的优惠卷数量的数组
  113 + * 赠送的优惠券数量的数组
114 114 */
115 115 private List<Integer> couponCounts;
116 116  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
1 1 package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
2 2  
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import cn.hutool.core.util.StrUtil;
3 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 7 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 8 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
7 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
  10 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
  11 +import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
8 12 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  13 +import com.github.yulichang.toolkit.MPJWrappers;
9 14 import org.apache.ibatis.annotations.Mapper;
10 15  
  16 +import java.time.LocalDateTime;
11 17 import java.util.Collection;
12 18 import java.util.List;
  19 +import java.util.function.Function;
  20 +import java.util.stream.Collectors;
13 21  
14 22 /**
15 23 * 优惠劵 Mapper
... ... @@ -19,11 +27,11 @@ import java.util.List;
19 27 @Mapper
20 28 public interface CouponMapper extends BaseMapperX<CouponDO> {
21 29  
22   - default PageResult<CouponDO> selectPage(CouponPageReqVO reqVO, Collection<Long> userIds) {
  30 + default PageResult<CouponDO> selectPage(CouponPageReqVO reqVO) {
23 31 return selectPage(reqVO, new LambdaQueryWrapperX<CouponDO>()
24 32 .eqIfPresent(CouponDO::getTemplateId, reqVO.getTemplateId())
25 33 .eqIfPresent(CouponDO::getStatus, reqVO.getStatus())
26   - .inIfPresent(CouponDO::getUserId, userIds)
  34 + .inIfPresent(CouponDO::getUserId, reqVO.getUserIds())
27 35 .betweenIfPresent(CouponDO::getCreateTime, reqVO.getCreateTime())
28 36 .orderByDesc(CouponDO::getId));
29 37 }
... ... @@ -62,4 +70,36 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; {
62 70 );
63 71 }
64 72  
  73 + default List<CouponTakeCountBO> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) {
  74 + return BeanUtil.copyToList(selectMaps(MPJWrappers.lambdaJoin(CouponDO.class)
  75 + .select(CouponDO::getTemplateId)
  76 + .selectCount(CouponDO::getId, CouponTakeCountBO::getCount)
  77 + .eq(CouponDO::getUserId, userId)
  78 + .in(CouponDO::getTemplateId, templateIds)
  79 + .groupBy(CouponDO::getTemplateId)), CouponTakeCountBO.class);
  80 + }
  81 +
  82 + default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope(
  83 + Long userId, Integer status, Integer usePrice, List<Long> spuIds, List<Long> categoryIds) {
  84 +
  85 + Function<List<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
  86 + .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id))
  87 + .collect(Collectors.joining(" OR "));
  88 + return selectList(new LambdaQueryWrapperX<CouponDO>()
  89 + .eq(CouponDO::getUserId, userId)
  90 + .eq(CouponDO::getStatus, status)
  91 + .le(CouponDO::getUsePrice, usePrice)
  92 + .and(w -> w.eq(CouponDO::getProductScope, PromotionProductScopeEnum.ALL.getScope())
  93 + .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.SPU.getScope())
  94 + .apply(productScopeValuesFindInSetFunc.apply(spuIds)))
  95 + .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope())
  96 + .apply(productScopeValuesFindInSetFunc.apply(categoryIds)))));
  97 + }
  98 +
  99 + default List<CouponDO> selectListByStatusAndValidEndTimeLe(Integer status, LocalDateTime validEndTime) {
  100 + return selectList(new LambdaQueryWrapperX<CouponDO>()
  101 + .eq(CouponDO::getStatus, status)
  102 + .le(CouponDO::getValidEndTime, validEndTime)
  103 + );
  104 + }
65 105 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
... ... @@ -39,6 +39,9 @@ public interface CouponTemplateMapper extends BaseMapperX&lt;CouponTemplateDO&gt; {
39 39 .eqIfPresent(CouponTemplateDO::getStatus, reqVO.getStatus())
40 40 .eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType())
41 41 .betweenIfPresent(CouponTemplateDO::getCreateTime, reqVO.getCreateTime())
  42 + .eqIfPresent(CouponTemplateDO::getProductScope, reqVO.getProductScope())
  43 + .and(reqVO.getProductScopeValue() != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)",
  44 + reqVO.getProductScopeValue()))
42 45 .and(canTakeConsumer != null, canTakeConsumer)
43 46 .orderByDesc(CouponTemplateDO::getId));
44 47 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/CouponExpireJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.job;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
  5 +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
  6 +import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 优惠券过期 Job
  13 + *
  14 + * @author owen
  15 + */
  16 +@Component
  17 +@TenantJob
  18 +public class CouponExpireJob implements JobHandler {
  19 +
  20 + @Resource
  21 + private CouponService couponService;
  22 +
  23 + @Override
  24 + public String execute(String param) {
  25 + int count = couponService.expireCoupon();
  26 + return StrUtil.format("过期优惠券 {} 个", count);
  27 + }
  28 +
  29 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
... ... @@ -3,12 +3,18 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
  6 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
6 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
7 8 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
  9 +import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
8 10  
  11 +import java.util.Collection;
9 12 import java.util.List;
  13 +import java.util.Map;
10 14 import java.util.Set;
11 15  
  16 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  17 +
12 18 /**
13 19 * 优惠劵 Service 接口
14 20 *
... ... @@ -18,11 +24,11 @@ public interface CouponService {
18 24  
19 25 /**
20 26 * 校验优惠劵,包括状态、有限期
21   - *
  27 + * <p>
22 28 * 1. 如果校验通过,则返回优惠劵信息
23 29 * 2. 如果校验不通过,则直接抛出业务异常
24 30 *
25   - * @param id 优惠劵编号
  31 + * @param id 优惠劵编号
26 32 * @param userId 用户编号
27 33 * @return 优惠劵信息
28 34 */
... ... @@ -31,9 +37,8 @@ public interface CouponService {
31 37 /**
32 38 * 校验优惠劵,包括状态、有限期
33 39 *
34   - * @see #validCoupon(Long, Long) 逻辑相同,只是入参不同
35   - *
36 40 * @param coupon 优惠劵
  41 + * @see #validCoupon(Long, Long) 逻辑相同,只是入参不同
37 42 */
38 43 void validCoupon(CouponDO coupon);
39 44  
... ... @@ -124,4 +129,39 @@ public interface CouponService {
124 129 takeCoupon(templateId, CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER);
125 130 }
126 131  
  132 + /**
  133 + * 统计会员领取优惠券的数量
  134 + *
  135 + * @param templateIds 优惠券模板编号列表
  136 + * @param userId 用户编号
  137 + * @return 领取优惠券的数量
  138 + */
  139 + default Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
  140 + return convertMap(getTakeCountListByTemplateIds(templateIds, userId), CouponTakeCountBO::getTemplateId, CouponTakeCountBO::getCount);
  141 + }
  142 +
  143 + /**
  144 + * 统计会员领取优惠券的数量
  145 + *
  146 + * @param templateIds 优惠券模板编号列表
  147 + * @param userId 用户编号
  148 + * @return 领取优惠券的数量
  149 + */
  150 + List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId);
  151 +
  152 + /**
  153 + * 获取用户匹配的优惠券列表
  154 + *
  155 + * @param userId 用户编号
  156 + * @param matchReqVO 匹配参数
  157 + * @return 优惠券列表
  158 + */
  159 + List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);
  160 +
  161 + /**
  162 + * 过期优惠券
  163 + *
  164 + * @return 过期数量
  165 + */
  166 + int expireCoupon();
127 167 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
... ... @@ -2,15 +2,18 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
2 2  
3 3 import cn.hutool.core.collection.CollStreamUtil;
4 4 import cn.hutool.core.collection.CollUtil;
  5 +import cn.hutool.core.collection.ListUtil;
5 6 import cn.hutool.core.map.MapUtil;
6 7 import cn.hutool.core.util.ObjectUtil;
7 8 import cn.hutool.core.util.StrUtil;
  9 +import cn.hutool.extra.spring.SpringUtil;
8 10 import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 11 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
10 12 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
11 13 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
12 14 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
13 15 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
  16 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
14 17 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
15 18 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
16 19 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
... ... @@ -18,12 +21,15 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
18 21 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
19 22 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
20 23 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
  24 +import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
  25 +import lombok.extern.slf4j.Slf4j;
21 26 import org.springframework.stereotype.Service;
22 27 import org.springframework.transaction.annotation.Transactional;
23 28 import org.springframework.validation.annotation.Validated;
24 29  
25 30 import javax.annotation.Resource;
26 31 import java.time.LocalDateTime;
  32 +import java.util.Collection;
27 33 import java.util.List;
28 34 import java.util.Map;
29 35 import java.util.Set;
... ... @@ -39,6 +45,7 @@ import static java.util.Arrays.asList;
39 45 *
40 46 * @author 芋道源码
41 47 */
  48 +@Slf4j
42 49 @Service
43 50 @Validated
44 51 public class CouponServiceImpl implements CouponService {
... ... @@ -77,16 +84,16 @@ public class CouponServiceImpl implements CouponService {
77 84 @Override
78 85 public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
79 86 // 获得用户编号
80   - Set<Long> userIds = null;
81 87 if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
82   - userIds = CollectionUtils.convertSet(memberUserApi.getUserListByNickname(pageReqVO.getNickname()),
  88 + Set<Long> userIds = CollectionUtils.convertSet(memberUserApi.getUserListByNickname(pageReqVO.getNickname()),
83 89 MemberUserRespDTO::getId);
84 90 if (CollUtil.isEmpty(userIds)) {
85 91 return PageResult.empty();
86 92 }
  93 + pageReqVO.setUserIds(userIds);
87 94 }
88 95 // 分页查询
89   - return couponMapper.selectPage(pageReqVO, userIds);
  96 + return couponMapper.selectPage(pageReqVO);
90 97 }
91 98  
92 99 @Override
... ... @@ -175,6 +182,57 @@ public class CouponServiceImpl implements CouponService {
175 182 couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size());
176 183 }
177 184  
  185 + @Override
  186 + public List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId) {
  187 + if (CollUtil.isEmpty(templateIds)) {
  188 + return ListUtil.empty();
  189 + }
  190 + return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
  191 + }
  192 +
  193 + @Override
  194 + public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
  195 + return couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId, CouponStatusEnum.UNUSED.getStatus(),
  196 + matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());
  197 + }
  198 +
  199 + @Override
  200 + public int expireCoupon() {
  201 + // 1. 查询待过期的优惠券
  202 + List<CouponDO> list = couponMapper.selectListByStatusAndValidEndTimeLe(
  203 + CouponStatusEnum.UNUSED.getStatus(), LocalDateTime.now());
  204 + if (CollUtil.isEmpty(list)) {
  205 + return 0;
  206 + }
  207 +
  208 + // 2. 遍历执行
  209 + int count = 0;
  210 + for (CouponDO coupon : list) {
  211 + try {
  212 + boolean success = getSelf().expireCoupon(coupon);
  213 + if (success) {
  214 + count++;
  215 + }
  216 + } catch (Exception e) {
  217 + log.error("[expireCoupon][coupon({}) 更新为已过期失败]", coupon.getId(), e);
  218 + }
  219 + }
  220 + return count;
  221 + }
  222 +
  223 + private boolean expireCoupon(CouponDO coupon) {
  224 + // 更新记录状态
  225 + CouponDO updateObj = new CouponDO().setStatus(CouponStatusEnum.EXPIRE.getStatus());
  226 + int updateRows = couponMapper.updateByIdAndStatus(coupon.getId(), CouponStatusEnum.UNUSED.getStatus(), updateObj);
  227 + if (updateRows == 0) {
  228 + log.error("[expireCoupon][coupon({}) 更新为已过期失败]", coupon.getId());
  229 + return false;
  230 + }
  231 +
  232 + log.info("[expireCoupon][coupon({}) 更新为已过期成功]", coupon.getId());
  233 + return true;
  234 + }
  235 +
178 236 /**
179 237 * 校验优惠券是否可以领取
180 238 *
... ... @@ -211,7 +269,7 @@ public class CouponServiceImpl implements CouponService {
211 269 /**
212 270 * 过滤掉达到领取上线的用户
213 271 *
214   - * @param userIds 用户编号数组
  272 + * @param userIds 用户编号数组
215 273 * @param couponTemplate 优惠劵模版
216 274 */
217 275 private void removeTakeLimitUser(Set<Long> userIds, CouponTemplateDO couponTemplate) {
... ... @@ -228,4 +286,12 @@ public class CouponServiceImpl implements CouponService {
228 286 userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount());
229 287 }
230 288  
  289 + /**
  290 + * 获得自身的代理对象,解决 AOP 生效问题
  291 + *
  292 + * @return 自己
  293 + */
  294 + private CouponServiceImpl getSelf() {
  295 + return SpringUtil.getBean(getClass());
  296 + }
231 297 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/bo/CouponTakeCountBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.service.coupon.bo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 优惠券领取数量 BO
  7 + *
  8 + * @author owen
  9 + */
  10 +@Data
  11 +public class CouponTakeCountBO {
  12 + /**
  13 + * 优惠劵模板编号
  14 + */
  15 + private Long templateId;
  16 + /**
  17 + * 领取数量
  18 + */
  19 + private Integer count;
  20 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java
... ... @@ -10,10 +10,10 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
10 10 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
11 11 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
12 12 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
13   -import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
14   -import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
15 13 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
16 14 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
  15 +import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
  16 +import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
17 17 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
18 18 import io.swagger.v3.oas.annotations.Operation;
19 19 import io.swagger.v3.oas.annotations.Parameter;
... ... @@ -92,20 +92,17 @@ public class BrokerageUserController {
92 92 Set<Long> userIds = convertSet(pageResult.getList(), BrokerageUserDO::getId);
93 93 // 查询用户信息
94 94 Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
95   - // TODO @疯狂:看看下面两个 getBrokerageUserCountByBindUserId、getWithdrawSummaryByUserId 有没可能一次性出结果,不然 n 次有点太花性能了;
96 95 // 合计分佣订单
97   - Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = convertMap(userIds,
98   - userId -> userId,
99   - userId -> brokerageRecordService.getUserBrokerageSummaryByUserId(userId,
100   - BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus()));
  96 + Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = brokerageRecordService.getUserBrokerageSummaryMapByUserId(
  97 + userIds, BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus());
  98 + // TODO @芋艿:看看下面 getBrokerageUserCountByBindUserId 有没可能一次性出结果,不然 n 次有点太花性能了;
101 99 // 合计推广用户数量
102 100 Map<Long, Long> brokerageUserCountMap = convertMap(userIds,
103 101 userId -> userId,
104 102 userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId, null));
105 103 // 合计提现
106   - Map<Long, UserWithdrawSummaryBO> withdrawMap = convertMap(userIds,
107   - userId -> userId,
108   - userId -> brokerageWithdrawService.getWithdrawSummaryByUserId(userId, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS));
  104 + Map<Long, UserWithdrawSummaryBO> withdrawMap = brokerageWithdrawService.getWithdrawSummaryMapByUserId(userIds,
  105 + BrokerageWithdrawStatusEnum.AUDIT_SUCCESS);
109 106 // 拼接返回
110 107 return success(BrokerageUserConvert.INSTANCE.convertPage(pageResult, userMap, brokerageUserCountMap,
111 108 userOrderSummaryMap, withdrawMap));
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java
... ... @@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.*;
27 27 import javax.annotation.Resource;
28 28 import javax.validation.Valid;
29 29 import java.time.LocalDateTime;
  30 +import java.util.Collections;
30 31 import java.util.Map;
31 32 import java.util.Optional;
32 33  
... ... @@ -76,23 +77,25 @@ public class AppBrokerageUserController {
76 77 @Operation(summary = "获得个人分销统计")
77 78 @PreAuthenticated
78 79 public CommonResult<AppBrokerageUserMySummaryRespVO> getBrokerageUserSummary() {
79   - Long userId = getLoginUserId();
80   - // TODO @疯狂:后面这种,要不也改成 convert;感觉 controller 这样更容易看到整体;核心其实是 86、8/87、9/90、9/91 这阶段
81   - // 统计 yesterdayPrice、withdrawPrice、firstBrokerageUserCount、secondBrokerageUserCount 字段
  80 + // 查询当前登录用户信息
  81 + BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(getLoginUserId());
  82 + // 统计用户昨日的佣金
82 83 LocalDateTime yesterday = LocalDateTime.now().minusDays(1);
83 84 LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(yesterday);
84 85 LocalDateTime endTime = LocalDateTimeUtil.endOfDay(yesterday);
85   - AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO()
86   - .setYesterdayPrice(brokerageRecordService.getSummaryPriceByUserId(userId, BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime))
87   - .setWithdrawPrice(Optional.ofNullable(brokerageWithdrawService.getWithdrawSummaryByUserId(userId, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS))
88   - .map(UserWithdrawSummaryBO::getPrice).orElse(0))
89   - .setBrokeragePrice(0).setFrozenPrice(0)
90   - .setFirstBrokerageUserCount(brokerageUserService.getBrokerageUserCountByBindUserId(userId, 1))
91   - .setSecondBrokerageUserCount(brokerageUserService.getBrokerageUserCountByBindUserId(userId, 2));
92   - // 设置 brokeragePrice、frozenPrice 字段
93   - Optional.ofNullable(brokerageUserService.getBrokerageUser(userId))
94   - .ifPresent(user -> respVO.setBrokeragePrice(user.getBrokeragePrice()).setFrozenPrice(user.getFrozenPrice()));
95   - return success(respVO);
  86 + Integer yesterdayPrice = brokerageRecordService.getSummaryPriceByUserId(brokerageUser.getId(),
  87 + BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime);
  88 + // 统计用户提现的佣金
  89 + Integer withdrawPrice = brokerageWithdrawService.getWithdrawSummaryByUserId(Collections.singleton(brokerageUser.getId()),
  90 + BrokerageWithdrawStatusEnum.AUDIT_SUCCESS).stream()
  91 + .findFirst().map(UserWithdrawSummaryBO::getPrice).orElse(0);
  92 + // 统计分销用户数量(一级)
  93 + Long firstBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(brokerageUser.getId(), 1);
  94 + // 统计分销用户数量(二级)
  95 + Long secondBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(brokerageUser.getId(), 2);
  96 +
  97 + // 拼接返回
  98 + return success(BrokerageUserConvert.INSTANCE.convert(yesterdayPrice, withdrawPrice, firstBrokerageUserCount, secondBrokerageUserCount, brokerageUser));
96 99 }
97 100  
98 101 @GetMapping("/rank-page-by-user-count")
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
... ... @@ -37,6 +37,8 @@ public class AppTradeOrderSettlementRespVO {
37 37  
38 38 // ========== SPU 信息 ==========
39 39  
  40 + @Schema(description = "品类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  41 + private Long categoryId;
40 42 @Schema(description = "SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
41 43 private Long spuId;
42 44 @Schema(description = "SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "Apple iPhone 12")
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageUserConvert.java
1 1 package cn.iocoder.yudao.module.trade.convert.brokerage;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.ObjUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
6 7 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserRespVO;
  8 +import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserMySummaryRespVO;
7 9 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankByUserCountRespVO;
8 10 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
9 11 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
... ... @@ -71,4 +73,18 @@ public interface BrokerageUserConvert {
71 73  
72 74 void copyTo(MemberUserRespDTO from, @MappingTarget AppBrokerageUserRankByUserCountRespVO to);
73 75  
  76 + default AppBrokerageUserMySummaryRespVO convert(Integer yesterdayPrice, Integer withdrawPrice,
  77 + Long firstBrokerageUserCount, Long secondBrokerageUserCount,
  78 + BrokerageUserDO brokerageUser) {
  79 + AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO()
  80 + .setYesterdayPrice(ObjUtil.defaultIfNull(yesterdayPrice, 0))
  81 + .setWithdrawPrice(ObjUtil.defaultIfNull(withdrawPrice, 0))
  82 + .setBrokeragePrice(0).setFrozenPrice(0)
  83 + .setFirstBrokerageUserCount(ObjUtil.defaultIfNull(firstBrokerageUserCount, 0L))
  84 + .setSecondBrokerageUserCount(ObjUtil.defaultIfNull(secondBrokerageUserCount, 0L));
  85 + // 设置 brokeragePrice、frozenPrice 字段
  86 + Optional.ofNullable(brokerageUser)
  87 + .ifPresent(user -> respVO.setBrokeragePrice(user.getBrokeragePrice()).setFrozenPrice(user.getFrozenPrice()));
  88 + return respVO;
  89 + }
74 90 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
1 1 package cn.iocoder.yudao.module.trade.dal.mysql.brokerage;
2 2  
  3 +import cn.hutool.core.bean.BeanUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
... ... @@ -9,12 +10,15 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
9 10 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
10 11 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
11 12 import com.baomidou.mybatisplus.core.metadata.IPage;
  13 +import com.github.yulichang.toolkit.MPJWrappers;
12 14 import org.apache.ibatis.annotations.Mapper;
13 15 import org.apache.ibatis.annotations.Param;
14 16 import org.apache.ibatis.annotations.Select;
15 17  
16 18 import java.time.LocalDateTime;
  19 +import java.util.Collection;
17 20 import java.util.List;
  21 +import java.util.Map;
18 22  
19 23 /**
20 24 * 佣金记录 Mapper
... ... @@ -52,11 +56,27 @@ public interface BrokerageRecordMapper extends BaseMapperX&lt;BrokerageRecordDO&gt; {
52 56 BrokerageRecordDO::getUserId, userId);
53 57 }
54 58  
55   - @Select("SELECT COUNT(1), SUM(price) FROM trade_brokerage_record " +
56   - "WHERE user_id = #{userId} AND biz_type = #{bizType} AND status = #{status} AND deleted = FALSE")
57   - UserBrokerageSummaryBO selectCountAndSumPriceByUserIdAndBizTypeAndStatus(@Param("userId") Long userId,
58   - @Param("bizType") Integer bizType,
59   - @Param("status") Integer status);
  59 + default List<UserBrokerageSummaryBO> selectCountAndSumPriceByUserIdInAndBizTypeAndStatus(Collection<Long> userIds,
  60 + Integer bizType,
  61 + Integer status) {
  62 + List<Map<String, Object>> list = selectMaps(MPJWrappers.lambdaJoin(BrokerageRecordDO.class)
  63 + .select(BrokerageRecordDO::getUserId)
  64 + .selectCount(BrokerageRecordDO::getId, UserBrokerageSummaryBO::getCount)
  65 + .selectSum(BrokerageRecordDO::getPrice)
  66 + .in(BrokerageRecordDO::getUserId, userIds)
  67 + .eq(BrokerageRecordDO::getBizId, bizType)
  68 + .eq(BrokerageRecordDO::getStatus, status)
  69 + .groupBy(BrokerageRecordDO::getUserId));
  70 + return BeanUtil.copyToList(list, UserBrokerageSummaryBO.class);
  71 +// return selectJoinList(UserBrokerageSummaryBO.class, MPJWrappers.lambdaJoin(BrokerageRecordDO.class)
  72 +// .select(BrokerageRecordDO::getUserId)
  73 +// .selectCount(BrokerageRecordDO::getId, UserBrokerageSummaryBO::getCount)
  74 +// .selectSum(BrokerageRecordDO::getPrice)
  75 +// .in(BrokerageRecordDO::getUserId, userIds)
  76 +// .eq(BrokerageRecordDO::getBizId, bizType)
  77 +// .eq(BrokerageRecordDO::getStatus, status)
  78 +// .groupBy(BrokerageRecordDO::getUserId));
  79 + }
60 80  
61 81 @Select("SELECT SUM(price) FROM trade_brokerage_record " +
62 82 "WHERE user_id = #{userId} AND biz_type = #{bizType} " +
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
1 1 package cn.iocoder.yudao.module.trade.dal.mysql.brokerage;
2 2  
  3 +import cn.hutool.core.bean.BeanUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
... ... @@ -7,9 +8,12 @@ import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.Brok
7 8 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
8 9 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
9 10 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  11 +import com.github.yulichang.wrapper.MPJLambdaWrapper;
10 12 import org.apache.ibatis.annotations.Mapper;
11   -import org.apache.ibatis.annotations.Param;
12   -import org.apache.ibatis.annotations.Select;
  13 +
  14 +import java.util.Collection;
  15 +import java.util.List;
  16 +import java.util.Map;
13 17  
14 18 /**
15 19 * 佣金提现 Mapper
... ... @@ -37,9 +41,23 @@ public interface BrokerageWithdrawMapper extends BaseMapperX&lt;BrokerageWithdrawDO
37 41 .eq(BrokerageWithdrawDO::getStatus, status));
38 42 }
39 43  
40   - @Select("SELECT COUNT(1) AS count, SUM(price) AS price FROM trade_brokerage_withdraw " +
41   - "WHERE user_id = #{userId} AND status = #{status} AND deleted = FALSE")
42   - UserWithdrawSummaryBO selectCountAndSumPriceByUserIdAndStatus(@Param("userId") Long userId,
43   - @Param("status") Integer status);
  44 + default List<UserWithdrawSummaryBO> selectCountAndSumPriceByUserIdAndStatus(Collection<Long> userIds, Integer status) {
  45 + List<Map<String, Object>> list = selectMaps(new MPJLambdaWrapper<BrokerageWithdrawDO>()
  46 + .select(BrokerageWithdrawDO::getUserId)
  47 + .selectCount(BrokerageWithdrawDO::getId, UserWithdrawSummaryBO::getCount)
  48 + .selectSum(BrokerageWithdrawDO::getPrice)
  49 + .in(BrokerageWithdrawDO::getUserId, userIds)
  50 + .eq(BrokerageWithdrawDO::getStatus, status)
  51 + .groupBy(BrokerageWithdrawDO::getUserId));
  52 + return BeanUtil.copyToList(list, UserWithdrawSummaryBO.class);
  53 + // selectJoinList有BUG,会与租户插件冲突:解析SQL时,发生异常 https://gitee.com/best_handsome/mybatis-plus-join/issues/I84GYW
  54 +// return selectJoinList(UserWithdrawSummaryBO.class, new MPJLambdaWrapper<BrokerageWithdrawDO>()
  55 +// .select(BrokerageWithdrawDO::getUserId)
  56 +// .selectCount(BrokerageWithdrawDO::getId, UserWithdrawSummaryBO::getCount)
  57 +// .selectSum(BrokerageWithdrawDO::getPrice)
  58 +// .in(BrokerageWithdrawDO::getUserId, userIds)
  59 +// .eq(BrokerageWithdrawDO::getStatus, status)
  60 +// .groupBy(BrokerageWithdrawDO::getUserId));
  61 + }
44 62  
45 63 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
... ... @@ -85,7 +85,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
85 85  
86 86 @Override
87 87 public TradeAfterSaleDO getAfterSale(Long userId, Long id) {
88   - return tradeAfterSaleMapper.selectByIdAndUserId(id, userId);
  88 + return tradeAfterSaleMapper.selectByIdAndUserId(id, userId);
89 89 }
90 90  
91 91 @Override
... ... @@ -167,9 +167,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
167 167 tradeAfterSaleMapper.insert(afterSale);
168 168  
169 169 // 更新交易订单项的售后状态
170   - tradeOrderUpdateService.updateOrderItemAfterSaleStatus(orderItem.getId(),
171   - TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
172   - afterSale.getId(), null);
  170 + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCreate(orderItem.getId(), afterSale.getId());
173 171  
174 172 // 记录售后日志
175 173 createAfterSaleLog(orderItem.getUserId(), UserTypeEnum.MEMBER.getValue(),
... ... @@ -219,8 +217,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
219 217 // TODO 发送售后消息
220 218  
221 219 // 更新交易订单项的售后状态为【未申请】
222   - tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
223   - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
  220 + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
224 221 }
225 222  
226 223 /**
... ... @@ -313,8 +310,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
313 310 // TODO 发送售后消息
314 311  
315 312 // 更新交易订单项的售后状态为【未申请】
316   - tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
317   - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
  313 + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
318 314 }
319 315  
320 316 /**
... ... @@ -360,9 +356,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
360 356 // TODO 发送售后消息
361 357  
362 358 // 更新交易订单项的售后状态为【已完成】
363   - tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
364   - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(),
365   - null, afterSale.getRefundPrice());
  359 + tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice());
366 360 }
367 361  
368 362 private void createPayRefund(String userIp, TradeAfterSaleDO afterSale) {
... ... @@ -403,8 +397,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
403 397 // TODO 发送售后消息
404 398  
405 399 // 更新交易订单项的售后状态为【未申请】
406   - tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
407   - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
  400 + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
408 401 }
409 402  
410 403 @Override
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java
... ... @@ -12,7 +12,11 @@ import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO
12 12  
13 13 import javax.validation.Valid;
14 14 import java.time.LocalDateTime;
  15 +import java.util.Collection;
15 16 import java.util.List;
  17 +import java.util.Map;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
16 20  
17 21 /**
18 22 * 佣金记录 Service 接口
... ... @@ -76,12 +80,24 @@ public interface BrokerageRecordService {
76 80 /**
77 81 * 汇总用户佣金
78 82 *
79   - * @param userId 用户编号
  83 + * @param userIds 用户编号
  84 + * @param bizType 业务类型
  85 + * @param status 佣金状态
  86 + * @return 用户佣金汇总
  87 + */
  88 + List<UserBrokerageSummaryBO> getUserBrokerageSummaryByUserId(Collection<Long> userIds, Integer bizType, Integer status);
  89 +
  90 + /**
  91 + * 汇总用户佣金
  92 + *
  93 + * @param userIds 用户编号
80 94 * @param bizType 业务类型
81 95 * @param status 佣金状态
82 96 * @return 用户佣金汇总
83 97 */
84   - UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status);
  98 + default Map<Long, UserBrokerageSummaryBO> getUserBrokerageSummaryMapByUserId(Collection<Long> userIds, Integer bizType, Integer status) {
  99 + return convertMap(getUserBrokerageSummaryByUserId(userIds, bizType, status), UserBrokerageSummaryBO::getUserId);
  100 + }
85 101  
86 102 /**
87 103 * 获得用户佣金合计
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java
... ... @@ -36,10 +36,7 @@ import org.springframework.validation.annotation.Validated;
36 36  
37 37 import javax.annotation.Resource;
38 38 import java.time.LocalDateTime;
39   -import java.util.ArrayList;
40   -import java.util.List;
41   -import java.util.Objects;
42   -import java.util.Optional;
  39 +import java.util.*;
43 40  
44 41 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
45 42 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
... ... @@ -235,9 +232,8 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
235 232 }
236 233  
237 234 @Override
238   - public UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status) {
239   - UserBrokerageSummaryBO summaryBO = brokerageRecordMapper.selectCountAndSumPriceByUserIdAndBizTypeAndStatus(userId, bizType, status);
240   - return summaryBO != null ? summaryBO : new UserBrokerageSummaryBO(0, 0);
  235 + public List<UserBrokerageSummaryBO> getUserBrokerageSummaryByUserId(Collection<Long> userIds, Integer bizType, Integer status) {
  236 + return brokerageRecordMapper.selectCountAndSumPriceByUserIdInAndBizTypeAndStatus(userIds, bizType, status);
241 237 }
242 238  
243 239 @Override
... ... @@ -338,7 +334,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
338 334 if (BooleanUtil.isTrue(spu.getSubCommissionType())) {
339 335 fixedMinPrice = getMinValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice);
340 336 fixedMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice);
341   - // 3.2 全局分佣模式(根据商品价格比例计算)
  337 + // 3.2 全局分佣模式(根据商品价格比例计算)
342 338 } else {
343 339 spuMinPrice = getMinValue(skuList, ProductSkuRespDTO::getPrice);
344 340 spuMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getPrice);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java
1 1 package cn.iocoder.yudao.module.trade.service.brokerage;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
5 4 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO;
6 5 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryPageReqVO;
7 6 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO;
... ... @@ -111,21 +110,7 @@ public interface BrokerageUserService {
111 110 * @param registerTime 用户注册时间
112 111 * @return 是否绑定
113 112 */
114   - default boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime) {
115   - // 注册时间在30秒内的,都算新用户
116   - boolean isNewUser = LocalDateTimeUtils.afterNow(registerTime.minusSeconds(30));
117   - return bindBrokerageUser(userId, bindUserId, isNewUser);
118   - }
119   -
120   - /**
121   - * 【会员】绑定推广员
122   - *
123   - * @param userId 用户编号
124   - * @param bindUserId 推广员编号
125   - * @param isNewUser 是否为新用户
126   - * @return 是否绑定
127   - */
128   - boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser);
  113 + boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime);
129 114  
130 115 /**
131 116 * 获取用户是否有分销资格
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java
... ... @@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
5 5 import cn.hutool.core.util.ArrayUtil;
6 6 import cn.hutool.core.util.BooleanUtil;
7 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
8 9 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
9 10 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO;
10 11 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryPageReqVO;
... ... @@ -154,7 +155,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
154 155 }
155 156  
156 157 @Override
157   - public boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser) {
  158 + public boolean bindBrokerageUser(Long userId, Long bindUserId, LocalDateTime registerTime) {
158 159 // 1. 获得分销用户
159 160 boolean isNewBrokerageUser = false;
160 161 BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(userId);
... ... @@ -164,7 +165,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
164 165 }
165 166  
166 167 // 2.1 校验是否能绑定用户
167   - boolean validated = isUserCanBind(brokerageUser, isNewUser);
  168 + boolean validated = isUserCanBind(brokerageUser, registerTime);
168 169 if (!validated) {
169 170 return false;
170 171 }
... ... @@ -222,7 +223,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
222 223 return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
223 224 }
224 225  
225   - private boolean isUserCanBind(BrokerageUserDO user, Boolean isNewUser) {
  226 + private boolean isUserCanBind(BrokerageUserDO user, LocalDateTime registerTime) {
226 227 // 校验分销功能是否启用
227 228 TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig();
228 229 if (tradeConfig == null || !BooleanUtil.isTrue(tradeConfig.getBrokerageEnabled())) {
... ... @@ -236,8 +237,9 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
236 237  
237 238 // 校验分销关系绑定模式
238 239 if (BrokerageBindModeEnum.REGISTER.getMode().equals(tradeConfig.getBrokerageBindMode())) {
239   - // TODO @疯狂:是不是把 isNewUser 挪到这里好点呀?
240   - if (!BooleanUtil.isTrue(isNewUser)) {
  240 + // 判断是否为新用户:注册时间在30秒内的,都算新用户
  241 + boolean isNotNewUser = LocalDateTimeUtils.beforeNow(registerTime.plusSeconds(30));
  242 + if (isNotNewUser) {
241 243 throw exception(BROKERAGE_BIND_MODE_REGISTER); // 只有在注册时可以绑定
242 244 }
243 245 } else if (BrokerageBindModeEnum.ANYTIME.getMode().equals(tradeConfig.getBrokerageBindMode())) {
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java
... ... @@ -7,6 +7,13 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawD
7 7 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
8 8 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
9 9  
  10 +import java.util.Collection;
  11 +import java.util.List;
  12 +import java.util.Map;
  13 +import java.util.Set;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  16 +
10 17 /**
11 18 * 佣金提现 Service 接口
12 19 *
... ... @@ -51,10 +58,20 @@ public interface BrokerageWithdrawService {
51 58 /**
52 59 * 汇总用户提现
53 60 *
54   - * @param userId 用户编号
55   - * @param status 提现状态
  61 + * @param userIds 用户编号
  62 + * @param status 提现状态
56 63 * @return 用户提现汇总
57 64 */
58   - UserWithdrawSummaryBO getWithdrawSummaryByUserId(Long userId, BrokerageWithdrawStatusEnum status);
  65 + List<UserWithdrawSummaryBO> getWithdrawSummaryByUserId(Collection<Long> userIds, BrokerageWithdrawStatusEnum status);
59 66  
  67 + /**
  68 + * 汇总用户提现
  69 + *
  70 + * @param userIds 用户编号
  71 + * @param status 提现状态
  72 + * @return 用户提现汇总
  73 + */
  74 + default Map<Long, UserWithdrawSummaryBO> getWithdrawSummaryMapByUserId(Set<Long> userIds, BrokerageWithdrawStatusEnum status) {
  75 + return convertMap(getWithdrawSummaryByUserId(userIds, status), UserWithdrawSummaryBO::getUserId);
  76 + }
60 77 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
... ... @@ -26,6 +26,8 @@ import org.springframework.validation.annotation.Validated;
26 26 import javax.annotation.Resource;
27 27 import javax.validation.Validator;
28 28 import java.time.LocalDateTime;
  29 +import java.util.Collection;
  30 +import java.util.List;
29 31 import java.util.Map;
30 32  
31 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -142,9 +144,8 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
142 144 }
143 145  
144 146 @Override
145   - public UserWithdrawSummaryBO getWithdrawSummaryByUserId(Long userId, BrokerageWithdrawStatusEnum status) {
146   - UserWithdrawSummaryBO summaryBO = brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userId, status.getStatus());
147   - return summaryBO != null ? summaryBO : new UserWithdrawSummaryBO(0, 0);
  147 + public List<UserWithdrawSummaryBO> getWithdrawSummaryByUserId(Collection<Long> userIds, BrokerageWithdrawStatusEnum status) {
  148 + return brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userIds, status.getStatus());
148 149 }
149 150  
150 151 /**
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java
... ... @@ -13,7 +13,10 @@ import lombok.NoArgsConstructor;
13 13 @NoArgsConstructor
14 14 @AllArgsConstructor
15 15 public class UserBrokerageSummaryBO {
16   -
  16 + /**
  17 + * 用户编号
  18 + */
  19 + private Long userId;
17 20 /**
18 21 * 佣金数量
19 22 */
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserWithdrawSummaryBO.java
... ... @@ -15,6 +15,11 @@ import lombok.NoArgsConstructor;
15 15 public class UserWithdrawSummaryBO {
16 16  
17 17 /**
  18 + * 用户编号
  19 + */
  20 + private Long userId;
  21 +
  22 + /**
18 23 * 提现次数
19 24 */
20 25 private Integer count;
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
1 1 package cn.iocoder.yudao.module.trade.service.order;
2 2  
3   -import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
4   -import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO;
5 3 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
6 4 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
  5 +import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
  6 +import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO;
7 7 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
8 8 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
9 9 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
10 10 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
11 11 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
12 12  
  13 +import javax.validation.constraints.NotNull;
  14 +
13 15 /**
14 16 * 交易订单【写】Service 接口
15 17 *
... ... @@ -116,27 +118,28 @@ public interface TradeOrderUpdateService {
116 118 // =================== Order Item ===================
117 119  
118 120 /**
119   - * 更新交易订单项的售后状态
  121 + * 当售后申请后,更新交易订单项的售后状态
  122 + *
  123 + * @param id 交易订单项编号
  124 + * @param afterSaleId 售后单编号
  125 + */
  126 + void updateOrderItemWhenAfterSaleCreate(@NotNull Long id, @NotNull Long afterSaleId);
  127 +
  128 +
  129 + /**
  130 + * 当售后完成后,更新交易订单项的售后状态
120 131 *
121   - * @param id 交易订单项编号
122   - * @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常
123   - * @param newAfterSaleStatus 目标售后状态
  132 + * @param id 交易订单项编号
  133 + * @param refundPrice 退款金额
124 134 */
125   - default void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus) {
126   - updateOrderItemAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, null, null);
127   - }
  135 + void updateOrderItemWhenAfterSaleSuccess(@NotNull Long id, @NotNull Integer refundPrice);
128 136  
129 137 /**
130   - * 更新交易订单项的售后状态
  138 + * 当售后取消(用户取消、管理员驳回、管理员拒绝收货)后,更新交易订单项的售后状态
131 139 *
132   - * @param id 交易订单项编号
133   - * @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常
134   - * @param newAfterSaleStatus 目标售后状态
135   - * @param afterSaleId 售后单编号;当订单项发起售后时,必须传递该字段
136   - * @param refundPrice 退款金额;当订单项退款成功时,必须传递该值
  140 + * @param id 交易订单项编号
137 141 */
138   - void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus,
139   - Long afterSaleId, Integer refundPrice);
  142 + void updateOrderItemWhenAfterSaleCancel(@NotNull Long id);
140 143  
141 144 /**
142 145 * 【会员】创建订单项的评论
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
... ... @@ -74,10 +74,7 @@ import org.springframework.transaction.annotation.Transactional;
74 74  
75 75 import javax.annotation.Resource;
76 76 import java.time.LocalDateTime;
77   -import java.util.ArrayList;
78   -import java.util.List;
79   -import java.util.Objects;
80   -import java.util.Set;
  77 +import java.util.*;
81 78  
82 79 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
83 80 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
... ... @@ -363,7 +360,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
363 360  
364 361 /**
365 362 * 校验交易订单满足被支付的条件
366   - *
  363 + * <p>
367 364 * 1. 交易订单未支付
368 365 * 2. 支付单已支付
369 366 *
... ... @@ -416,7 +413,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
416 413  
417 414 @Override
418 415 @Transactional(rollbackFor = Exception.class)
419   - @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL)
  416 + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_DELIVERY)
420 417 public void deliveryOrder(TradeOrderDeliveryReqVO deliveryReqVO) {
421 418 // 1.1 校验并获得交易订单(可发货)
422 419 TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId());
... ... @@ -455,7 +452,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
455 452  
456 453 /**
457 454 * 校验交易订单满足被发货的条件
458   - *
  455 + * <p>
459 456 * 1. 交易订单未发货
460 457 *
461 458 * @param id 交易订单编号
... ... @@ -521,7 +518,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
521 518 for (TradeOrderDO order : orders) {
522 519 try {
523 520 getSelf().receiveOrderBySystem(order);
524   - count ++;
  521 + count++;
525 522 } catch (Throwable e) {
526 523 log.error("[receiveOrderBySystem][order({}) 自动收货订单异常]", order.getId(), e);
527 524 }
... ... @@ -559,7 +556,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
559 556  
560 557 /**
561 558 * 校验交易订单满足可售货的条件
562   - *
  559 + * <p>
563 560 * 1. 交易订单待收货
564 561 *
565 562 * @param userId 用户编号
... ... @@ -612,7 +609,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
612 609 for (TradeOrderDO order : orders) {
613 610 try {
614 611 getSelf().cancelOrderBySystem(order);
615   - count ++;
  612 + count++;
616 613 } catch (Throwable e) {
617 614 log.error("[cancelOrderBySystem][order({}) 过期订单异常]", order.getId(), e);
618 615 }
... ... @@ -634,7 +631,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
634 631 /**
635 632 * 取消订单的核心实现
636 633 *
637   - * @param order 订单
  634 + * @param order 订单
638 635 * @param cancelType 取消类型
639 636 */
640 637 private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) {
... ... @@ -754,68 +751,81 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
754 751  
755 752 // =================== Order Item ===================
756 753  
757   - // TODO 疯狂:帮我重构下:
758   - // 1. updateOrderItemAfterSaleStatus 拆分成三个方法:发起;同意;拒绝。原因是,职责更清晰,操作日志也更容易记录;
759   -
760 754 @Override
761   - @Transactional(rollbackFor = Exception.class)
762   - public void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus,
763   - Long afterSaleId, Integer refundPrice) {
764   - // 如果退款成功,则 refundPrice 非空
765   - if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())
766   - && refundPrice == null) {
767   - throw new IllegalArgumentException(StrUtil.format("id({}) 退款成功,退款金额不能为空", id));
768   - }
769   - // 如果退款发起,则 afterSaleId 非空
770   - if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus())
771   - && afterSaleId == null) {
  755 + public void updateOrderItemWhenAfterSaleCreate(Long id, Long afterSaleId) {
  756 + if (afterSaleId == null) {
772 757 throw new IllegalArgumentException(StrUtil.format("id({}) 退款发起,售后单编号不能为空", id));
773 758 }
774 759  
775 760 // 更新订单项
776   - int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId);
777   - if (updateCount <= 0) {
778   - throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
779   - }
  761 + updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(),
  762 + TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), afterSaleId);
  763 + }
780 764  
781   - // 如果有退款金额,则需要更新订单
  765 + @Override
  766 + @Transactional(rollbackFor = Exception.class)
  767 + public void updateOrderItemWhenAfterSaleSuccess(Long id, Integer refundPrice) {
782 768 if (refundPrice == null) {
783   - return;
  769 + throw new IllegalArgumentException(StrUtil.format("id({}) 退款成功,退款金额不能为空", id));
784 770 }
785   - // 计算总的退款金额
  771 +
  772 + // 1. 更新订单项
  773 + updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
  774 + TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), null);
  775 +
  776 + // 2. 计算总的退款金额、退回积分
786 777 TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id);
787 778 TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId());
788 779 Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
789   - // TODO @疯狂:809 到 817 改成:cancelOrderByAfterSale:相当于全部售后成功后,就是要取消胆子;
  780 + Integer orderRefundPoint = order.getRefundPoint() + orderItem.getUsePoint();
790 781 if (isAllOrderItemAfterSaleSuccess(order.getId())) { // 如果都售后成功,则需要取消订单
791   - tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
792   - .setRefundStatus(TradeOrderRefundStatusEnum.ALL.getStatus()).setRefundPrice(orderRefundPrice).setRefundPoint(order.getRefundPoint() + orderItem.getUsePoint())
793   - .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
794   -
795   - // TODO 芋艿:记录订单日志
796   -
797   - // TODO 芋艿:要不要退优惠劵
798   -
  782 + cancelOrderByAfterSale(order, orderRefundPrice, orderRefundPoint);
799 783 } else { // 如果部分售后,则更新退款金额
800 784 tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
801   - .setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice));
  785 + .setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus())
  786 + .setRefundPrice(orderRefundPrice).setRefundPoint(orderRefundPoint));
802 787 }
803 788  
804 789 // TODO 芋艿:这块扣减规则,需要在考虑下
805   - // 售后成功后,执行数据回滚逻辑
806   - if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())) {
807   - // TODO @疯狂:这里库存也要扣减下;
808   - // 扣减用户积分(赠送的)
809   - reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, afterSaleId);
810   - // 增加用户积分(返还抵扣)
811   - addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, afterSaleId);
812   - // 扣减用户经验
813   - getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId);
814   - // 更新分佣记录为已失效
815   - getSelf().cancelBrokerageAsync(order.getUserId(), id);
  790 + // 3.1 回滚数据:增加 SKU 库存
  791 + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem)));
  792 + // 3.2 回滚数据:扣减用户积分(赠送的)
  793 + reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, orderItem.getAfterSaleId());
  794 + // 3.3 回滚数据:增加用户积分(返还抵扣)
  795 + addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, orderItem.getAfterSaleId());
  796 + // 3.4 回滚数据:扣减用户经验
  797 + getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, orderItem.getAfterSaleId());
  798 + // 3.5 回滚数据:更新分佣记录为已失效
  799 + getSelf().cancelBrokerageAsync(order.getUserId(), id);
  800 + }
  801 +
  802 + @Override
  803 + public void updateOrderItemWhenAfterSaleCancel(Long id) {
  804 + // 更新订单项
  805 + updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
  806 + TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null);
  807 + }
  808 +
  809 + private void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus,
  810 + Long afterSaleId) {
  811 + // 更新订单项
  812 + int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId);
  813 + if (updateCount <= 0) {
  814 + throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
816 815 }
817 816 }
818 817  
  818 + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL)
  819 + private void cancelOrderByAfterSale(TradeOrderDO order, Integer orderRefundPrice, Integer refundPoint) {
  820 + // 1. 更新订单
  821 + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
  822 + .setRefundStatus(TradeOrderRefundStatusEnum.ALL.getStatus())
  823 + .setRefundPrice(orderRefundPrice).setRefundPoint(refundPoint)
  824 + .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
  825 + // 2. 退还优惠券
  826 + couponApi.returnUsedCoupon(order.getCouponId());
  827 + }
  828 +
819 829  
820 830 /**
821 831 * 判断指定订单的所有订单项,是不是都售后成功
... ... @@ -879,7 +889,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
879 889 for (TradeOrderDO order : orders) {
880 890 try {
881 891 getSelf().createOrderItemCommentBySystemBySystem(order);
882   - count ++;
  892 + count++;
883 893 } catch (Throwable e) {
884 894 log.error("[createOrderItemCommentBySystem][order({}) 过期订单异常]", order.getId(), e);
885 895 }
... ... @@ -923,7 +933,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
923 933 /**
924 934 * 创建订单项的评论的核心实现
925 935 *
926   - * @param orderItem 订单项
  936 + * @param orderItem 订单项
927 937 * @param createReqVO 评论内容
928 938 * @return 评论编号
929 939 */
... ... @@ -979,14 +989,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
979 989  
980 990 /**
981 991 * 创建分销记录
982   - *
  992 + * <p>
983 993 * 目前是支付成功后,就会创建分销记录。
984   - *
  994 + * <p>
985 995 * 业内还有两种做法,可以根据自己的业务调整:
986   - * 1. 确认收货后,才创建分销记录
987   - * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
  996 + * 1. 确认收货后,才创建分销记录
  997 + * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
988 998 *
989   - * @param userId 用户编号
  999 + * @param userId 用户编号
990 1000 * @param orderId 订单编号
991 1001 */
992 1002 @Async
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java
... ... @@ -56,7 +56,6 @@ public class TradePointGiveCalculator implements TradePriceCalculator {
56 56 TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i);
57 57 // 商品可能赠送了积分,所以这里要加上
58 58 orderItem.setGivePoint(orderItem.getGivePoint() + dividePoints.get(i));
59   - TradePriceCalculatorHelper.recountPayPrice(orderItem); // TODO @疯狂:这个应该不用调用哇?不影响支付金额
60 59 }
61 60 // 3.3 更新订单赠送积分
62 61 TradePriceCalculatorHelper.recountAllGivePoint(result);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java
1 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.hutool.core.util.BooleanUtil;
4   -import cn.hutool.core.util.NumberUtil;
5 4 import cn.hutool.core.util.StrUtil;
6 5 import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
7 6 import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
... ... @@ -15,10 +14,11 @@ import org.springframework.core.annotation.Order;
15 14 import org.springframework.stereotype.Component;
16 15  
17 16 import javax.annotation.Resource;
18   -import java.math.RoundingMode;
19 17 import java.util.List;
20 18  
  19 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
21 20 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
  21 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
22 22  
23 23 // TODO @疯狂:搞个单测,嘿嘿;
24 24 /**
... ... @@ -45,7 +45,7 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
45 45 }
46 46 // 1.2 校验积分抵扣是否开启
47 47 MemberPointConfigRespDTO config = memberPointApi.getConfig();
48   - if (!checkDeductPointEnable(config)) {
  48 + if (!isDeductPointEnable(config)) {
49 49 return;
50 50 }
51 51 // 1.3 校验用户积分余额
... ... @@ -55,7 +55,6 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
55 55 }
56 56  
57 57 // 2.1 计算积分优惠金额
58   - // TODO @疯狂:如果计算出来,优惠金额为 0,那是不是不用执行后续逻辑哈
59 58 int pointPrice = calculatePointPrice(config, user.getPoint(), result);
60 59 // 2.2 计算分摊的积分、抵扣金额
61 60 List<TradePriceCalculateRespBO.OrderItem> orderItems = filterList(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSelected);
... ... @@ -77,18 +76,10 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
77 76 TradePriceCalculatorHelper.recountAllPrice(result);
78 77 }
79 78  
80   - // TODO @疯狂:这个最好是 is 开头;因为 check 或者 validator,更多失败,会抛出异常;
81   - private boolean checkDeductPointEnable(MemberPointConfigRespDTO config) {
82   - // TODO @疯狂:这个要不直接写成 return config != null && config .... 多行这样一个形式;
83   - if (config == null) {
84   - return false;
85   - }
86   - if (!BooleanUtil.isTrue(config.getTradeDeductEnable())) {
87   - return false;
88   - }
89   -
90   - // 有没有配置:1 积分抵扣多少分
91   - return config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0;
  79 + private boolean isDeductPointEnable(MemberPointConfigRespDTO config) {
  80 + return config != null &&
  81 + !BooleanUtil.isTrue(config.getTradeDeductEnable()) && // 积分功能是否启用
  82 + config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0; // 有没有配置:1 积分抵扣多少分
92 83 }
93 84  
94 85 private Integer calculatePointPrice(MemberPointConfigRespDTO config, Integer usePoint, TradePriceCalculateRespBO result) {
... ... @@ -98,14 +89,18 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
98 89 }
99 90 // 积分优惠金额(分)
100 91 int pointPrice = usePoint * config.getTradeDeductUnitPrice();
101   - // 0 元购!!!:用户积分比较多时,积分可以抵扣的金额要大于支付金额,这时需要根据支付金额反推使用多少积分
102   - if (result.getPrice().getPayPrice() < pointPrice) {
103   - pointPrice = result.getPrice().getPayPrice();
104   - // 反推需要扣除的积分
105   - usePoint = NumberUtil.toBigDecimal(pointPrice)
106   - .divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
107   - .intValue();
  92 + if (result.getPrice().getPayPrice() <= pointPrice) {
  93 + // 禁止0元购
  94 + throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
108 95 }
  96 +// // 允许0 元购!!!:用户积分比较多时,积分可以抵扣的金额要大于支付金额,这时需要根据支付金额反推使用多少积分
  97 +// if (result.getPrice().getPayPrice() < pointPrice) {
  98 +// pointPrice = result.getPrice().getPayPrice();
  99 +// // 反推需要扣除的积分
  100 +// usePoint = NumberUtil.toBigDecimal(pointPrice)
  101 +// .divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
  102 +// .intValue();
  103 +// }
109 104 // 记录使用的积分
110 105 result.setUsePoint(usePoint);
111 106 return pointPrice;
... ...