Commit 8f86f65884df2984cfb43f3c6c2aab1fba52b367

Authored by YunaiV
2 parents 8321d14b 482a84a6

Merge branch 'feature/mall_product' of https://gitee.com/zhijiantianya/ruoyi-vue-pro

Showing 43 changed files with 530 additions and 74 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/InEnum.java
@@ -17,7 +17,7 @@ import java.lang.annotation.*; @@ -17,7 +17,7 @@ import java.lang.annotation.*;
17 @Retention(RetentionPolicy.RUNTIME) 17 @Retention(RetentionPolicy.RUNTIME)
18 @Documented 18 @Documented
19 @Constraint( 19 @Constraint(
20 - validatedBy = InEnumValidator.class 20 + validatedBy = {InEnumValidator.class, InEnumCollectionValidator.class}
21 ) 21 )
22 public @interface InEnum { 22 public @interface InEnum {
23 23
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/InEnumCollectionValidator.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.common.validation;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  5 +
  6 +import javax.validation.ConstraintValidator;
  7 +import javax.validation.ConstraintValidatorContext;
  8 +import java.util.Arrays;
  9 +import java.util.Collection;
  10 +import java.util.Collections;
  11 +import java.util.List;
  12 +import java.util.stream.Collectors;
  13 +
  14 +public class InEnumCollectionValidator implements ConstraintValidator<InEnum, Collection<Integer>> {
  15 +
  16 + private List<Integer> values;
  17 +
  18 + @Override
  19 + public void initialize(InEnum annotation) {
  20 + IntArrayValuable[] values = annotation.value().getEnumConstants();
  21 + if (values.length == 0) {
  22 + this.values = Collections.emptyList();
  23 + } else {
  24 + this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toList());
  25 + }
  26 + }
  27 +
  28 + @Override
  29 + public boolean isValid(Collection<Integer> list, ConstraintValidatorContext context) {
  30 + // 校验通过
  31 + if (CollUtil.containsAll(values, list)) {
  32 + return true;
  33 + }
  34 + // 校验不通过,自定义提示语句(因为,注解上的 value 是枚举类,无法获得枚举类的实际值)
  35 + context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
  36 + context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
  37 + .replaceAll("\\{value}", CollUtil.join(list, ","))).addConstraintViolation(); // 重新添加错误提示语句
  38 + return false;
  39 + }
  40 +
  41 +}
  42 +
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java
@@ -21,6 +21,13 @@ public interface CouponApi { @@ -21,6 +21,13 @@ public interface CouponApi {
21 void useCoupon(@Valid CouponUseReqDTO useReqDTO); 21 void useCoupon(@Valid CouponUseReqDTO useReqDTO);
22 22
23 /** 23 /**
  24 + * 退还已使用的优惠券
  25 + *
  26 + * @param id 优惠券编号
  27 + */
  28 + void returnUsedCoupon(Long id);
  29 +
  30 + /**
24 * 校验优惠劵 31 * 校验优惠劵
25 * 32 *
26 * @param validReqDTO 校验请求 33 * @param validReqDTO 校验请求
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/dto/CouponRespDTO.java
@@ -71,9 +71,9 @@ public class CouponRespDTO { @@ -71,9 +71,9 @@ public class CouponRespDTO {
71 */ 71 */
72 private Integer productScope; 72 private Integer productScope;
73 /** 73 /**
74 - * 商品 SPU 编号的数组 74 + * 商品范围编号的数组
75 */ 75 */
76 - private List<Long> productSpuIds; 76 + private List<Long> productScopeValues;
77 // ========== 使用规则 END ========== 77 // ========== 使用规则 END ==========
78 78
79 // ========== 使用效果 BEGIN ========== 79 // ========== 使用效果 BEGIN ==========
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
@@ -27,12 +27,17 @@ public interface ErrorCodeConstants { @@ -27,12 +27,17 @@ public interface ErrorCodeConstants {
27 // ========== 优惠劵模板 1013004000 ========== 27 // ========== 优惠劵模板 1013004000 ==========
28 ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1013004000, "优惠劵模板不存在"); 28 ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1013004000, "优惠劵模板不存在");
29 ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1013004001, "发放数量不能小于已领取数量({})"); 29 ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1013004001, "发放数量不能小于已领取数量({})");
  30 + ErrorCode COUPON_TEMPLATE_NOT_ENOUGH = new ErrorCode(1013004002, "当前剩余数量不够领取");
  31 + ErrorCode COUPON_TEMPLATE_USER_ALREADY_TAKE = new ErrorCode(1013004003, "用户已领取过此优惠券");
  32 + ErrorCode COUPON_TEMPLATE_EXPIRED = new ErrorCode(1013004004, "优惠券已过期");
  33 + ErrorCode COUPON_TEMPLATE_CANNOT_TAKE = new ErrorCode(1013004005, "领取方式不正确");
30 34
31 - // ========== 优惠劵模板 1013005000 ========== 35 + // ========== 优惠劵 1013005000 ==========
32 ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1013005000, "优惠券不存在"); 36 ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1013005000, "优惠券不存在");
33 ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1013005001, "回收优惠劵失败,优惠劵已被使用"); 37 ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1013005001, "回收优惠劵失败,优惠劵已被使用");
34 ErrorCode COUPON_STATUS_NOT_UNUSED = new ErrorCode(1013005002, "优惠劵不处于待使用状态"); 38 ErrorCode COUPON_STATUS_NOT_UNUSED = new ErrorCode(1013005002, "优惠劵不处于待使用状态");
35 ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1013005003, "优惠券不在使用时间范围内"); 39 ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1013005003, "优惠券不在使用时间范围内");
  40 + ErrorCode COUPON_STATUS_NOT_USED = new ErrorCode(1013005004, "优惠劵不是已使用状态");
36 41
37 // ========== 满减送活动 1013006000 ========== 42 // ========== 满减送活动 1013006000 ==========
38 ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1013006000, "满减送活动不存在"); 43 ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1013006000, "满减送活动不存在");
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java
@@ -15,8 +15,9 @@ import java.util.Arrays; @@ -15,8 +15,9 @@ import java.util.Arrays;
15 @AllArgsConstructor 15 @AllArgsConstructor
16 public enum PromotionProductScopeEnum implements IntArrayValuable { 16 public enum PromotionProductScopeEnum implements IntArrayValuable {
17 17
18 - ALL(1, "全部商品参与"),  
19 - SPU(2, "指定商品参与"), 18 + ALL(1, "通用卷"), // 全部商品
  19 + SPU(2, "商品卷"), // 指定商品
  20 + CATEGORY(3, "品类卷"), // 指定商品
20 ; 21 ;
21 22
22 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionProductScopeEnum::getScope).toArray(); 23 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionProductScopeEnum::getScope).toArray();
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponTakeTypeEnum.java
@@ -15,8 +15,9 @@ import java.util.Arrays; @@ -15,8 +15,9 @@ import java.util.Arrays;
15 @Getter 15 @Getter
16 public enum CouponTakeTypeEnum implements IntArrayValuable { 16 public enum CouponTakeTypeEnum implements IntArrayValuable {
17 17
18 - BY_USER(1, "直接领取"), // 用户可在首页、每日领劵直接领取  
19 - BY_ADMIN(2, "指定发放"), // 后台指定会员赠送优惠劵 18 + USER(1, "直接领取"), // 用户可在首页、每日领劵直接领取
  19 + ADMIN(2, "指定发放"), // 后台指定会员赠送优惠劵
  20 + REGISTER(3, "新人券"), // 注册时自动领取
20 ; 21 ;
21 22
22 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CouponTakeTypeEnum::getValue).toArray(); 23 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CouponTakeTypeEnum::getValue).toArray();
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java
@@ -29,6 +29,11 @@ public class CouponApiImpl implements CouponApi { @@ -29,6 +29,11 @@ public class CouponApiImpl implements CouponApi {
29 } 29 }
30 30
31 @Override 31 @Override
  32 + public void returnUsedCoupon(Long id) {
  33 + couponService.returnUsedCoupon(id);
  34 + }
  35 +
  36 + @Override
