Commit 8f86f65884df2984cfb43f3c6c2aab1fba52b367
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<CouponDO> { | @@ -55,4 +55,11 @@ public interface CouponMapper extends BaseMapperX<CouponDO> { | ||
| 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); |