32 public CouponRespDTO validateCoupon(CouponValidReqDTO validReqDTO) { 37 public CouponRespDTO validateCoupon(CouponValidReqDTO validReqDTO) {
33 CouponDO coupon = couponService.validCoupon(validReqDTO.getId(), validReqDTO.getUserId()); 38 CouponDO coupon = couponService.validCoupon(validReqDTO.getId(), validReqDTO.getUserId());
34 return CouponConvert.INSTANCE.convert(coupon); 39 return CouponConvert.INSTANCE.convert(coupon);
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponController.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.member.api.user.MemberUserApi; @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
8 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; 8 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
9 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO; 9 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
10 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; 10 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
  11 +import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponSendReqVO;
11 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert; 12 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
12 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; 13 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
13 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService; 14 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
@@ -21,7 +22,6 @@ import org.springframework.web.bind.annotation.*; @@ -21,7 +22,6 @@ import org.springframework.web.bind.annotation.*;
21 import javax.annotation.Resource; 22 import javax.annotation.Resource;
22 import javax.validation.Valid; 23 import javax.validation.Valid;
23 import java.util.Map; 24 import java.util.Map;
24 -import java.util.Set;  
25 25
26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -64,12 +64,20 @@ public class CouponController { @@ -64,12 +64,20 @@ public class CouponController {
64 if (CollUtil.isEmpty(pageResulVO.getList())) { 64 if (CollUtil.isEmpty(pageResulVO.getList())) {
65 return success(pageResulVO); 65 return success(pageResulVO);
66 } 66 }
  67 +
67 // 读取用户信息,进行拼接 68 // 读取用户信息,进行拼接
68 - Set<Long> userIds = convertSet(pageResult.getList(), CouponDO::getUserId);  
69 - Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds); 69 + Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(convertSet(pageResult.getList(), CouponDO::getUserId));
70 pageResulVO.getList().forEach(itemRespVO -> MapUtils.findAndThen(userMap, itemRespVO.getUserId(), 70 pageResulVO.getList().forEach(itemRespVO -> MapUtils.findAndThen(userMap, itemRespVO.getUserId(),
71 userRespDTO -> itemRespVO.setNickname(userRespDTO.getNickname()))); 71 userRespDTO -> itemRespVO.setNickname(userRespDTO.getNickname())));
72 return success(pageResulVO); 72 return success(pageResulVO);
73 } 73 }
74 74
  75 + @PostMapping("/send")
  76 + @Operation(summary = "发送优惠劵")
  77 + @PreAuthorize("@ss.hasPermission('promotion:coupon:send')")
  78 + public CommonResult<Boolean> sendCoupon(@Valid @RequestBody CouponSendReqVO reqVO) {
  79 + couponService.takeCouponByAdmin(reqVO.getTemplateId(), reqVO.getUserIds());
  80 + return success(true);
  81 + }
  82 +
75 } 83 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponTemplateController.java
@@ -75,5 +75,4 @@ public class CouponTemplateController { @@ -75,5 +75,4 @@ public class CouponTemplateController {
75 PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage(pageVO); 75 PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage(pageVO);
76 return success(CouponTemplateConvert.INSTANCE.convertPage(pageResult)); 76 return success(CouponTemplateConvert.INSTANCE.convertPage(pageResult));
77 } 77 }
78 -  
79 } 78 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponBaseVO.java
@@ -26,7 +26,7 @@ public class CouponBaseVO { @@ -26,7 +26,7 @@ public class CouponBaseVO {
26 // ========== 基本信息 BEGIN ========== 26 // ========== 基本信息 BEGIN ==========
27 @Schema(description = "优惠劵模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") 27 @Schema(description = "优惠劵模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
28 @NotNull(message = "优惠劵模板编号不能为空") 28 @NotNull(message = "优惠劵模板编号不能为空")
29 - private Integer templateId; 29 + private Long templateId;
30 30
31 @Schema(description = "优惠劵名", requiredMode = Schema.RequiredMode.REQUIRED, example = "春节送送送") 31 @Schema(description = "优惠劵名", requiredMode = Schema.RequiredMode.REQUIRED, example = "春节送送送")
32 @NotNull(message = "优惠劵名不能为空") 32 @NotNull(message = "优惠劵名不能为空")
@@ -67,8 +67,8 @@ public class CouponBaseVO { @@ -67,8 +67,8 @@ public class CouponBaseVO {
67 @InEnum(PromotionProductScopeEnum.class) 67 @InEnum(PromotionProductScopeEnum.class)
68 private Integer productScope; 68 private Integer productScope;
69 69
70 - @Schema(description = "商品 SPU 编号的数组", example = "1,3")  
71 - private List<Long> productSpuIds; 70 + @Schema(description = "商品范围编号的数组", example = "1,3")
  71 + private List<Long> productScopeValues;
72 // ========== 使用规则 END ========== 72 // ========== 使用规则 END ==========
73 73
74 // ========== 使用效果 BEGIN ========== 74 // ========== 使用效果 BEGIN ==========
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponSendReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.ToString;
  6 +
  7 +import javax.validation.constraints.NotEmpty;
  8 +import javax.validation.constraints.NotNull;
  9 +import java.util.Set;
  10 +
  11 +@Schema(description = "管理后台 - 优惠劵发放 Request VO")
  12 +@Data
  13 +@ToString(callSuper = true)
  14 +public class CouponSendReqVO {
  15 +
  16 + @Schema(description = "优惠劵模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + @NotNull(message = "优惠劵模板编号不能为空")
  18 + private Long templateId;
  19 +
  20 + @Schema(description = "用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
  21 + @NotEmpty(message = "用户编号列表不能为空")
  22 + private Set<Long> userIds;
  23 +
  24 +}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateBaseVO.java
@@ -54,8 +54,8 @@ public class CouponTemplateBaseVO { @@ -54,8 +54,8 @@ public class CouponTemplateBaseVO {
54 @InEnum(PromotionProductScopeEnum.class) 54 @InEnum(PromotionProductScopeEnum.class)
55 private Integer productScope; 55 private Integer productScope;
56 56
57 - @Schema(description = "商品 SPU 编号的数组", example = "1,3")  
58 - private List<Long> productSpuIds; 57 + @Schema(description = "商品范围编号的数组", example = "[1, 3]")
  58 + private List<Long> productScopeValues;
59 59
60 @Schema(description = "生效日期类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 60 @Schema(description = "生效日期类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
61 @NotNull(message = "生效日期类型不能为空") 61 @NotNull(message = "生效日期类型不能为空")
@@ -95,11 +95,11 @@ public class CouponTemplateBaseVO { @@ -95,11 +95,11 @@ public class CouponTemplateBaseVO {
95 @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用 95 @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
96 private Integer discountLimitPrice; 96 private Integer discountLimitPrice;
97 97
98 - @AssertTrue(message = "商品 SPU 编号的数组不能为空") 98 + @AssertTrue(message = "商品范围编号的数组不能为空")
99 @JsonIgnore 99 @JsonIgnore
100 - public boolean isProductSpuIdsValid() { 100 + public boolean isProductScopeValuesValid() {
101 return Objects.equals(productScope, PromotionProductScopeEnum.ALL.getScope()) // 全部范围时,可以为空 101 return Objects.equals(productScope, PromotionProductScopeEnum.ALL.getScope()) // 全部范围时,可以为空
102 - || CollUtil.isNotEmpty(productSpuIds); 102 + || CollUtil.isNotEmpty(productScopeValues);
103 } 103 }
104 104
105 @AssertTrue(message = "生效开始时间不能为空") 105 @AssertTrue(message = "生效开始时间不能为空")
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplatePageReqVO.java
1 package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template; 1 package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageParam; 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.CouponTakeTypeEnum;
4 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 7 import lombok.Data;
6 import lombok.EqualsAndHashCode; 8 import lombok.EqualsAndHashCode;
@@ -8,6 +10,7 @@ import lombok.ToString; @@ -8,6 +10,7 @@ import lombok.ToString;
8 import org.springframework.format.annotation.DateTimeFormat; 10 import org.springframework.format.annotation.DateTimeFormat;
9 11
10 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
  13 +import java.util.List;
11 14
12 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; 15 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
13 16
@@ -30,4 +33,8 @@ public class CouponTemplatePageReqVO extends PageParam { @@ -30,4 +33,8 @@ public class CouponTemplatePageReqVO extends PageParam {
30 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) 33 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
31 private LocalDateTime[] createTime; 34 private LocalDateTime[] createTime;
32 35
  36 + @Schema(description = "可以领取的类型", example = "[1,2, 3]")
  37 + @InEnum(value = CouponTakeTypeEnum.class, message = "可以领取的类型,必须是 {value}")
  38 + private List<Integer> canTakeTypes;
  39 +
33 } 40 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplateRespVO.java
@@ -28,8 +28,8 @@ public class AppCouponTemplateRespVO { @@ -28,8 +28,8 @@ public class AppCouponTemplateRespVO {
28 // @InEnum(PromotionProductScopeEnum.class) 28 // @InEnum(PromotionProductScopeEnum.class)
29 // private Integer productScope; 29 // private Integer productScope;
30 // 30 //
31 -// @Schema(description = "商品 SPU 编号的数组", example = "1,3")  
32 -// private List<Long> productSpuIds; 31 +// @Schema(description = "商品范围编号的数组", example = "1,3")
  32 +// private List<Long> productScopeValues;
33 33
34 @Schema(description = "生效日期类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 34 @Schema(description = "生效日期类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
35 private Integer validityType; 35 private Integer validityType;
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
@@ -4,9 +4,14 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -4,9 +4,14 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; 4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO; 5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
6 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; 6 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
  7 +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
  8 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
  9 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
7 import org.mapstruct.Mapper; 10 import org.mapstruct.Mapper;
8 import org.mapstruct.factory.Mappers; 11 import org.mapstruct.factory.Mappers;
9 12
  13 +import java.time.LocalDateTime;
  14 +
10 /** 15 /**
11 * 优惠劵 Convert 16 * 优惠劵 Convert
12 * 17 *
@@ -21,4 +26,27 @@ public interface CouponConvert { @@ -21,4 +26,27 @@ public interface CouponConvert {
21 26
22 CouponRespDTO convert(CouponDO bean); 27 CouponRespDTO convert(CouponDO bean);
23 28
  29 + default CouponDO convert(CouponTemplateDO template, Long userId) {
  30 + CouponDO couponDO = new CouponDO()
  31 + .setTemplateId(template.getId())
  32 + .setName(template.getName())
  33 + .setTakeType(template.getTakeType())
  34 + .setUsePrice(template.getUsePrice())
  35 + .setProductScope(template.getProductScope())
  36 + .setProductScopeValues(template.getProductScopeValues())
  37 + .setDiscountType(template.getDiscountType())
  38 + .setDiscountPercent(template.getDiscountPercent())
  39 + .setDiscountPrice(template.getDiscountPrice())
  40 + .setDiscountLimitPrice(template.getDiscountLimitPrice())
  41 + .setStatus(CouponStatusEnum.UNUSED.getStatus())
  42 + .setUserId(userId);
  43 + if (CouponTemplateValidityTypeEnum.DATE.getType().equals(template.getValidityType())) {
  44 + couponDO.setValidStartTime(template.getValidStartTime());
  45 + couponDO.setValidEndTime(template.getValidEndTime());
  46 + } else if (CouponTemplateValidityTypeEnum.TERM.getType().equals(template.getValidityType())) {
  47 + couponDO.setValidStartTime(LocalDateTime.now().plusDays(template.getFixedStartTerm()));
  48 + couponDO.setValidEndTime(LocalDateTime.now().plusDays(template.getFixedEndTerm()));
  49 + }
  50 + return couponDO;
  51 + }
24 } 52 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java
@@ -36,7 +36,7 @@ public class CouponDO extends BaseDO { @@ -36,7 +36,7 @@ public class CouponDO extends BaseDO {
36 * 36 *
37 * 关联 {@link CouponTemplateDO#getId()} 37 * 关联 {@link CouponTemplateDO#getId()}
38 */ 38 */
39 - private Integer templateId; 39 + private Long templateId;
40 /** 40 /**
41 * 优惠劵名 41 * 优惠劵名
42 * 42 *
@@ -89,12 +89,12 @@ public class CouponDO extends BaseDO { @@ -89,12 +89,12 @@ public class CouponDO extends BaseDO {
89 */ 89 */
90 private Integer productScope; 90 private Integer productScope;
91 /** 91 /**
92 - * 商品 SPU 编号的数组 92 + * 商品范围编号的数组
93 * 93 *
94 - * 冗余 {@link CouponTemplateDO#getProductSpuIds()} 94 + * 冗余 {@link CouponTemplateDO#getProductScopeValues()}
95 */ 95 */
96 @TableField(typeHandler = LongListTypeHandler.class) 96 @TableField(typeHandler = LongListTypeHandler.class)
97 - private List<Long> productSpuIds; 97 + private List<Long> productScopeValues;
98 // ========== 使用规则 END ========== 98 // ========== 使用规则 END ==========
99 99
100 // ========== 使用效果 BEGIN ========== 100 // ========== 使用效果 BEGIN ==========
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponTemplateDO.java
@@ -85,10 +85,10 @@ public class CouponTemplateDO extends BaseDO { @@ -85,10 +85,10 @@ public class CouponTemplateDO extends BaseDO {
85 */ 85 */
86 private Integer productScope; 86 private Integer productScope;
87 /** 87 /**
88 - * 商品 SPU 编号的数组 88 + * 商品范围编号的数组
89 */ 89 */
90 @TableField(typeHandler = LongListTypeHandler.class) 90 @TableField(typeHandler = LongListTypeHandler.class)
91 - private List<Long> productSpuIds; 91 + private List<Long> productScopeValues;
92 /** 92 /**
93 * 生效日期类型 93 * 生效日期类型
94 * 94 *
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
@@ -55,4 +55,11 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; { @@ -55,4 +55,11 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; {
55 .eq(CouponDO::getStatus, status)); 55 .eq(CouponDO::getStatus, status));
56 } 56 }
57 57
  58 + default List<CouponDO> selectListByTemplateIdAndUserId(Long templateId, Collection<Long> userIds) {
  59 + return selectList(new LambdaQueryWrapperX<CouponDO>()
  60 + .eq(CouponDO::getTemplateId, templateId)
  61 + .in(CouponDO::getUserId, userIds)
  62 + );
  63 + }
  64 +
58 } 65 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
1 package cn.iocoder.yudao.module.promotion.dal.mysql.coupon; 1 package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 7 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO; 8 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
7 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
  10 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
8 import org.apache.ibatis.annotations.Mapper; 11 import org.apache.ibatis.annotations.Mapper;
9 import org.apache.ibatis.annotations.Param; 12 import org.apache.ibatis.annotations.Param;
10 13
  14 +import java.time.LocalDateTime;
  15 +import java.util.function.Consumer;
  16 +
11 /** 17 /**
12 * 优惠劵模板 Mapper 18 * 优惠劵模板 Mapper
13 * 19 *
@@ -17,11 +23,23 @@ import org.apache.ibatis.annotations.Param; @@ -17,11 +23,23 @@ import org.apache.ibatis.annotations.Param;
17 public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> { 23 public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
18 24
19 default PageResult<CouponTemplateDO> selectPage(CouponTemplatePageReqVO reqVO) { 25 default PageResult<CouponTemplateDO> selectPage(CouponTemplatePageReqVO reqVO) {
  26 + // 构建可领取的查询条件, 好啰嗦 ( ╯-_-)╯┴—┴
  27 + Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = null;
  28 + if (CollUtil.isNotEmpty(reqVO.getCanTakeTypes())) {
  29 + canTakeConsumer = w ->
  30 + w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
  31 + .in(CouponTemplateDO::getTakeType, reqVO.getCanTakeTypes()) // 2. 领取方式一致
  32 + .and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime) // 3. 未过期
  33 + .or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
  34 + .apply(" take_count < total_count "); // 4. 剩余数量大于 0
  35 + }
  36 + // 执行分页查询
20 return selectPage(reqVO, new LambdaQueryWrapperX<CouponTemplateDO>() 37 return selectPage(reqVO, new LambdaQueryWrapperX<CouponTemplateDO>()
21 .likeIfPresent(CouponTemplateDO::getName, reqVO.getName()) 38 .likeIfPresent(CouponTemplateDO::getName, reqVO.getName())
22 .eqIfPresent(CouponTemplateDO::getStatus, reqVO.getStatus()) 39 .eqIfPresent(CouponTemplateDO::getStatus, reqVO.getStatus())
23 .eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType()) 40 .eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType())
24 .betweenIfPresent(CouponTemplateDO::getCreateTime, reqVO.getCreateTime()) 41 .betweenIfPresent(CouponTemplateDO::getCreateTime, reqVO.getCreateTime())
  42 + .and(canTakeConsumer != null, canTakeConsumer)
25 .orderByDesc(CouponTemplateDO::getId)); 43 .orderByDesc(CouponTemplateDO::getId));
26 } 44 }
27 45
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
1 package cn.iocoder.yudao.module.promotion.service.coupon; 1 package cn.iocoder.yudao.module.promotion.service.coupon;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; 5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
5 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; 6 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
  7 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
6 8
7 import java.util.List; 9 import java.util.List;
  10 +import java.util.Set;
8 11
9 /** 12 /**
10 * 优惠劵 Service 接口 13 * 优惠劵 Service 接口
@@ -52,6 +55,13 @@ public interface CouponService { @@ -52,6 +55,13 @@ public interface CouponService {
52 void useCoupon(Long id, Long userId, Long orderId); 55 void useCoupon(Long id, Long userId, Long orderId);
53 56
54 /** 57 /**
  58 + * 退还已使用的优惠券
  59 + *
  60 + * @param id 优惠券编号
  61 + */
  62 + void returnUsedCoupon(Long id);
  63 +
  64 + /**
55 * 回收优惠劵 65 * 回收优惠劵
56 * 66 *
57 * @param id 优惠劵编号 67 * @param id 优惠劵编号
@@ -75,4 +85,43 @@ public interface CouponService { @@ -75,4 +85,43 @@ public interface CouponService {
75 */ 85 */
76 Long getUnusedCouponCount(Long userId); 86 Long getUnusedCouponCount(Long userId);
77 87
  88 + /**
  89 + * 领取优惠券
  90 + *
  91 + * @param templateId 优惠券模板编号
  92 + * @param userIds 用户编号列表
  93 + * @param takeType 领取方式
  94 + */
  95 + void takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType);
  96 +
  97 + /**
  98 + * 【管理员】给用户发送优惠券
  99 + *
  100 + * @param templateId 优惠券模板编号
  101 + * @param userIds 用户编号列表
  102 + */
  103 + default void takeCouponByAdmin(Long templateId, Set<Long> userIds) {
  104 + takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN);
  105 + }
  106 +
  107 + /**
  108 + * 【会员】领取优惠券
  109 + *
  110 + * @param templateId 优惠券模板编号
  111 + * @param userId 用户编号
  112 + */
  113 + default void takeCouponByUser(Long templateId, Long userId) {
  114 + takeCoupon(templateId, CollUtil.newHashSet(userId), CouponTakeTypeEnum.USER);
  115 + }
  116 +
  117 + /**
  118 + * 【系统】给用户发送新人券
  119 + *
  120 + * @param templateId 优惠券模板编号
  121 + * @param userId 用户编号列表
  122 + */
  123 + default void takeCouponByRegister(Long templateId, Long userId) {
  124 + takeCoupon(templateId, CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER);
  125 + }
  126 +
78 } 127 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
1 package cn.iocoder.yudao.module.promotion.service.coupon; 1 package cn.iocoder.yudao.module.promotion.service.coupon;
2 2
  3 +import cn.hutool.core.collection.CollStreamUtil;
3 import cn.hutool.core.collection.CollUtil; 4 import cn.hutool.core.collection.CollUtil;
  5 +import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.ObjectUtil; 6 import cn.hutool.core.util.ObjectUtil;
5 import cn.hutool.core.util.StrUtil; 7 import cn.hutool.core.util.StrUtil;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -9,9 +11,13 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; @@ -9,9 +11,13 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
9 import cn.iocoder.yudao.module.member.api.user.MemberUserApi; 11 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
10 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; 12 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
11 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; 13 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
  14 +import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
12 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; 15 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
  16 +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
13 import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper; 17 import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
14 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; 18 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
  19 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
  20 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
15 import org.springframework.stereotype.Service; 21 import org.springframework.stereotype.Service;
16 import org.springframework.transaction.annotation.Transactional; 22 import org.springframework.transaction.annotation.Transactional;
17 import org.springframework.validation.annotation.Validated; 23 import org.springframework.validation.annotation.Validated;
@@ -19,9 +25,12 @@ import org.springframework.validation.annotation.Validated; @@ -19,9 +25,12 @@ import org.springframework.validation.annotation.Validated;
19 import javax.annotation.Resource; 25 import javax.annotation.Resource;
20 import java.time.LocalDateTime; 26 import java.time.LocalDateTime;
21 import java.util.List; 27 import java.util.List;
  28 +import java.util.Map;
22 import java.util.Set; 29 import java.util.Set;
  30 +import java.util.stream.Collectors;
23 31
24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  33 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
25 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; 34 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
26 import static java.util.Arrays.asList; 35 import static java.util.Arrays.asList;
27 36
@@ -84,6 +93,7 @@ public class CouponServiceImpl implements CouponService { @@ -84,6 +93,7 @@ public class CouponServiceImpl implements CouponService {
84 public void useCoupon(Long id, Long userId, Long orderId) { 93 public void useCoupon(Long id, Long userId, Long orderId) {
85 // 校验优惠劵 94 // 校验优惠劵
86 validCoupon(id, userId); 95 validCoupon(id, userId);
  96 +
87 // 更新状态 97 // 更新状态
88 int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(), 98 int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
89 new CouponDO().setStatus(CouponStatusEnum.USED.getStatus()) 99 new CouponDO().setStatus(CouponStatusEnum.USED.getStatus())
@@ -94,6 +104,31 @@ public class CouponServiceImpl implements CouponService { @@ -94,6 +104,31 @@ public class CouponServiceImpl implements CouponService {
94 } 104 }
95 105
96 @Override 106 @Override
  107 + public void returnUsedCoupon(Long id) {
  108 + // 校验存在
  109 + CouponDO coupon = couponMapper.selectById(id);
  110 + if (coupon == null) {
  111 + throw exception(COUPON_NOT_EXISTS);
  112 + }
  113 + // 校验状态
  114 + if (ObjectUtil.notEqual(coupon.getTemplateId(), CouponStatusEnum.USED.getStatus())) {
  115 + throw exception(COUPON_STATUS_NOT_USED);
  116 + }
  117 +
  118 + // 退还
  119 + Integer status = LocalDateTimeUtils.beforeNow(coupon.getValidEndTime())
  120 + ? CouponStatusEnum.EXPIRE.getStatus() // 退还时可能已经过期了
  121 + : CouponStatusEnum.UNUSED.getStatus();
  122 + int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
  123 + new CouponDO().setStatus(status));
  124 + if (updateCount == 0) {
  125 + throw exception(COUPON_STATUS_NOT_USED);
  126 + }
  127 +
  128 + // TODO 增加优惠券变动记录?
  129 + }
  130 +
  131 + @Override
97 @Transactional 132 @Transactional
98 public void deleteCoupon(Long id) { 133 public void deleteCoupon(Long id) {
99 // 校验存在 134 // 校验存在
@@ -125,4 +160,72 @@ public class CouponServiceImpl implements CouponService { @@ -125,4 +160,72 @@ public class CouponServiceImpl implements CouponService {
125 return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus()); 160 return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus());
126 } 161 }
127 162
  163 + @Override
  164 + public void takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType) {
  165 + CouponTemplateDO template = couponTemplateService.getCouponTemplate(templateId);
  166 + // 1. 过滤掉达到领取限制的用户
  167 + removeTakeLimitUser(userIds, template);
  168 + // 2. 校验优惠劵是否可以领取
  169 + validateCouponTemplateCanTake(template, userIds, takeType);
  170 +
  171 + // 3. 批量保存优惠劵
  172 + couponMapper.insertBatch(convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId)));
  173 +
  174 + // 3. 增加优惠劵模板的领取数量
  175 + couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size());
  176 + }
  177 +
  178 + /**
  179 + * 校验优惠券是否可以领取
  180 + *
  181 + * @param couponTemplate 优惠券模板
  182 + * @param userIds 领取人列表
  183 + * @param takeType 领取方式
  184 + */
  185 + private void validateCouponTemplateCanTake(CouponTemplateDO couponTemplate, Set<Long> userIds, CouponTakeTypeEnum takeType) {
  186 + // 如果所有用户都领取过,则抛出异常
  187 + if (CollUtil.isEmpty(userIds)) {
  188 + throw exception(COUPON_TEMPLATE_USER_ALREADY_TAKE);
  189 + }
  190 +
  191 + // 校验模板
  192 + if (couponTemplate == null) {
  193 + throw exception(COUPON_TEMPLATE_NOT_EXISTS);
  194 + }
  195 + // 校验剩余数量
  196 + if (couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
  197 + throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
  198 + }
  199 + // 校验"固定日期"的有效期类型是否过期
  200 + if (CouponTemplateValidityTypeEnum.DATE.getType().equals(couponTemplate.getValidityType())) {
  201 + if (LocalDateTimeUtils.beforeNow(couponTemplate.getValidEndTime())) {
  202 + throw exception(COUPON_TEMPLATE_EXPIRED);
  203 + }
  204 + }
  205 + // 校验领取方式
  206 + if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) {
  207 + throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
  208 + }
  209 + }
  210 +
  211 + /**
  212 + * 过滤掉达到领取上线的用户
  213 + *
  214 + * @param userIds 用户编号数组
  215 + * @param couponTemplate 优惠劵模版
  216 + */
  217 + private void removeTakeLimitUser(Set<Long> userIds, CouponTemplateDO couponTemplate) {
  218 + if (couponTemplate.getTakeLimitCount() <= 0) {
  219 + return;
  220 + }
  221 + // 查询已领过券的用户
  222 + List<CouponDO> alreadyTakeCoupons = couponMapper.selectListByTemplateIdAndUserId(couponTemplate.getId(), userIds);
  223 + if (CollUtil.isEmpty(alreadyTakeCoupons)) {
  224 + return;
  225 + }
  226 + // 移除达到领取限制的用户
  227 + Map<Long, Integer> userTakeCountMap = CollStreamUtil.groupBy(alreadyTakeCoupons, CouponDO::getUserId, Collectors.summingInt(c -> 1));
  228 + userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount());
  229 + }
  230 +
128 } 231 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java
@@ -33,7 +33,7 @@ public interface CouponTemplateService { @@ -33,7 +33,7 @@ public interface CouponTemplateService {
33 /** 33 /**
34 * 更新优惠劵模板的状态 34 * 更新优惠劵模板的状态
35 * 35 *
36 - * @param id 编号 36 + * @param id 编号
37 * @param status 状态 37 * @param status 状态
38 */ 38 */
39 void updateCouponTemplateStatus(Long id, Integer status); 39 void updateCouponTemplateStatus(Long id, Integer status);
@@ -64,7 +64,7 @@ public interface CouponTemplateService { @@ -64,7 +64,7 @@ public interface CouponTemplateService {
64 /** 64 /**
65 * 更新优惠劵模板的领取数量 65 * 更新优惠劵模板的领取数量
66 * 66 *
67 - * @param id 优惠劵模板编号 67 + * @param id 优惠劵模板编号
68 * @param incrCount 增加数量 68 * @param incrCount 增加数量
69 */ 69 */
70 void updateCouponTemplateTakeCount(Long id, int incrCount); 70 void updateCouponTemplateTakeCount(Long id, int incrCount);
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java
@@ -14,7 +14,8 @@ import org.springframework.validation.annotation.Validated; @@ -14,7 +14,8 @@ import org.springframework.validation.annotation.Validated;
14 import javax.annotation.Resource; 14 import javax.annotation.Resource;
15 15
16 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 16 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
17 -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; 17 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_EXISTS;
  18 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL;
18 19
19 /** 20 /**
20 * 优惠劵模板 Service 实现类 21 * 优惠劵模板 Service 实现类
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
@@ -33,6 +33,7 @@ public interface ErrorCodeConstants { @@ -33,6 +33,7 @@ public interface ErrorCodeConstants {
33 ErrorCode ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1011000022, "交易订单发货失败,拼团未成功"); 33 ErrorCode ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1011000022, "交易订单发货失败,拼团未成功");
34 ErrorCode ORDER_DELIVERY_FAIL_BARGAIN_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1011000023, "交易订单发货失败,砍价未成功"); 34 ErrorCode ORDER_DELIVERY_FAIL_BARGAIN_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1011000023, "交易订单发货失败,砍价未成功");
35 ErrorCode ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS = new ErrorCode(1011000024, "交易订单发货失败,发货类型不是快递"); 35 ErrorCode ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS = new ErrorCode(1011000024, "交易订单发货失败,发货类型不是快递");
  36 + ErrorCode ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID = new ErrorCode(1011000025, "交易订单取消失败,订单不是【待支付】状态");
36 37
37 // ========== After Sale 模块 1011000100 ========== 38 // ========== After Sale 模块 1011000100 ==========
38 ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1011000100, "售后单不存在"); 39 ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1011000100, "售后单不存在");
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/TradeAfterSaleController.java
@@ -27,6 +27,8 @@ import org.springframework.web.bind.annotation.*; @@ -27,6 +27,8 @@ import org.springframework.web.bind.annotation.*;
27 import javax.annotation.Resource; 27 import javax.annotation.Resource;
28 import javax.annotation.security.PermitAll; 28 import javax.annotation.security.PermitAll;
29 import javax.validation.Valid; 29 import javax.validation.Valid;
  30 +import java.time.LocalDateTime;
  31 +import java.util.ArrayList;
30 import java.util.List; 32 import java.util.List;
31 import java.util.Map; 33 import java.util.Map;
32 34
@@ -82,6 +84,24 @@ public class TradeAfterSaleController { @@ -82,6 +84,24 @@ public class TradeAfterSaleController {
82 MemberUserRespDTO user = memberUserApi.getUser(afterSale.getUserId()); 84 MemberUserRespDTO user = memberUserApi.getUser(afterSale.getUserId());
83 // 获取售后日志 85 // 获取售后日志
84 List<TradeAfterSaleLogRespDTO> logs = afterSaleLogService.getLog(afterSale.getId()); 86 List<TradeAfterSaleLogRespDTO> logs = afterSaleLogService.getLog(afterSale.getId());
  87 + // TODO 方便测试看效果,review 后移除
  88 + if (logs == null) {
  89 + logs = new ArrayList<>();
  90 + }
  91 + for (int i = 1; i <= 6; i++) {
  92 + TradeAfterSaleLogRespDTO respVO = new TradeAfterSaleLogRespDTO();
  93 + respVO.setId((long) i);
  94 + respVO.setUserId((long) i);
  95 + respVO.setUserType(1);
  96 + respVO.setAfterSaleId(id);
  97 + respVO.setOrderId((long) i);
  98 + respVO.setOrderItemId((long) i);
  99 + respVO.setBeforeStatus((i - 1) * 10);
  100 + respVO.setAfterStatus(i * 10);
  101 + respVO.setContent("66+6");
  102 + respVO.setCreateTime(LocalDateTime.now());
  103 + logs.add(respVO);
  104 + }
85 return success(TradeAfterSaleConvert.INSTANCE.convert(afterSale, order, orderItems, user, logs)); 105 return success(TradeAfterSaleConvert.INSTANCE.convert(afterSale, order, orderItems, user, logs));
86 } 106 }
87 107
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java
@@ -25,7 +25,6 @@ import java.util.Map; @@ -25,7 +25,6 @@ import java.util.Map;
25 25
26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
28 -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;  
29 28
30 @Tag(name = "管理后台 - 交易订单") 29 @Tag(name = "管理后台 - 交易订单")
31 @RestController 30 @RestController
@@ -70,7 +69,7 @@ public class TradeOrderController { @@ -70,7 +69,7 @@ public class TradeOrderController {
70 TradeOrderDO order = tradeOrderQueryService.getOrder(id); 69 TradeOrderDO order = tradeOrderQueryService.getOrder(id);
71 // 查询订单项 70 // 查询订单项
72 List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(id); 71 List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(id);
73 - 72 + // orderLog
74 // 拼接数据 73 // 拼接数据
75 MemberUserRespDTO user = memberUserApi.getUser(order.getUserId()); 74 MemberUserRespDTO user = memberUserApi.getUser(order.getUserId());
76 return success(TradeOrderConvert.INSTANCE.convert(order, orderItems, user)); 75 return success(TradeOrderConvert.INSTANCE.convert(order, orderItems, user));
@@ -82,7 +81,7 @@ public class TradeOrderController { @@ -82,7 +81,7 @@ public class TradeOrderController {
82 @PreAuthorize("@ss.hasPermission('trade:order:query')") 81 @PreAuthorize("@ss.hasPermission('trade:order:query')")
83 public CommonResult<List<?>> getOrderExpressTrackList(@RequestParam("id") Long id) { 82 public CommonResult<List<?>> getOrderExpressTrackList(@RequestParam("id") Long id) {
84 return success(TradeOrderConvert.INSTANCE.convertList02( 83 return success(TradeOrderConvert.INSTANCE.convertList02(
85 - tradeOrderQueryService.getExpressTrackList(id, getLoginUserId()))); 84 + tradeOrderQueryService.getExpressTrackList(id)));
86 } 85 }
87 86
88 @PutMapping("/delivery") 87 @PutMapping("/delivery")
@@ -117,11 +116,4 @@ public class TradeOrderController { @@ -117,11 +116,4 @@ public class TradeOrderController {
117 return success(true); 116 return success(true);
118 } 117 }
119 118
120 - // TODO @puhui999 订单物流详情  
121 - // TODO @puhui999 【前台】订单取消  
122 - // TODO @puhui999 【后台】订单取消  
123 - // TODO @puhui999 【前台】订单核销  
124 - // TODO @puhui999 【前台】订单删除  
125 - // TODO @puhui999 【后台】订单统计  
126 -  
127 } 119 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderDetailRespVO.java
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.Prod @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.Prod
5 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 6 import lombok.Data;
7 7
  8 +import java.time.LocalDateTime;
8 import java.util.List; 9 import java.util.List;
9 10
10 @Schema(description = "管理后台 - 交易订单的详情 Response VO") 11 @Schema(description = "管理后台 - 交易订单的详情 Response VO")
@@ -24,6 +25,27 @@ public class TradeOrderDetailRespVO extends TradeOrderBaseVO { @@ -24,6 +25,27 @@ public class TradeOrderDetailRespVO extends TradeOrderBaseVO {
24 */ 25 */
25 private MemberUserRespVO user; 26 private MemberUserRespVO user;
26 27
  28 + /**
  29 + * TODO 订单操作日志, 先模拟一波;返回 logs,简洁,然后复数哈
  30 + */
  31 + private List<OrderLog> orderLog;
  32 +
  33 + // TODO @puhui999:swagger 注解
  34 + @Data
  35 + public static class OrderLog {
  36 +
  37 + /**
  38 + * 内容
  39 + */
  40 + private String content;
  41 +
  42 + /**
  43 + * 创建时间
  44 + */
  45 + private LocalDateTime createTime;
  46 +
  47 + }
  48 +
27 @Schema(description = "管理后台 - 交易订单的详情的订单项目") 49 @Schema(description = "管理后台 - 交易订单的详情的订单项目")
28 @Data 50 @Data
29 public static class Item extends TradeOrderItemBaseVO { 51 public static class Item extends TradeOrderItemBaseVO {
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderPageReqVO.java
@@ -61,5 +61,5 @@ public class TradeOrderPageReqVO extends PageParam { @@ -61,5 +61,5 @@ public class TradeOrderPageReqVO extends PageParam {
61 @Schema(description = "订单来源", example = "10") 61 @Schema(description = "订单来源", example = "10")
62 @InEnum(value = TerminalEnum.class, message = "订单来源 {value}") 62 @InEnum(value = TerminalEnum.class, message = "订单来源 {value}")
63 private Integer terminal; 63 private Integer terminal;
64 - 64 +// TODO 添加配送方式筛选
65 } 65 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/AppDeliverPickUpStoreController.java
@@ -24,6 +24,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -24,6 +24,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
24 @Validated 24 @Validated
25 public class AppDeliverPickUpStoreController { 25 public class AppDeliverPickUpStoreController {
26 26
  27 + // TODO 待实现[门店自提]:如果 latitude、longitude 非空,计算经纬度,并排序。计算的库,可以使用 hutool 的 DistanceUtil 计算。
27 @GetMapping("/list") 28 @GetMapping("/list")
28 @Operation(summary = "获得自提门店列表") 29 @Operation(summary = "获得自提门店列表")
29 public CommonResult<List<AppDeliveryPickUpStoreRespVO>> getDeliveryPickUpStoreList( 30 public CommonResult<List<AppDeliveryPickUpStoreRespVO>> getDeliveryPickUpStoreList(
@@ -50,6 +51,7 @@ public class AppDeliverPickUpStoreController { @@ -50,6 +51,7 @@ public class AppDeliverPickUpStoreController {
50 return success(list); 51 return success(list);
51 } 52 }
52 53
  54 + // TODO 待实现[门店自提]:
53 @GetMapping("/get") 55 @GetMapping("/get")
54 @Operation(summary = "获得自提门店") 56 @Operation(summary = "获得自提门店")
55 @Parameter(name = "id", description = "门店编号") 57 @Parameter(name = "id", description = "门店编号")
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
@@ -50,9 +50,6 @@ public class AppTradeOrderController { @@ -50,9 +50,6 @@ public class AppTradeOrderController {
50 private DeliveryExpressService deliveryExpressService; 50 private DeliveryExpressService deliveryExpressService;
51 51
52 @Resource 52 @Resource
53 - private ProductPropertyValueApi productPropertyValueApi;  
54 -  
55 - @Resource  
56 private TradeOrderProperties tradeOrderProperties; 53 private TradeOrderProperties tradeOrderProperties;
57 54
58 @GetMapping("/settlement") 55 @GetMapping("/settlement")
@@ -78,6 +75,7 @@ public class AppTradeOrderController { @@ -78,6 +75,7 @@ public class AppTradeOrderController {
78 return success(true); 75 return success(true);
79 } 76 }
80 77
  78 + // TODO @芋艿:如果拼团活动、秒杀活动、砍价活动时,是不是要额外在返回活动之类的信息;
81 @GetMapping("/get-detail") 79 @GetMapping("/get-detail")
82 @Operation(summary = "获得交易订单") 80 @Operation(summary = "获得交易订单")
83 @Parameter(name = "id", description = "交易订单编号") 81 @Parameter(name = "id", description = "交易订单编号")
@@ -93,6 +91,7 @@ public class AppTradeOrderController { @@ -93,6 +91,7 @@ public class AppTradeOrderController {
93 // 查询物流公司 91 // 查询物流公司
94 DeliveryExpressDO express = order.getLogisticsId() != null && order.getLogisticsId() > 0 ? 92 DeliveryExpressDO express = order.getLogisticsId() != null && order.getLogisticsId() > 0 ?
95 deliveryExpressService.getDeliveryExpress(order.getLogisticsId()) : null; 93 deliveryExpressService.getDeliveryExpress(order.getLogisticsId()) : null;
  94 + // TODO @puhui999:如果门店自提,信息的拼接;
96 // 最终组合 95 // 最终组合
97 return success(TradeOrderConvert.INSTANCE.convert02(order, orderItems, tradeOrderProperties, express)); 96 return success(TradeOrderConvert.INSTANCE.convert02(order, orderItems, tradeOrderProperties, express));
98 } 97 }
@@ -141,7 +140,7 @@ public class AppTradeOrderController { @@ -141,7 +140,7 @@ public class AppTradeOrderController {
141 @PutMapping("/receive") 140 @PutMapping("/receive")
142 @Operation(summary = "确认交易订单收货") 141 @Operation(summary = "确认交易订单收货")
143 @Parameter(name = "id", description = "交易订单编号") 142 @Parameter(name = "id", description = "交易订单编号")
144 - public CommonResult<Boolean> takeOrder(@RequestParam("id") Long id) { 143 + public CommonResult<Boolean> receiveOrder(@RequestParam("id") Long id) {
145 tradeOrderUpdateService.receiveOrder(getLoginUserId(), id); 144 tradeOrderUpdateService.receiveOrder(getLoginUserId(), id);
146 return success(true); 145 return success(true);
147 } 146 }
@@ -150,7 +149,7 @@ public class AppTradeOrderController { @@ -150,7 +149,7 @@ public class AppTradeOrderController {
150 @Operation(summary = "取消交易订单") 149 @Operation(summary = "取消交易订单")
151 @Parameter(name = "id", description = "交易订单编号") 150 @Parameter(name = "id", description = "交易订单编号")
152 public CommonResult<Boolean> cancelOrder(@RequestParam("id") Long id) { 151 public CommonResult<Boolean> cancelOrder(@RequestParam("id") Long id) {
153 - // TODO @芋艿:未实现,mock 用 152 + tradeOrderUpdateService.cancelOrder(getLoginUserId(), id);
154 return success(true); 153 return success(true);
155 } 154 }
156 155
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/TradeAfterSaleConvert.java
@@ -72,16 +72,18 @@ public interface TradeAfterSaleConvert { @@ -72,16 +72,18 @@ public interface TradeAfterSaleConvert {
72 72
73 default TradeAfterSaleDetailRespVO convert(TradeAfterSaleDO afterSale, TradeOrderDO order, List<TradeOrderItemDO> orderItems, 73 default TradeAfterSaleDetailRespVO convert(TradeAfterSaleDO afterSale, TradeOrderDO order, List<TradeOrderItemDO> orderItems,
74 MemberUserRespDTO user, List<TradeAfterSaleLogRespDTO> logs) { 74 MemberUserRespDTO user, List<TradeAfterSaleLogRespDTO> logs) {
75 - TradeAfterSaleDetailRespVO respVO = convert(afterSale, orderItems, convertList1(logs)); 75 + TradeAfterSaleDetailRespVO respVO = convert(afterSale, orderItems);
76 // 处理用户信息 76 // 处理用户信息
77 respVO.setUser(convert(user)); 77 respVO.setUser(convert(user));
78 // 处理订单信息 78 // 处理订单信息
79 respVO.setOrder(convert(order)); 79 respVO.setOrder(convert(order));
  80 + // 处理售后日志
  81 + respVO.setAfterSaleLog(convertList1(logs));
80 return respVO; 82 return respVO;
81 } 83 }
82 List<TradeAfterSaleLogRespVO> convertList1(List<TradeAfterSaleLogRespDTO> list); 84 List<TradeAfterSaleLogRespVO> convertList1(List<TradeAfterSaleLogRespDTO> list);
83 @Mapping(target = "id", source = "afterSale.id") 85 @Mapping(target = "id", source = "afterSale.id")
84 - TradeAfterSaleDetailRespVO convert(TradeAfterSaleDO afterSale, List<TradeOrderItemDO> orderItems, List<TradeAfterSaleLogRespVO> logs); 86 + TradeAfterSaleDetailRespVO convert(TradeAfterSaleDO afterSale, List<TradeOrderItemDO> orderItems);
85 TradeOrderBaseVO convert(TradeOrderDO order); 87 TradeOrderBaseVO convert(TradeOrderDO order);
86 88
87 } 89 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
1 package cn.iocoder.yudao.module.trade.convert.order; 1 package cn.iocoder.yudao.module.trade.convert.order;
2 2
3 -import cn.hutool.core.collection.CollUtil;  
4 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@@ -37,8 +36,10 @@ import org.mapstruct.Mapping; @@ -37,8 +36,10 @@ import org.mapstruct.Mapping;
37 import org.mapstruct.Mappings; 36 import org.mapstruct.Mappings;
38 import org.mapstruct.factory.Mappers; 37 import org.mapstruct.factory.Mappers;
39 38
40 -import java.util.*;  
41 -import java.util.stream.Collectors; 39 +import java.time.LocalDateTime;
  40 +import java.util.ArrayList;
  41 +import java.util.List;
  42 +import java.util.Map;
42 43
43 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 44 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
44 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; 45 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
@@ -86,7 +87,14 @@ public interface TradeOrderConvert { @@ -86,7 +87,14 @@ public interface TradeOrderConvert {
86 default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) { 87 default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) {
87 return new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(list)); 88 return new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(list));
88 } 89 }
  90 +
  91 + default ProductSkuUpdateStockReqDTO convertNegative(List<TradeOrderItemDO> list) {
  92 + List<ProductSkuUpdateStockReqDTO.Item> items = TradeOrderConvert.INSTANCE.convertList(list);
  93 + items.forEach(item -> item.setIncrCount(-item.getIncrCount()));
  94 + return new ProductSkuUpdateStockReqDTO(items);
  95 + }
89 List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list); 96 List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
  97 +
90 @Mappings({ 98 @Mappings({
91 @Mapping(source = "skuId", target = "id"), 99 @Mapping(source = "skuId", target = "id"),
92 @Mapping(source = "count", target = "incrCount"), 100 @Mapping(source = "count", target = "incrCount"),
@@ -137,6 +145,15 @@ public interface TradeOrderConvert { @@ -137,6 +145,15 @@ public interface TradeOrderConvert {
137 orderVO.setReceiverAreaName(AreaUtils.format(order.getReceiverAreaId())); 145 orderVO.setReceiverAreaName(AreaUtils.format(order.getReceiverAreaId()));
138 // 处理用户信息 146 // 处理用户信息
139 orderVO.setUser(convert(user)); 147 orderVO.setUser(convert(user));
  148 + // TODO puhui999:模拟订单操作日志
  149 + ArrayList<TradeOrderDetailRespVO.OrderLog> orderLogs = new ArrayList<>();
  150 + for (int i = 0; i < 6; i++) {
  151 + TradeOrderDetailRespVO.OrderLog orderLog = new TradeOrderDetailRespVO.OrderLog();
  152 + orderLog.setContent("订单操作" + i);
  153 + orderLog.setCreateTime(LocalDateTime.now());
  154 + orderLogs.add(orderLog);
  155 + }
  156 + orderVO.setOrderLog(orderLogs);
140 return orderVO; 157 return orderVO;
141 } 158 }
142 159
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java
@@ -143,6 +143,8 @@ public class TradeOrderItemDO extends BaseDO { @@ -143,6 +143,8 @@ public class TradeOrderItemDO extends BaseDO {
143 * 对应 taobao 的 trade.point_fee 字段 143 * 对应 taobao 的 trade.point_fee 字段
144 */ 144 */
145 private Integer pointPrice; 145 private Integer pointPrice;
  146 + // TODO @芋艿:如果商品 vip 折扣时,到底是新增一个 vipPrice 记录优惠记录,还是 vipDiscountPrice,记录 vip 的优惠;还是直接使用 vipPrice;
  147 + // 目前 crmeb 的选择,单独一个 vipPrice 记录优惠价格;感觉不一定合理,可以在看看有赞的;
146 148
147 // ========== 售后基本信息 ========== 149 // ========== 售后基本信息 ==========
148 150
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
@@ -98,6 +98,8 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa @@ -98,6 +98,8 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
98 return afterSale; 98 return afterSale;
99 } 99 }
100 100
  101 + // TODO 芋艿:拼团失败,要不要发起售后的方式退款?还是走取消逻辑?
  102 +
101 @Override 103 @Override
102 @Transactional(rollbackFor = Exception.class) 104 @Transactional(rollbackFor = Exception.class)
103 public Long createAfterSale(Long userId, AppTradeAfterSaleCreateReqVO createReqVO) { 105 public Long createAfterSale(Long userId, AppTradeAfterSaleCreateReqVO createReqVO) {
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java
@@ -66,7 +66,7 @@ public interface TradeOrderQueryService { @@ -66,7 +66,7 @@ public interface TradeOrderQueryService {
66 Long getOrderCount(Long userId, Integer status, Boolean commonStatus); 66 Long getOrderCount(Long userId, Integer status, Boolean commonStatus);
67 67
68 /** 68 /**
69 - * 获得订单的物流轨迹 69 + * 【前台】获得订单的物流轨迹
70 * 70 *
71 * @param id 订单编号 71 * @param id 订单编号
72 * @param userId 用户编号 72 * @param userId 用户编号
@@ -74,6 +74,14 @@ public interface TradeOrderQueryService { @@ -74,6 +74,14 @@ public interface TradeOrderQueryService {
74 */ 74 */
75 List<ExpressTrackRespDTO> getExpressTrackList(Long id, Long userId); 75 List<ExpressTrackRespDTO> getExpressTrackList(Long id, Long userId);
76 76
  77 + /**
  78 + * 【后台】获得订单的物流轨迹
  79 + *
  80 + * @param id 订单编号
  81 + * @return 物流轨迹数组
  82 + */
  83 + List<ExpressTrackRespDTO> getExpressTrackList(Long id);
  84 +
77 // =================== Order Item =================== 85 // =================== Order Item ===================
78 86
79 /** 87 /**
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java
@@ -106,6 +106,27 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { @@ -106,6 +106,27 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
106 throw exception(ORDER_NOT_FOUND); 106 throw exception(ORDER_NOT_FOUND);
107 } 107 }
108 108
  109 + return getExpressTrackList(order);
  110 + }
  111 +
  112 + @Override
  113 + public List<ExpressTrackRespDTO> getExpressTrackList(Long id) {
  114 + // 查询订单
  115 + TradeOrderDO order = tradeOrderMapper.selectById(id);
  116 + if (order == null) {
  117 + throw exception(ORDER_NOT_FOUND);
  118 + }
  119 +
  120 + return getExpressTrackList(order);
  121 + }
  122 +
  123 + /**
  124 + * 获得订单的物流轨迹
  125 + *
  126 + * @param order 订单
  127 + * @return 物流轨迹
  128 + */
  129 + private List<ExpressTrackRespDTO> getExpressTrackList(TradeOrderDO order) {
109 // 查询物流公司 130 // 查询物流公司
110 if (order.getLogisticsId() == null) { 131 if (order.getLogisticsId() == null) {
111 return Collections.emptyList(); 132 return Collections.emptyList();
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
@@ -117,4 +117,11 @@ public interface TradeOrderUpdateService { @@ -117,4 +117,11 @@ public interface TradeOrderUpdateService {
117 */ 117 */
118 Long createOrderItemComment(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO); 118 Long createOrderItemComment(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO);
119 119
  120 + /**
  121 + * 【会员】取消订单
  122 + *
  123 + * @param userId 用户ID
  124 + * @param id 订单编号
  125 + */
  126 + void cancelOrder(Long userId, Long id);
120 } 127 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
@@ -96,6 +96,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -96,6 +96,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
96 private TradePriceService tradePriceService; 96 private TradePriceService tradePriceService;
97 @Resource 97 @Resource
98 private DeliveryExpressService deliveryExpressService; 98 private DeliveryExpressService deliveryExpressService;
  99 + @Resource
  100 + private TradeMessageService tradeMessageService;
99 101
100 @Resource 102 @Resource
101 private ProductSkuApi productSkuApi; 103 private ProductSkuApi productSkuApi;
@@ -105,26 +107,21 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -105,26 +107,21 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
105 private AddressApi addressApi; 107 private AddressApi addressApi;
106 @Resource 108 @Resource
107 private CouponApi couponApi; 109 private CouponApi couponApi;
108 - 110 + @Resource
  111 + private CombinationRecordApi combinationRecordApi;
  112 + @Resource
  113 + private BargainRecordApi bargainRecordApi;
109 @Resource 114 @Resource
110 private MemberUserApi memberUserApi; 115 private MemberUserApi memberUserApi;
111 @Resource 116 @Resource
112 private MemberLevelApi memberLevelApi; 117 private MemberLevelApi memberLevelApi;
113 @Resource 118 @Resource
114 private MemberPointApi memberPointApi; 119 private MemberPointApi memberPointApi;
115 -  
116 @Resource 120 @Resource
117 private ProductCommentApi productCommentApi; 121 private ProductCommentApi productCommentApi;
118 - @Resource  
119 - private TradeMessageService tradeMessageService;  
120 - @Resource  
121 - private TradeOrderProperties tradeOrderProperties;  
122 -  
123 - @Resource  
124 - private CombinationRecordApi combinationRecordApi;  
125 122
126 @Resource 123 @Resource
127 - private BargainRecordApi bargainRecordApi; 124 + private TradeOrderProperties tradeOrderProperties;
128 125
129 // =================== Order =================== 126 // =================== Order ===================
130 127
@@ -179,15 +176,17 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -179,15 +176,17 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
179 @Override 176 @Override
180 @Transactional(rollbackFor = Exception.class) 177 @Transactional(rollbackFor = Exception.class)
181 public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) { 178 public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) {
182 - // 2. 价格计算 179 + // 1. 价格计算
183 TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO); 180 TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
184 - // 3.1 插入 TradeOrderDO 订单 181 +
  182 + // 2.1 插入 TradeOrderDO 订单
185 TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO); 183 TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO);
186 - // 3.2 插入 TradeOrderItemDO 订单项 184 + // 2.2 插入 TradeOrderItemDO 订单项
187 List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO); 185 List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO);
188 - // 订单创建完后的逻辑 186 +
  187 + // 3. 订单创建完后的逻辑
189 afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO); 188 afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO);
190 - // 3.3 校验订单类型 189 + // 3.1 拼团的特殊逻辑
191 // TODO @puhui999:这个逻辑,先抽个小方法;未来要通过设计模式,把这些拼团之类的逻辑,抽象出去 190 // TODO @puhui999:这个逻辑,先抽个小方法;未来要通过设计模式,把这些拼团之类的逻辑,抽象出去
192 // 拼团 191 // 拼团
193 if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) { 192 if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
@@ -205,15 +204,19 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -205,15 +204,19 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
205 204
206 combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, orderItemDO, createReqVO, user)); 205 combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, orderItemDO, createReqVO, user));
207 } 206 }
  207 + // 3.2 秒杀的特殊逻辑
208 // TODO 秒杀扣减库存是下单就扣除还是等待订单支付成功再扣除 208 // TODO 秒杀扣减库存是下单就扣除还是等待订单支付成功再扣除
209 if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), order.getType())) { 209 if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), order.getType())) {
210 210
211 } 211 }
  212 + // 3.3 砍价的特殊逻辑
212 213
213 // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来! 214 // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
214 return order; 215 return order;
215 } 216 }
216 217
  218 + // TODO @puhui999:订单超时,自动取消;
  219 +
217 /** 220 /**
218 * 校验收件地址是否存在 221 * 校验收件地址是否存在
219 * 222 *
@@ -239,7 +242,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -239,7 +242,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
239 } 242 }
240 TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address); 243 TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address);
241 order.setType(validateActivity(createReqVO)); 244 order.setType(validateActivity(createReqVO));
242 - order.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的; 245 + order.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @puhui999: 参考支付订单,的 no 生成哈;
243 order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); 246 order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
244 order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); 247 order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
245 order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); 248 order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
@@ -251,6 +254,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -251,6 +254,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
251 // 退款信息 254 // 退款信息
252 order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); 255 order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
253 tradeOrderMapper.insert(order); 256 tradeOrderMapper.insert(order);
  257 + // TODO @puhui999:如果是门店订单,则需要生成核销码;
254 return order; 258 return order;
255 } 259 }
256 260
@@ -291,7 +295,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -291,7 +295,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
291 TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems, 295 TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems,
292 TradePriceCalculateRespBO calculateRespBO) { 296 TradePriceCalculateRespBO calculateRespBO) {
293 // 下单时扣减商品库存 297 // 下单时扣减商品库存
294 - productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems)); 298 + // TODO @puhui999:扣库存,需要前置;
  299 + // 1)如果是秒杀商品:额外扣减秒杀的库存;
  300 + // 2)如果是拼团活动:额外扣减拼团的库存;
  301 + // 3)如果是砍价活动:额外扣减砍价的库存;
  302 + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
295 303
296 // 删除购物车商品 304 // 删除购物车商品
297 Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId); 305 Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
@@ -299,9 +307,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -299,9 +307,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
299 cartService.deleteCart(userId, cartIds); 307 cartService.deleteCart(userId, cartIds);
300 } 308 }
301 309
302 - // 扣减积分 TODO 芋艿:待实现 310 + // 扣减积分 TODO 芋艿:待实现,需要前置;
  311 + // 这个是不是应该放到支付成功之后?如果支付后的话,可能积分可以重复使用哈。资源类,都要预扣
303 312
304 - // 有使用优惠券时更新 313 + // 有使用优惠券时更新 TODO 芋艿:需要前置;
305 if (createReqVO.getCouponId() != null) { 314 if (createReqVO.getCouponId() != null) {
306 couponApi.useCoupon(new CouponUseReqDTO().setId(createReqVO.getCouponId()).setUserId(userId) 315 couponApi.useCoupon(new CouponUseReqDTO().setId(createReqVO.getCouponId()).setUserId(userId)
307 .setOrderId(tradeOrderDO.getId())); 316 .setOrderId(tradeOrderDO.getId()));
@@ -510,6 +519,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -510,6 +519,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
510 // TODO 芋艿:lili 发送订单变化的消息 519 // TODO 芋艿:lili 发送订单变化的消息
511 520
512 // TODO 芋艿:lili 发送商品被购买完成的数据 521 // TODO 芋艿:lili 发送商品被购买完成的数据
  522 +
  523 + // TODO 芋艿:销售佣金的记录;
  524 +
  525 + // TODO 芋艿:获得积分;
513 } 526 }
514 527
515 @Override 528 @Override
@@ -529,11 +542,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -529,11 +542,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
529 if (order.getPayStatus()) { 542 if (order.getPayStatus()) {
530 throw exception(ORDER_UPDATE_PRICE_FAIL_PAID); 543 throw exception(ORDER_UPDATE_PRICE_FAIL_PAID);
531 } 544 }
  545 + // TODO @puhui999:如果改价,需要校验下是否真的变化;
532 546
533 // 更新 547 // 更新
534 // TODO @puhui999:TradeOrderItemDO 需要做 adjustPrice 的分摊;另外,支付订单那的价格,需要 update 下; 548 // TODO @puhui999:TradeOrderItemDO 需要做 adjustPrice 的分摊;另外,支付订单那的价格,需要 update 下;
535 TradeOrderDO update = TradeOrderConvert.INSTANCE.convert(reqVO); 549 TradeOrderDO update = TradeOrderConvert.INSTANCE.convert(reqVO);
536 update.setPayPrice(update.getPayPrice() + update.getAdjustPrice()); 550 update.setPayPrice(update.getPayPrice() + update.getAdjustPrice());
  551 + // TODO @芋艿:改价时,赠送的积分,要不要做改动???
537 tradeOrderMapper.updateById(update); 552 tradeOrderMapper.updateById(update);
538 } 553 }
539 554
@@ -651,10 +666,46 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -651,10 +666,46 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
651 List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId()); 666 List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
652 if (!anyMatch(orderItems, item -> Objects.equals(item.getCommentStatus(), Boolean.FALSE))) { 667 if (!anyMatch(orderItems, item -> Objects.equals(item.getCommentStatus(), Boolean.FALSE))) {
653 tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setCommentStatus(Boolean.TRUE)); 668 tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setCommentStatus(Boolean.TRUE));
  669 + // TODO 待实现:已完成评价,要不要写一条订单日志?目前 crmeb 会写,有赞可以研究下
654 } 670 }
655 return comment; 671 return comment;
656 } 672 }
657 673
  674 + @Override
  675 + public void cancelOrder(Long userId, Long id) {
  676 + // 校验存在
  677 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId);
  678 + if (order == null) {
  679 + throw exception(ORDER_NOT_FOUND);
  680 + }
  681 + // 校验状态
  682 + if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) {
  683 + throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
  684 + }
  685 +
  686 + // 1.更新 TradeOrderDO 状态为已取消
  687 + int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
  688 + new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
  689 + .setCancelTime(LocalDateTime.now())
  690 + .setCancelType(TradeOrderCancelTypeEnum.MEMBER_CANCEL.getType()));
  691 + if (updateCount == 0) {
  692 + throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
  693 + }
  694 +
  695 + // 2.回滚库存
  696 + List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
  697 + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
  698 +
  699 + // 3.回滚优惠券
  700 + couponApi.returnUsedCoupon(order.getCouponId());
  701 +
  702 + // 4.回滚积分:积分是支付成功后才增加的吧? 回复:每个项目不同,目前看下来,确认收货貌似更合适,我再看看其它项目的业务选择;
  703 +
  704 + // TODO 芋艿:OrderLog
  705 +
  706 + // TODO 芋艿:lili 发送订单变化的消息
  707 + }
  708 +
658 /** 709 /**
659 * 判断指定订单的所有订单项,是不是都售后成功 710 * 判断指定订单的所有订单项,是不是都售后成功
660 * 711 *
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java
@@ -41,6 +41,7 @@ public class TradePriceServiceImpl implements TradePriceService { @@ -41,6 +41,7 @@ public class TradePriceServiceImpl implements TradePriceService {
41 @Resource 41 @Resource
42 private List<TradePriceCalculator> priceCalculators; 42 private List<TradePriceCalculator> priceCalculators;
43 43
  44 + // TODO @疯狂:需要搞个 TradePriceCalculator,计算赠送积分;
44 @Override 45 @Override
45 public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) { 46 public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) {
46 // 1.1 获得商品 SKU 数组 47 // 1.1 获得商品 SKU 数组
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java
@@ -102,7 +102,10 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator { @@ -102,7 +102,10 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
102 Predicate<TradePriceCalculateRespBO.OrderItem> matchPredicate = TradePriceCalculateRespBO.OrderItem::getSelected; 102 Predicate<TradePriceCalculateRespBO.OrderItem> matchPredicate = TradePriceCalculateRespBO.OrderItem::getSelected;
103 if (PromotionProductScopeEnum.SPU.getScope().equals(coupon.getProductScope())) { 103 if (PromotionProductScopeEnum.SPU.getScope().equals(coupon.getProductScope())) {
104 matchPredicate = matchPredicate // 额外加如下条件 104 matchPredicate = matchPredicate // 额外加如下条件
105 - .and(orderItem -> coupon.getProductSpuIds().contains(orderItem.getSpuId())); 105 + .and(orderItem -> coupon.getProductScopeValues().contains(orderItem.getSpuId()));
  106 + } else if (PromotionProductScopeEnum.CATEGORY.getScope().equals(coupon.getProductScope())) {
  107 + matchPredicate = matchPredicate // 额外加如下条件
  108 + .and(orderItem -> coupon.getProductScopeValues().contains(orderItem.getCategoryId()));
106 } 109 }
107 return filterList(result.getItems(), matchPredicate); 110 return filterList(result.getItems(), matchPredicate);
108 } 111 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
@@ -42,6 +42,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { @@ -42,6 +42,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
42 42
43 @Override 43 @Override
44 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { 44 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  45 + // TODO @芋艿:如果门店自提,需要校验是否开启;
45 // 1.1 判断配送方式 46 // 1.1 判断配送方式
46 if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) { 47 if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
47 return; 48 return;
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java
@@ -65,7 +65,7 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest { @@ -65,7 +65,7 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest {
65 65
66 // mock 方法(优惠劵 Coupon 信息) 66 // mock 方法(优惠劵 Coupon 信息)
67 CouponRespDTO coupon = randomPojo(CouponRespDTO.class, o -> o.setId(1024L).setName("程序员节") 67 CouponRespDTO coupon = randomPojo(CouponRespDTO.class, o -> o.setId(1024L).setName("程序员节")
68 - .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L)) 68 + .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
69 .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()) 69 .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType())
70 .setDiscountPercent(50).setDiscountLimitPrice(70)); 70 .setDiscountPercent(50).setDiscountLimitPrice(70));
71 when(couponApi.validateCoupon(eq(new CouponValidReqDTO().setId(1024L).setUserId(233L)))).thenReturn(coupon); 71 when(couponApi.validateCoupon(eq(new CouponValidReqDTO().setId(1024L).setUserId(233L)))).thenReturn(coupon);