Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17
This commit is contained in:
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.product.api.spu.dto;
|
||||
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品 SPU 信息 Response DTO
|
||||
*
|
||||
@@ -68,6 +70,13 @@ public class ProductSpuRespDTO {
|
||||
|
||||
// ========== 物流相关字段 =========
|
||||
|
||||
/**
|
||||
* 配送方式数组
|
||||
*
|
||||
* 对应 DeliveryTypeEnum 枚举
|
||||
*/
|
||||
private List<Integer> deliveryTypes;
|
||||
|
||||
/**
|
||||
* 物流配置模板编号
|
||||
*
|
||||
|
||||
@@ -67,7 +67,7 @@ public class ProductCommentServiceImpl implements ProductCommentService {
|
||||
// 校验 SPU
|
||||
ProductSpuDO spu = validateSpu(sku.getSpuId());
|
||||
// 校验评论
|
||||
validateCommentExists(createReqDTO.getUserId(), createReqDTO.getOrderId());
|
||||
validateCommentExists(createReqDTO.getUserId(), createReqDTO.getOrderItemId());
|
||||
// 获取用户详细信息
|
||||
MemberUserRespDTO user = memberUserApi.getUser(createReqDTO.getUserId());
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@ package cn.iocoder.yudao.module.promotion.api.coupon;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 优惠劵 API 接口
|
||||
*
|
||||
@@ -35,4 +37,21 @@ public interface CouponApi {
|
||||
*/
|
||||
CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO);
|
||||
|
||||
/**
|
||||
* 【管理员】给指定用户批量发送优惠券
|
||||
*
|
||||
* @param giveCoupons key: 优惠劵模版编号,value:对应的数量
|
||||
* @param userId 用户编号
|
||||
* @return 优惠券编号列表
|
||||
*/
|
||||
List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId);
|
||||
|
||||
/**
|
||||
* 【管理员】作废指定用户的指定优惠劵
|
||||
*
|
||||
* @param giveCouponIds 赠送的优惠券编号
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
void invalidateCouponsByAdmin(List<Long> giveCouponIds, Long userId);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package cn.iocoder.yudao.module.promotion.api.reward.dto;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 满减送活动的匹配 Response DTO
|
||||
@@ -21,28 +26,50 @@ public class RewardActivityMatchRespDTO {
|
||||
* 活动标题
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
private LocalDateTime startTime;
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private LocalDateTime endTime;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
/**
|
||||
* 条件类型
|
||||
*
|
||||
* 枚举 {@link PromotionConditionTypeEnum}
|
||||
*/
|
||||
private Integer conditionType;
|
||||
/**
|
||||
* 商品范围
|
||||
*
|
||||
* 枚举 {@link PromotionProductScopeEnum}
|
||||
*/
|
||||
private Integer productScope;
|
||||
/**
|
||||
* 商品 SPU 编号的数组
|
||||
*/
|
||||
private List<Long> productScopeValues;
|
||||
/**
|
||||
* 优惠规则的数组
|
||||
*/
|
||||
private List<Rule> rules;
|
||||
|
||||
/**
|
||||
* 商品 SPU 编号的数组
|
||||
*/
|
||||
private List<Long> spuIds;
|
||||
|
||||
// TODO 芋艿:后面 RewardActivityRespDTO 有了之后,Rule 可以放过去
|
||||
/**
|
||||
* 优惠规则
|
||||
*/
|
||||
@Data
|
||||
public static class Rule {
|
||||
public static class Rule implements Serializable {
|
||||
|
||||
/**
|
||||
* 优惠门槛
|
||||
@@ -64,13 +91,14 @@ public class RewardActivityMatchRespDTO {
|
||||
*/
|
||||
private Integer point;
|
||||
/**
|
||||
* 赠送的优惠劵编号的数组
|
||||
* 赠送的优惠劵
|
||||
*
|
||||
* key: 优惠劵模版编号
|
||||
* value:对应的优惠券数量
|
||||
*
|
||||
* 目的:用于订单支付后赠送优惠券
|
||||
*/
|
||||
private List<Long> couponIds;
|
||||
/**
|
||||
* 赠送的优惠券数量的数组
|
||||
*/
|
||||
private List<Integer> couponCounts;
|
||||
private Map<Long, Integer> giveCouponTemplateCounts;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_002, "满减送活动已关闭,不能修改");
|
||||
ErrorCode REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_006_003, "满减送活动未关闭,不能删除");
|
||||
ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_006_004, "满减送活动已关闭,不能重复关闭");
|
||||
ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1_013_006_005, "满减送活动已结束,不能关闭");
|
||||
ErrorCode REWARD_ACTIVITY_SCOPE_ALL_EXISTS = new ErrorCode(1_013_006_005, "已存在商品范围为全场的满减送活动");
|
||||
ErrorCode REWARD_ACTIVITY_SCOPE_CATEGORY_EXISTS = new ErrorCode(1_013_006_006, "存在商品类型参加了其它满减送活动");
|
||||
|
||||
// ========== TODO 空着 1-013-007-000 ============
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 营销的商品范围枚举
|
||||
@@ -15,10 +16,9 @@ import java.util.Arrays;
|
||||
@AllArgsConstructor
|
||||
public enum PromotionProductScopeEnum implements IntArrayValuable {
|
||||
|
||||
ALL(1, "通用券"), // 全部商品
|
||||
SPU(2, "商品券"), // 指定商品
|
||||
CATEGORY(3, "品类券"), // 指定品类
|
||||
;
|
||||
ALL(1, "全部商品"),
|
||||
SPU(2, "指定商品"),
|
||||
CATEGORY(3, "指定品类");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionProductScopeEnum::getScope).toArray();
|
||||
|
||||
@@ -36,4 +36,16 @@ public enum PromotionProductScopeEnum implements IntArrayValuable {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static boolean isAll(Integer scope) {
|
||||
return Objects.equals(scope, ALL.scope);
|
||||
}
|
||||
|
||||
public static boolean isSpu(Integer scope) {
|
||||
return Objects.equals(scope, SPU.scope);
|
||||
}
|
||||
|
||||
public static boolean isCategory(Integer scope) {
|
||||
return Objects.equals(scope, CATEGORY.scope);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@ public enum CouponStatusEnum implements IntArrayValuable {
|
||||
|
||||
UNUSED(1, "未使用"),
|
||||
USED(2, "已使用"),
|
||||
EXPIRE(3, "已过期"),
|
||||
;
|
||||
EXPIRE(3, "已过期");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CouponStatusEnum::getStatus).toArray();
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
|
||||
import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 优惠劵 API 实现类
|
||||
@@ -41,4 +43,14 @@ public class CouponApiImpl implements CouponApi {
|
||||
return CouponConvert.INSTANCE.convert(coupon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId) {
|
||||
return couponService.takeCouponsByAdmin(giveCoupons, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCouponsByAdmin(List<Long> giveCouponIds, Long userId) {
|
||||
couponService.invalidateCouponsByAdmin(giveCouponIds, userId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,23 +2,22 @@ package cn.iocoder.yudao.module.promotion.controller.admin.reward;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityRespVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
|
||||
import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 满减送活动")
|
||||
@@ -69,7 +68,7 @@ public class RewardActivityController {
|
||||
@PreAuthorize("@ss.hasPermission('promotion:reward-activity:query')")
|
||||
public CommonResult<RewardActivityRespVO> getRewardActivity(@RequestParam("id") Long id) {
|
||||
RewardActivityDO rewardActivity = rewardActivityService.getRewardActivity(id);
|
||||
return success(RewardActivityConvert.INSTANCE.convert(rewardActivity));
|
||||
return success(BeanUtils.toBean(rewardActivity, RewardActivityRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@@ -77,7 +76,7 @@ public class RewardActivityController {
|
||||
@PreAuthorize("@ss.hasPermission('promotion:reward-activity:query')")
|
||||
public CommonResult<PageResult<RewardActivityRespVO>> getRewardActivityPage(@Valid RewardActivityPageReqVO pageVO) {
|
||||
PageResult<RewardActivityDO> pageResult = rewardActivityService.getRewardActivityPage(pageVO);
|
||||
return success(RewardActivityConvert.INSTANCE.convertPage(pageResult));
|
||||
return success(BeanUtils.toBean(pageResult, RewardActivityRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,18 +6,17 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.AssertTrue;
|
||||
import jakarta.validation.constraints.Future;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 满减送活动 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
@@ -32,12 +31,10 @@ public class RewardActivityBaseVO {
|
||||
|
||||
@Schema(description = "开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "开始时间不能为空")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime startTime;
|
||||
|
||||
@Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "结束时间不能为空")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@Future(message = "结束时间必须大于当前时间")
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@@ -54,8 +51,8 @@ public class RewardActivityBaseVO {
|
||||
@InEnum(value = PromotionProductScopeEnum.class, message = "商品范围必须是 {value}")
|
||||
private Integer productScope;
|
||||
|
||||
@Schema(description = "商品 SPU 编号的数组", example = "1,2,3")
|
||||
private List<Long> productSpuIds;
|
||||
@Schema(description = "商品范围编号的数组", example = "[1, 3]")
|
||||
private List<Long> productScopeValues;
|
||||
|
||||
/**
|
||||
* 优惠规则的数组
|
||||
@@ -76,24 +73,28 @@ public class RewardActivityBaseVO {
|
||||
private Integer discountPrice;
|
||||
|
||||
@Schema(description = "是否包邮", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
@NotNull(message = "规则是否包邮不能为空")
|
||||
private Boolean freeDelivery;
|
||||
|
||||
@Schema(description = "赠送的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
@Min(value = 1L, message = "赠送的积分必须大于等于 1")
|
||||
private Integer point;
|
||||
|
||||
@Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3")
|
||||
private List<Long> couponIds;
|
||||
@Schema(description = "赠送的优惠劵编号的数组")
|
||||
private Map<Long, Integer> giveCouponTemplateCounts;
|
||||
|
||||
@Schema(description = "赠送的优惠券数量的数组", example = "1,2,3")
|
||||
private List<Integer> couponCounts;
|
||||
|
||||
@AssertTrue(message = "优惠劵和数量必须一一对应")
|
||||
@AssertTrue(message = "赠送的积分不能小于 0")
|
||||
@JsonIgnore
|
||||
public boolean isCouponCountsValid() {
|
||||
return CollUtil.size(couponCounts) == CollUtil.size(couponCounts);
|
||||
public boolean isPointValid() {
|
||||
return point == null || point >= 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@AssertTrue(message = "商品范围编号的数组不能为空")
|
||||
@JsonIgnore
|
||||
public boolean isProductScopeValuesValid() {
|
||||
return Objects.equals(productScope, PromotionProductScopeEnum.ALL.getScope()) // 全部范围时,可以为空
|
||||
|| CollUtil.isNotEmpty(productScopeValues);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package cn.iocoder.yudao.module.promotion.controller.app.activity;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
|
||||
@@ -11,7 +14,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivit
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
|
||||
import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
|
||||
@@ -30,7 +33,6 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
@@ -52,6 +54,9 @@ public class AppActivityController {
|
||||
@Resource
|
||||
private RewardActivityService rewardActivityService;
|
||||
|
||||
@Resource
|
||||
private ProductSpuApi productSpuApi;
|
||||
|
||||
@GetMapping("/list-by-spu-id")
|
||||
@Operation(summary = "获得单个商品,近期参与的每个活动")
|
||||
@Parameter(name = "spuId", description = "商品编号", required = true)
|
||||
@@ -87,7 +92,7 @@ public class AppActivityController {
|
||||
// 4. 限时折扣活动
|
||||
getDiscountActivities(spuIds, now, activityList);
|
||||
// 5. 满减送活动
|
||||
getRewardActivities(spuIds, now, activityList);
|
||||
getRewardActivityList(spuIds, now, activityList);
|
||||
return activityList;
|
||||
}
|
||||
|
||||
@@ -144,28 +149,51 @@ public class AppActivityController {
|
||||
item.getName(), productMap.get(item.getId()), item.getStartTime(), item.getEndTime())));
|
||||
}
|
||||
|
||||
private void getRewardActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
|
||||
// TODO @puhui999:有 3 范围,不只 spuId,还有 categoryId,全部
|
||||
List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(
|
||||
spuIds, PromotionActivityStatusEnum.RUN.getStatus(), now);
|
||||
private void getRewardActivityList(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
|
||||
// 1.1 获得所有的活动
|
||||
List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityListByStatusAndDateTimeLt(
|
||||
CommonStatusEnum.ENABLE.getStatus(), now);
|
||||
if (CollUtil.isEmpty(rewardActivityList)) {
|
||||
return;
|
||||
}
|
||||
// 1.2 获得所有的商品信息
|
||||
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(spuIds);
|
||||
if (CollUtil.isEmpty(spuList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Long, Optional<RewardActivityDO>> spuIdAndActivityMap = spuIds.stream()
|
||||
.collect(Collectors.toMap(
|
||||
spuId -> spuId,
|
||||
spuId -> rewardActivityList.stream()
|
||||
.filter(activity -> activity.getProductSpuIds().contains(spuId))
|
||||
.max(Comparator.comparing(RewardActivityDO::getCreateTime))));
|
||||
for (Long supId : spuIdAndActivityMap.keySet()) {
|
||||
if (spuIdAndActivityMap.get(supId).isEmpty()) {
|
||||
// 2. 构建活动
|
||||
for (RewardActivityDO rewardActivity : rewardActivityList) {
|
||||
// 情况一:所有商品都能参加
|
||||
if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope())) {
|
||||
buildAppActivityRespVO(rewardActivity, spuIds, activityList);
|
||||
}
|
||||
// 情况二:指定商品参加
|
||||
if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope())) {
|
||||
List<Long> fSpuIds = spuList.stream().map(ProductSpuRespDTO::getId).filter(id ->
|
||||
rewardActivity.getProductScopeValues().contains(id)).toList();
|
||||
buildAppActivityRespVO(rewardActivity, fSpuIds, activityList);
|
||||
}
|
||||
// 情况三:指定商品类型参加
|
||||
if (PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) {
|
||||
List<Long> fSpuIds = spuList.stream().filter(spuItem -> rewardActivity.getProductScopeValues()
|
||||
.contains(spuItem.getCategoryId())).map(ProductSpuRespDTO::getId).toList();
|
||||
buildAppActivityRespVO(rewardActivity, fSpuIds, activityList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildAppActivityRespVO(RewardActivityDO rewardActivity, Collection<Long> spuIds,
|
||||
List<AppActivityRespVO> activityList) {
|
||||
for (Long spuId : spuIds) {
|
||||
// 校验商品是否已经加入过活动
|
||||
if (anyMatch(activityList, appActivity -> ObjUtil.equal(appActivity.getId(), rewardActivity.getId()) &&
|
||||
ObjUtil.equal(appActivity.getSpuId(), spuId))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get();
|
||||
activityList.add(new AppActivityRespVO(rewardActivityDO.getId(), PromotionTypeEnum.REWARD_ACTIVITY.getType(),
|
||||
rewardActivityDO.getName(), supId, rewardActivityDO.getStartTime(), rewardActivityDO.getEndTime()));
|
||||
activityList.add(new AppActivityRespVO(rewardActivity.getId(),
|
||||
PromotionTypeEnum.REWARD_ACTIVITY.getType(), rewardActivity.getName(), spuId,
|
||||
rewardActivity.getStartTime(), rewardActivity.getEndTime()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package cn.iocoder.yudao.module.promotion.convert.reward;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityRespVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* 满减送活动 Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface RewardActivityConvert {
|
||||
|
||||
RewardActivityConvert INSTANCE = Mappers.getMapper(RewardActivityConvert.class);
|
||||
|
||||
RewardActivityDO convert(RewardActivityCreateReqVO bean);
|
||||
|
||||
RewardActivityDO convert(RewardActivityUpdateReqVO bean);
|
||||
|
||||
RewardActivityRespVO convert(RewardActivityDO bean);
|
||||
|
||||
PageResult<RewardActivityRespVO> convertPage(PageResult<RewardActivityDO> page);
|
||||
|
||||
}
|
||||
@@ -50,7 +50,6 @@ public class CouponDO extends BaseDO {
|
||||
*
|
||||
* 枚举 {@link CouponStatusEnum}
|
||||
*/
|
||||
// TODO 芋艿:已作废?
|
||||
private Integer status;
|
||||
|
||||
// TODO 芋艿:发放 adminid?
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package cn.iocoder.yudao.module.promotion.dal.dataobject.reward;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
@@ -16,6 +16,7 @@ import lombok.EqualsAndHashCode;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 满减送活动 DO
|
||||
@@ -40,7 +41,7 @@ public class RewardActivityDO extends BaseDO {
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link PromotionActivityStatusEnum}
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
@@ -71,7 +72,7 @@ public class RewardActivityDO extends BaseDO {
|
||||
* 商品 SPU 编号的数组
|
||||
*/
|
||||
@TableField(typeHandler = LongListTypeHandler.class)
|
||||
private List<Long> productSpuIds;
|
||||
private List<Long> productScopeValues;
|
||||
/**
|
||||
* 优惠规则的数组
|
||||
*/
|
||||
@@ -104,13 +105,14 @@ public class RewardActivityDO extends BaseDO {
|
||||
*/
|
||||
private Integer point;
|
||||
/**
|
||||
* 赠送的优惠劵编号的数组
|
||||
* 赠送的优惠劵
|
||||
*
|
||||
* key: 优惠劵模版编号
|
||||
* value:对应的优惠券数量
|
||||
*
|
||||
* 目的:用于订单支付后赠送优惠券
|
||||
*/
|
||||
private List<Long> couponIds;
|
||||
/**
|
||||
* 赠送的优惠券数量的数组
|
||||
*/
|
||||
private List<Integer> couponCounts;
|
||||
private Map<Long, Integer> giveCouponTemplateCounts;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -30,10 +30,6 @@ public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> {
|
||||
.orderByDesc(RewardActivityDO::getId));
|
||||
}
|
||||
|
||||
default List<RewardActivityDO> selectListByStatus(Collection<Integer> statuses) {
|
||||
return selectList(RewardActivityDO::getStatus, statuses);
|
||||
}
|
||||
|
||||
default List<RewardActivityDO> selectListByProductScopeAndStatus(Integer productScope, Integer status) {
|
||||
return selectList(new LambdaQueryWrapperX<RewardActivityDO>()
|
||||
.eq(RewardActivityDO::getProductScope, productScope)
|
||||
@@ -53,16 +49,16 @@ public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> {
|
||||
* 获取指定活动编号的活动列表且
|
||||
* 开始时间和结束时间小于给定时间 dateTime 的活动列表
|
||||
*
|
||||
* @param ids 活动编号
|
||||
* @param status 状态
|
||||
* @param dateTime 指定日期
|
||||
* @return 活动列表
|
||||
*/
|
||||
default List<RewardActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
|
||||
default List<RewardActivityDO> selectListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) {
|
||||
return selectList(new LambdaQueryWrapperX<RewardActivityDO>()
|
||||
.in(RewardActivityDO::getId, ids)
|
||||
.eq(RewardActivityDO::getStatus, status)
|
||||
.lt(RewardActivityDO::getStartTime, dateTime)
|
||||
.gt(RewardActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动
|
||||
.orderByDesc(RewardActivityDO::getCreateTime)
|
||||
.orderByAsc(RewardActivityDO::getStartTime)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStat
|
||||
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
|
||||
import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -37,7 +38,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
@@ -335,7 +339,8 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
|
||||
List<CombinationRecordDO> headAndRecords = updateBatchCombinationRecords(headRecord,
|
||||
CombinationRecordStatusEnum.FAILED);
|
||||
// 2. 订单取消
|
||||
headAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId()));
|
||||
headAndRecords.forEach(item -> tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId(),
|
||||
TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,14 +38,6 @@ public interface CouponService {
|
||||
*/
|
||||
void validCoupon(CouponDO coupon);
|
||||
|
||||
/**
|
||||
* 获得优惠劵分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 优惠劵分页
|
||||
*/
|
||||
PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 使用优惠劵
|
||||
*
|
||||
@@ -69,42 +61,44 @@ public interface CouponService {
|
||||
*/
|
||||
void deleteCoupon(Long id);
|
||||
|
||||
/**
|
||||
* 获得用户的优惠劵列表
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param status 优惠劵状态
|
||||
* @return 优惠劵列表
|
||||
*/
|
||||
List<CouponDO> getCouponList(Long userId, Integer status);
|
||||
|
||||
/**
|
||||
* 获得未使用的优惠劵数量
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @return 未使用的优惠劵数量
|
||||
*/
|
||||
Long getUnusedCouponCount(Long userId);
|
||||
|
||||
/**
|
||||
* 领取优惠券
|
||||
*
|
||||
* @param templateId 优惠券模板编号
|
||||
* @param userIds 用户编号列表
|
||||
* @param takeType 领取方式
|
||||
* @return key: userId, value: 优惠券编号列表
|
||||
*/
|
||||
void takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType);
|
||||
Map<Long, List<Long>> takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType);
|
||||
|
||||
/**
|
||||
* 【管理员】给用户发送优惠券
|
||||
*
|
||||
* @param templateId 优惠券模板编号
|
||||
* @param userIds 用户编号列表
|
||||
* @return key: userId, value: 优惠券编号列表
|
||||
*/
|
||||
default void takeCouponByAdmin(Long templateId, Set<Long> userIds) {
|
||||
takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN);
|
||||
default Map<Long, List<Long>> takeCouponByAdmin(Long templateId, Set<Long> userIds) {
|
||||
return takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【管理员】给指定用户批量发送优惠券
|
||||
*
|
||||
* @param giveCoupons key: 优惠劵模版编号,value:对应的数量
|
||||
* @param userId 用户编号
|
||||
* @return 优惠券编号列表
|
||||
*/
|
||||
List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId);
|
||||
|
||||
/**
|
||||
* 【管理员】作废指定用户的指定优惠劵
|
||||
*
|
||||
* @param giveCouponIds 赠送的优惠券编号
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
void invalidateCouponsByAdmin(List<Long> giveCouponIds, Long userId);
|
||||
|
||||
/**
|
||||
* 【会员】领取优惠券
|
||||
*
|
||||
@@ -122,6 +116,49 @@ public interface CouponService {
|
||||
*/
|
||||
void takeCouponByRegister(Long userId);
|
||||
|
||||
/**
|
||||
* 过期优惠券
|
||||
*
|
||||
* @return 过期数量
|
||||
*/
|
||||
int expireCoupon();
|
||||
|
||||
// ======================= 查询相关 =======================
|
||||
|
||||
/**
|
||||
* 获得未使用的优惠劵数量
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @return 未使用的优惠劵数量
|
||||
*/
|
||||
Long getUnusedCouponCount(Long userId);
|
||||
|
||||
/**
|
||||
* 获得优惠劵分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 优惠劵分页
|
||||
*/
|
||||
PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得用户的优惠劵列表
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param status 优惠劵状态
|
||||
* @return 优惠劵列表
|
||||
*/
|
||||
List<CouponDO> getCouponList(Long userId, Integer status);
|
||||
|
||||
/**
|
||||
* 统计会员领取优惠券的数量
|
||||
*
|
||||
* @param templateIds 优惠券模板编号列表
|
||||
* @param userId 用户编号
|
||||
* @return 领取优惠券的数量
|
||||
*/
|
||||
Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId);
|
||||
|
||||
/**
|
||||
* 获取会员领取指定优惠券的数量
|
||||
*
|
||||
@@ -134,15 +171,6 @@ public interface CouponService {
|
||||
return MapUtil.getInt(map, templateId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计会员领取优惠券的数量
|
||||
*
|
||||
* @param templateIds 优惠券模板编号列表
|
||||
* @param userId 用户编号
|
||||
* @return 领取优惠券的数量
|
||||
*/
|
||||
Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId);
|
||||
|
||||
/**
|
||||
* 获取用户匹配的优惠券列表
|
||||
*
|
||||
@@ -152,13 +180,6 @@ public interface CouponService {
|
||||
*/
|
||||
List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);
|
||||
|
||||
/**
|
||||
* 过期优惠券
|
||||
*
|
||||
* @return 过期数量
|
||||
*/
|
||||
int expireCoupon();
|
||||
|
||||
/**
|
||||
* 获取用户是否可以领取优惠券
|
||||
*
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
|
||||
import cn.hutool.core.collection.CollStreamUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
@@ -19,19 +20,19 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
@@ -76,20 +77,6 @@ public class CouponServiceImpl implements CouponService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
|
||||
// 获得用户编号
|
||||
if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
|
||||
List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
|
||||
if (CollUtil.isEmpty(users)) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId));
|
||||
}
|
||||
// 分页查询
|
||||
return couponMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useCoupon(Long id, Long userId, Long orderId) {
|
||||
// 校验优惠劵
|
||||
@@ -147,25 +134,8 @@ public class CouponServiceImpl implements CouponService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CouponDO> getCouponList(Long userId, Integer status) {
|
||||
return couponMapper.selectListByUserIdAndStatus(userId, status);
|
||||
}
|
||||
|
||||
private CouponDO validateCouponExists(Long id) {
|
||||
CouponDO coupon = couponMapper.selectById(id);
|
||||
if (coupon == null) {
|
||||
throw exception(COUPON_NOT_EXISTS);
|
||||
}
|
||||
return coupon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getUnusedCouponCount(Long userId) {
|
||||
return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType) {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<Long, List<Long>> takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType) {
|
||||
CouponTemplateDO template = couponTemplateService.getCouponTemplate(templateId);
|
||||
// 1. 过滤掉达到领取限制的用户
|
||||
removeTakeLimitUser(userIds, template);
|
||||
@@ -173,10 +143,77 @@ public class CouponServiceImpl implements CouponService {
|
||||
validateCouponTemplateCanTake(template, userIds, takeType);
|
||||
|
||||
// 3. 批量保存优惠劵
|
||||
couponMapper.insertBatch(convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId)));
|
||||
List<CouponDO> couponList = convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId));
|
||||
couponMapper.insertBatch(couponList);
|
||||
|
||||
// 3. 增加优惠劵模板的领取数量
|
||||
// 4. 增加优惠劵模板的领取数量
|
||||
couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size());
|
||||
|
||||
return convertMultiMap(couponList, CouponDO::getUserId, CouponDO::getId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId) {
|
||||
if (CollUtil.isEmpty(giveCoupons)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Long> couponIds = new ArrayList<>();
|
||||
// 循环发放
|
||||
for (Map.Entry<Long, Integer> entry : giveCoupons.entrySet()) {
|
||||
try {
|
||||
for (int i = 0; i < entry.getValue(); i++) {
|
||||
Map<Long, List<Long>> userCouponIdsMap = getSelf().takeCoupon(entry.getKey(), CollUtil.newHashSet(userId),
|
||||
CouponTakeTypeEnum.ADMIN);
|
||||
findAndThen(userCouponIdsMap, userId, couponIds::addAll);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[takeCouponsByAdmin][coupon({}) 优惠券发放失败]", entry, e);
|
||||
}
|
||||
}
|
||||
return couponIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCouponsByAdmin(List<Long> giveCouponIds, Long userId) {
|
||||
// 循环收回
|
||||
for (Long couponId : giveCouponIds) {
|
||||
try {
|
||||
getSelf().invalidateCoupon(couponId, userId);
|
||||
} catch (Exception e) {
|
||||
log.error("[invalidateCouponsByAdmin][couponId({}) 收回优惠券失败]", couponId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【管理员】收回优惠券
|
||||
*
|
||||
* @param couponId 模版编号
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void invalidateCoupon(Long couponId, Long userId) {
|
||||
// 1.1 校验优惠券
|
||||
CouponDO coupon = couponMapper.selectByIdAndUserId(couponId, userId);
|
||||
if (coupon == null) {
|
||||
throw exception(COUPON_NOT_EXISTS);
|
||||
}
|
||||
// 1.2 校验模板
|
||||
CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(coupon.getTemplateId());
|
||||
if (couponTemplate == null) {
|
||||
throw exception(COUPON_TEMPLATE_NOT_EXISTS);
|
||||
}
|
||||
// 1.3 校验优惠券是否已经使用,如若使用则先不管
|
||||
if (ObjUtil.equal(coupon.getStatus(), CouponStatusEnum.USED.getStatus())) {
|
||||
log.info("[invalidateCoupon][coupon({}) 已经使用,无法作废]", couponId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2.1 减少优惠劵模板的领取数量
|
||||
couponTemplateService.updateCouponTemplateTakeCount(couponTemplate.getId(), -1);
|
||||
// 2.2 作废优惠劵
|
||||
couponMapper.deleteById(couponId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -188,24 +225,6 @@ public class CouponServiceImpl implements CouponService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
|
||||
if (CollUtil.isEmpty(templateIds)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
|
||||
List<CouponDO> list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId,
|
||||
CouponStatusEnum.UNUSED.getStatus(),
|
||||
matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());
|
||||
// 兜底逻辑:如果 CouponExpireJob 未执行,status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤
|
||||
list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime()));
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expireCoupon() {
|
||||
// 1. 查询待过期的优惠券
|
||||
@@ -230,27 +249,6 @@ public class CouponServiceImpl implements CouponService {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
|
||||
// 1. 未登录时,都显示可以领取
|
||||
Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
|
||||
if (userId == null) {
|
||||
return userCanTakeMap;
|
||||
}
|
||||
|
||||
// 2.1 过滤领取数量无限制的
|
||||
Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
|
||||
// 2.2 检查用户领取的数量是否超过限制
|
||||
if (CollUtil.isNotEmpty(templateIds)) {
|
||||
Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
|
||||
for (CouponTemplateDO template : templates) {
|
||||
Integer takeCount = couponTakeCountMap.get(template.getId());
|
||||
userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount());
|
||||
}
|
||||
}
|
||||
return userCanTakeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过期单个优惠劵
|
||||
*
|
||||
@@ -322,11 +320,84 @@ public class CouponServiceImpl implements CouponService {
|
||||
userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount());
|
||||
}
|
||||
|
||||
//======================= 查询相关 =======================
|
||||
|
||||
@Override
|
||||
public Long getUnusedCouponCount(Long userId) {
|
||||
return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
|
||||
// 获得用户编号
|
||||
if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
|
||||
List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
|
||||
if (CollUtil.isEmpty(users)) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId));
|
||||
}
|
||||
// 分页查询
|
||||
return couponMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CouponDO> getCouponList(Long userId, Integer status) {
|
||||
return couponMapper.selectListByUserIdAndStatus(userId, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
|
||||
if (CollUtil.isEmpty(templateIds)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
|
||||
List<CouponDO> list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId,
|
||||
CouponStatusEnum.UNUSED.getStatus(),
|
||||
matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());
|
||||
// 兜底逻辑:如果 CouponExpireJob 未执行,status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤
|
||||
list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime()));
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
|
||||
// 1. 未登录时,都显示可以领取
|
||||
Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
|
||||
if (userId == null) {
|
||||
return userCanTakeMap;
|
||||
}
|
||||
|
||||
// 2.1 过滤领取数量无限制的
|
||||
Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
|
||||
// 2.2 检查用户领取的数量是否超过限制
|
||||
if (CollUtil.isNotEmpty(templateIds)) {
|
||||
Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
|
||||
for (CouponTemplateDO template : templates) {
|
||||
Integer takeCount = couponTakeCountMap.get(template.getId());
|
||||
userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount());
|
||||
}
|
||||
}
|
||||
return userCanTakeMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouponDO getCoupon(Long userId, Long id) {
|
||||
return couponMapper.selectByIdAndUserId(id, userId);
|
||||
}
|
||||
|
||||
private CouponDO validateCouponExists(Long id) {
|
||||
CouponDO coupon = couponMapper.selectById(id);
|
||||
if (coupon == null) {
|
||||
throw exception(COUPON_NOT_EXISTS);
|
||||
}
|
||||
return coupon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得自身的代理对象,解决 AOP 生效问题
|
||||
*
|
||||
@@ -335,4 +406,5 @@ public class CouponServiceImpl implements CouponService {
|
||||
private CouponServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -104,7 +104,10 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
|
||||
}
|
||||
// 计算新增的记录
|
||||
List<DiscountProductDO> newDiscountProducts = convertList(updateReqVO.getProducts(),
|
||||
product -> DiscountActivityConvert.INSTANCE.convert(product).setActivityId(updateReqVO.getId()));
|
||||
product -> DiscountActivityConvert.INSTANCE.convert(product)
|
||||
.setActivityId(updateReqVO.getId())
|
||||
.setActivityStartTime(updateReqVO.getStartTime())
|
||||
.setActivityEndTime(updateReqVO.getEndTime()));
|
||||
newDiscountProducts.removeIf(product -> dbDiscountProducts.stream().anyMatch(
|
||||
dbProduct -> DiscountActivityConvert.INSTANCE.isEquals(dbProduct, product))); // 如果匹配到,说明是更新的
|
||||
if (CollectionUtil.isNotEmpty(newDiscountProducts)) {
|
||||
|
||||
@@ -75,11 +75,10 @@ public interface RewardActivityService {
|
||||
/**
|
||||
* 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
|
||||
*
|
||||
* @param spuIds spu 编号
|
||||
* @param status 状态
|
||||
* @param dateTime 当前日期时间
|
||||
* @return 满减送活动列表
|
||||
*/
|
||||
List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
|
||||
List<RewardActivityDO> getRewardActivityListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.reward;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi;
|
||||
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityBaseVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -17,13 +20,13 @@ import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.hutool.core.collection.CollUtil.intersectionDistinct;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
/**
|
||||
* 满减送活动 Service 实现类
|
||||
@@ -37,13 +40,20 @@ public class RewardActivityServiceImpl implements RewardActivityService {
|
||||
@Resource
|
||||
private RewardActivityMapper rewardActivityMapper;
|
||||
|
||||
@Resource
|
||||
private ProductCategoryApi productCategoryApi;
|
||||
@Resource
|
||||
private ProductSpuApi productSpuApi;
|
||||
|
||||
@Override
|
||||
public Long createRewardActivity(RewardActivityCreateReqVO createReqVO) {
|
||||
// 校验商品是否冲突
|
||||
validateRewardActivitySpuConflicts(null, createReqVO.getProductSpuIds());
|
||||
// 1.1 校验商品范围
|
||||
validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues());
|
||||
// 1.2 校验商品是否冲突
|
||||
validateRewardActivitySpuConflicts(null, createReqVO);
|
||||
|
||||
// 插入
|
||||
RewardActivityDO rewardActivity = RewardActivityConvert.INSTANCE.convert(createReqVO)
|
||||
// 2. 插入
|
||||
RewardActivityDO rewardActivity = BeanUtils.toBean(createReqVO, RewardActivityDO.class)
|
||||
.setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getEndTime()));
|
||||
rewardActivityMapper.insert(rewardActivity);
|
||||
// 返回
|
||||
@@ -52,16 +62,18 @@ public class RewardActivityServiceImpl implements RewardActivityService {
|
||||
|
||||
@Override
|
||||
public void updateRewardActivity(RewardActivityUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
// 1.1 校验存在
|
||||
RewardActivityDO dbRewardActivity = validateRewardActivityExists(updateReqVO.getId());
|
||||
if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能修改噢
|
||||
if (dbRewardActivity.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { // 已关闭的活动,不能修改噢
|
||||
throw exception(REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
|
||||
}
|
||||
// 校验商品是否冲突
|
||||
validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO.getProductSpuIds());
|
||||
// 1.2 校验商品范围
|
||||
validateProductScope(updateReqVO.getProductScope(), updateReqVO.getProductScopeValues());
|
||||
// 1.3 校验商品是否冲突
|
||||
validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO);
|
||||
|
||||
// 更新
|
||||
RewardActivityDO updateObj = RewardActivityConvert.INSTANCE.convert(updateReqVO)
|
||||
// 2. 更新
|
||||
RewardActivityDO updateObj = BeanUtils.toBean(updateReqVO, RewardActivityDO.class)
|
||||
.setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getEndTime()));
|
||||
rewardActivityMapper.updateById(updateObj);
|
||||
}
|
||||
@@ -70,15 +82,12 @@ public class RewardActivityServiceImpl implements RewardActivityService {
|
||||
public void closeRewardActivity(Long id) {
|
||||
// 校验存在
|
||||
RewardActivityDO dbRewardActivity = validateRewardActivityExists(id);
|
||||
if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能关闭噢
|
||||
if (dbRewardActivity.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { // 已关闭的活动,不能关闭噢
|
||||
throw exception(REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED);
|
||||
}
|
||||
if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.END.getStatus())) { // 已关闭的活动,不能关闭噢
|
||||
throw exception(REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END);
|
||||
}
|
||||
|
||||
// 更新
|
||||
RewardActivityDO updateObj = new RewardActivityDO().setId(id).setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
|
||||
RewardActivityDO updateObj = new RewardActivityDO().setId(id).setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
rewardActivityMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@@ -86,7 +95,7 @@ public class RewardActivityServiceImpl implements RewardActivityService {
|
||||
public void deleteRewardActivity(Long id) {
|
||||
// 校验存在
|
||||
RewardActivityDO dbRewardActivity = validateRewardActivityExists(id);
|
||||
if (!dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 未关闭的活动,不能删除噢
|
||||
if (dbRewardActivity.getStatus().equals(CommonStatusEnum.ENABLE.getStatus())) { // 未关闭的活动,不能删除噢
|
||||
throw exception(REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED);
|
||||
}
|
||||
|
||||
@@ -102,41 +111,39 @@ public class RewardActivityServiceImpl implements RewardActivityService {
|
||||
return activity;
|
||||
}
|
||||
|
||||
// TODO @芋艿:逻辑有问题,需要优化;要分成全场、和指定来校验;
|
||||
|
||||
/**
|
||||
* 校验商品参加的活动是否冲突
|
||||
*
|
||||
* @param id 活动编号
|
||||
* @param spuIds 商品 SPU 编号数组
|
||||
* @param id 活动编号
|
||||
* @param rewardActivity 请求
|
||||
*/
|
||||
private void validateRewardActivitySpuConflicts(Long id, Collection<Long> spuIds) {
|
||||
if (CollUtil.isEmpty(spuIds)) {
|
||||
return;
|
||||
}
|
||||
// 查询商品参加的活动
|
||||
List<RewardActivityDO> rewardActivityList = getRewardActivityListBySpuIds(spuIds,
|
||||
asList(PromotionActivityStatusEnum.WAIT.getStatus(), PromotionActivityStatusEnum.RUN.getStatus()));
|
||||
private void validateRewardActivitySpuConflicts(Long id, RewardActivityBaseVO rewardActivity) {
|
||||
List<RewardActivityDO> list = rewardActivityMapper.selectList(RewardActivityDO::getProductScope,
|
||||
rewardActivity.getProductScope(), RewardActivityDO::getStatus, CommonStatusEnum.ENABLE.getStatus());
|
||||
if (id != null) { // 排除自己这个活动
|
||||
rewardActivityList.removeIf(activity -> id.equals(activity.getId()));
|
||||
list.removeIf(activity -> id.equals(activity.getId()));
|
||||
}
|
||||
// 如果非空,则说明冲突
|
||||
if (CollUtil.isNotEmpty(rewardActivityList)) {
|
||||
throw exception(REWARD_ACTIVITY_SPU_CONFLICTS);
|
||||
|
||||
// 情况一:全部商品参加
|
||||
if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope()) && !list.isEmpty()) {
|
||||
throw exception(REWARD_ACTIVITY_SCOPE_ALL_EXISTS);
|
||||
}
|
||||
if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope()) || // 情况二:指定商品参加
|
||||
PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) { // 情况三:指定商品类型参加
|
||||
if (anyMatch(list, item -> !intersectionDistinct(item.getProductScopeValues(),
|
||||
rewardActivity.getProductScopeValues()).isEmpty())) {
|
||||
throw exception(PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope()) ?
|
||||
REWARD_ACTIVITY_SPU_CONFLICTS : REWARD_ACTIVITY_SCOPE_CATEGORY_EXISTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得商品参加的满减送活动的数组
|
||||
*
|
||||
* @param spuIds 商品 SPU 编号数组
|
||||
* @param statuses 活动状态数组
|
||||
* @return 商品参加的满减送活动的数组
|
||||
*/
|
||||
private List<RewardActivityDO> getRewardActivityListBySpuIds(Collection<Long> spuIds,
|
||||
Collection<Integer> statuses) {
|
||||
List<RewardActivityDO> list = rewardActivityMapper.selectListByStatus(statuses);
|
||||
return CollUtil.filter(list, activity -> CollUtil.containsAny(activity.getProductSpuIds(), spuIds));
|
||||
private void validateProductScope(Integer productScope, List<Long> productScopeValues) {
|
||||
if (Objects.equals(PromotionProductScopeEnum.SPU.getScope(), productScope)) {
|
||||
productSpuApi.validateSpuList(productScopeValues);
|
||||
} else if (Objects.equals(PromotionProductScopeEnum.CATEGORY.getScope(), productScope)) {
|
||||
productCategoryApi.validateCategoryList(productScopeValues);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,32 +158,13 @@ public class RewardActivityServiceImpl implements RewardActivityService {
|
||||
|
||||
@Override
|
||||
public List<RewardActivityMatchRespDTO> getMatchRewardActivityList(Collection<Long> spuIds) {
|
||||
// TODO 芋艿:待实现;先指定,然后再全局的;
|
||||
// // 如果有全局活动,则直接选择它
|
||||
// List<RewardActivityDO> allActivities = rewardActivityMapper.selectListByProductScopeAndStatus(
|
||||
// PromotionProductScopeEnum.ALL.getScope(), PromotionActivityStatusEnum.RUN.getStatus());
|
||||
// if (CollUtil.isNotEmpty(allActivities)) {
|
||||
// return MapUtil.builder(allActivities.get(0), spuIds).build();
|
||||
// }
|
||||
//
|
||||
// // 查询某个活动参加的活动
|
||||
// List<RewardActivityDO> productActivityList = getRewardActivityListBySpuIds(spuIds,
|
||||
// singleton(PromotionActivityStatusEnum.RUN.getStatus()));
|
||||
// return convertMap(productActivityList, activity -> activity,
|
||||
// rewardActivityDO -> intersectionDistinct(rewardActivityDO.getProductSpuIds(), spuIds)); // 求交集返回
|
||||
return null;
|
||||
List<RewardActivityDO> list = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, CommonStatusEnum.ENABLE.getStatus());
|
||||
return BeanUtils.toBean(list, RewardActivityMatchRespDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
|
||||
// 1. 查询出指定 spuId 的 spu 参加的活动
|
||||
List<RewardActivityDO> rewardActivityList = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, status);
|
||||
if (CollUtil.isEmpty(rewardActivityList)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 2. 查询活动详情
|
||||
return rewardActivityMapper.selectListByIdsAndDateTimeLt(convertSet(rewardActivityList, RewardActivityDO::getId), dateTime);
|
||||
public List<RewardActivityDO> getRewardActivityListByStatusAndDateTimeLt(Integer status, LocalDateTime dateTime) {
|
||||
return rewardActivityMapper.selectListByStatusAndDateTimeLt(status, dateTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.reward;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.randomEle;
|
||||
@@ -27,15 +29,15 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.REWARD_ACTIVITY_NOT_EXISTS;
|
||||
import static java.util.Arrays.asList;
|
||||
import static com.google.common.primitives.Longs.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link RewardActivityServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
* {@link RewardActivityServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Disabled // TODO 芋艿:后续 fix 补充的单测
|
||||
@Import(RewardActivityServiceImpl.class)
|
||||
public class RewardActivityServiceImplTest extends BaseDbUnitTest {
|
||||
@@ -63,7 +65,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
|
||||
// 校验记录的属性是否正确
|
||||
RewardActivityDO rewardActivity = rewardActivityMapper.selectById(rewardActivityId);
|
||||
assertPojoEquals(reqVO, rewardActivity, "rules");
|
||||
assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus());
|
||||
assertEquals(rewardActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus());
|
||||
for (int i = 0; i < reqVO.getRules().size(); i++) {
|
||||
assertPojoEquals(reqVO.getRules().get(i), rewardActivity.getRules().get(i));
|
||||
}
|
||||
@@ -72,7 +74,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
|
||||
@Test
|
||||
public void testUpdateRewardActivity_success() {
|
||||
// mock 数据
|
||||
RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus()));
|
||||
RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
RewardActivityUpdateReqVO reqVO = randomPojo(RewardActivityUpdateReqVO.class, o -> {
|
||||
@@ -88,7 +90,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
|
||||
// 校验是否更新正确
|
||||
RewardActivityDO rewardActivity = rewardActivityMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, rewardActivity, "rules");
|
||||
assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus());
|
||||
assertEquals(rewardActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus());
|
||||
for (int i = 0; i < reqVO.getRules().size(); i++) {
|
||||
assertPojoEquals(reqVO.getRules().get(i), rewardActivity.getRules().get(i));
|
||||
}
|
||||
@@ -97,7 +99,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
|
||||
@Test
|
||||
public void testCloseRewardActivity() {
|
||||
// mock 数据
|
||||
RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus()));
|
||||
RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbRewardActivity.getId();
|
||||
@@ -106,7 +108,7 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
|
||||
rewardActivityService.closeRewardActivity(id);
|
||||
// 校验状态
|
||||
RewardActivityDO rewardActivity = rewardActivityMapper.selectById(id);
|
||||
assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.CLOSE.getStatus());
|
||||
assertEquals(rewardActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,15 +123,15 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
|
||||
@Test
|
||||
public void testDeleteRewardActivity_success() {
|
||||
// mock 数据
|
||||
RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus()));
|
||||
RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbRewardActivity.getId();
|
||||
|
||||
// 调用
|
||||
rewardActivityService.deleteRewardActivity(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(rewardActivityMapper.selectById(id));
|
||||
// 校验数据不存在了
|
||||
assertNull(rewardActivityMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,77 +145,82 @@ public class RewardActivityServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Test
|
||||
public void testGetRewardActivityPage() {
|
||||
// mock 数据
|
||||
RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> { // 等会查询到
|
||||
o.setName("芋艿");
|
||||
o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
|
||||
});
|
||||
rewardActivityMapper.insert(dbRewardActivity);
|
||||
// 测试 name 不匹配
|
||||
rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setName("土豆")));
|
||||
// 测试 status 不匹配
|
||||
rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())));
|
||||
// 准备参数
|
||||
RewardActivityPageReqVO reqVO = new RewardActivityPageReqVO();
|
||||
reqVO.setName("芋艿");
|
||||
reqVO.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
|
||||
// mock 数据
|
||||
RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> { // 等会查询到
|
||||
o.setName("芋艿");
|
||||
o.setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
});
|
||||
rewardActivityMapper.insert(dbRewardActivity);
|
||||
// 测试 name 不匹配
|
||||
rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setName("土豆")));
|
||||
// 测试 status 不匹配
|
||||
rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())));
|
||||
// 准备参数
|
||||
RewardActivityPageReqVO reqVO = new RewardActivityPageReqVO();
|
||||
reqVO.setName("芋艿");
|
||||
reqVO.setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
|
||||
// 调用
|
||||
PageResult<RewardActivityDO> pageResult = rewardActivityService.getRewardActivityPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbRewardActivity, pageResult.getList().get(0), "rules");
|
||||
// 调用
|
||||
PageResult<RewardActivityDO> pageResult = rewardActivityService.getRewardActivityPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbRewardActivity, pageResult.getList().get(0), "rules");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRewardActivities_all() {
|
||||
// mock 数据
|
||||
RewardActivityDO allActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
|
||||
RewardActivityDO allActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
|
||||
.setProductScope(PromotionProductScopeEnum.ALL.getScope()));
|
||||
rewardActivityMapper.insert(allActivity);
|
||||
RewardActivityDO productActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L)));
|
||||
RewardActivityDO productActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)));
|
||||
rewardActivityMapper.insert(productActivity);
|
||||
// 准备参数
|
||||
Set<Long> spuIds = asSet(1L, 2L);
|
||||
|
||||
// 调用 TODO getMatchRewardActivities 没有这个方法,但是找到了 getMatchRewardActivityList
|
||||
//Map<RewardActivityDO, Set<Long>> matchRewardActivities = rewardActivityService.getMatchRewardActivities(spuIds);
|
||||
List<RewardActivityMatchRespDTO> matchRewardActivityList = rewardActivityService.getMatchRewardActivityList(spuIds);
|
||||
// 断言
|
||||
//assertEquals(matchRewardActivities.size(), 1);
|
||||
//Map.Entry<RewardActivityDO, Set<Long>> next = matchRewardActivities.entrySet().iterator().next();
|
||||
//assertPojoEquals(next.getKey(), allActivity);
|
||||
//assertEquals(next.getValue(), spuIds);
|
||||
assertEquals(matchRewardActivityList.size(), 1);
|
||||
matchRewardActivityList.forEach((activity) -> {
|
||||
if (activity.getId().equals(productActivity.getId())) {
|
||||
assertPojoEquals(activity, productActivity);
|
||||
assertEquals(activity.getProductScopeValues(), asList(1L, 2L));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRewardActivities_product() {
|
||||
// mock 数据
|
||||
RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L)));
|
||||
RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)));
|
||||
rewardActivityMapper.insert(productActivity01);
|
||||
RewardActivityDO productActivity02 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(singletonList(3L)));
|
||||
RewardActivityDO productActivity02 = randomPojo(RewardActivityDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L)));
|
||||
rewardActivityMapper.insert(productActivity02);
|
||||
// 准备参数
|
||||
Set<Long> spuIds = asSet(1L, 2L, 3L);
|
||||
|
||||
// 调用 TODO getMatchRewardActivities 没有这个方法,但是找到了 getMatchRewardActivityList
|
||||
//Map<RewardActivityDO, Set<Long>> matchRewardActivities = rewardActivityService.getMatchRewardActivities(spuIds);
|
||||
List<RewardActivityMatchRespDTO> matchRewardActivityList = rewardActivityService.getMatchRewardActivityList(spuIds);
|
||||
// 断言
|
||||
//assertEquals(matchRewardActivities.size(), 2);
|
||||
//matchRewardActivities.forEach((activity, activitySpuIds) -> {
|
||||
// if (activity.getId().equals(productActivity01.getId())) {
|
||||
// assertPojoEquals(activity, productActivity01);
|
||||
// assertEquals(activitySpuIds, asSet(1L, 2L));
|
||||
// } else if (activity.getId().equals(productActivity02.getId())) {
|
||||
// assertPojoEquals(activity, productActivity02);
|
||||
// assertEquals(activitySpuIds, asSet(3L));
|
||||
// } else {
|
||||
// fail();
|
||||
// }
|
||||
//});
|
||||
assertEquals(matchRewardActivityList.size(), 2);
|
||||
matchRewardActivityList.forEach((activity) -> {
|
||||
if (activity.getId().equals(productActivity01.getId())) {
|
||||
assertPojoEquals(activity, productActivity01);
|
||||
assertEquals(activity.getProductScopeValues(), asList(1L, 2L));
|
||||
} else if (activity.getId().equals(productActivity02.getId())) {
|
||||
assertPojoEquals(activity, productActivity02);
|
||||
assertEquals(activity.getProductScopeValues(), singletonList(3L));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ public interface TradeOrderApi {
|
||||
*/
|
||||
TradeOrderRespDTO getOrder(Long id);
|
||||
|
||||
// TODO 芋艿:需要优化下;
|
||||
/**
|
||||
* 取消支付订单
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param userId 用户编号
|
||||
* @param orderId 订单编号
|
||||
* @param cancelType 取消类型
|
||||
*/
|
||||
void cancelPaidOrder(Long userId, Long orderId);
|
||||
void cancelPaidOrder(Long userId, Long orderId, Integer cancelType);
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】");
|
||||
ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态");
|
||||
ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单");
|
||||
ErrorCode ORDER_CANCEL_PAID_FAIL = new ErrorCode(1_011_000_033, "交易订单取消支付失败,原因:订单不是【{}】状态");
|
||||
|
||||
// ========== After Sale 模块 1-011-000-100 ==========
|
||||
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
|
||||
@@ -59,6 +60,8 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板");
|
||||
ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵");
|
||||
ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_005, "参与秒杀的商品,超过了秒杀总限购数量");
|
||||
ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL = new ErrorCode(1_011_003_006, "计算快递运费异常,配送方式不匹配");
|
||||
ErrorCode PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH = new ErrorCode(1_011_003_007, "该优惠劵无法使用,原因:优惠金额超过订单金额");
|
||||
|
||||
// ========== 物流 Express 模块 1-011-004-000 ==========
|
||||
ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
|
||||
|
||||
@@ -17,7 +17,8 @@ public enum TradeOrderCancelTypeEnum implements IntArrayValuable {
|
||||
|
||||
PAY_TIMEOUT(10, "超时未支付"),
|
||||
AFTER_SALE_CLOSE(20, "退款关闭"),
|
||||
MEMBER_CANCEL(30, "买家取消");
|
||||
MEMBER_CANCEL(30, "买家取消"),
|
||||
COMBINATION_CLOSE(40, "拼团关闭");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TradeOrderCancelTypeEnum::getType).toArray();
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -36,8 +36,8 @@ public class TradeOrderApiImpl implements TradeOrderApi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelPaidOrder(Long userId, Long orderId) {
|
||||
tradeOrderUpdateService.cancelPaidOrder(userId, orderId);
|
||||
public void cancelPaidOrder(Long userId, Long orderId, Integer cancelType) {
|
||||
tradeOrderUpdateService.cancelPaidOrder(userId, orderId, cancelType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
|
||||
@@ -12,10 +13,14 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 交易订单 DO
|
||||
@@ -291,6 +296,24 @@ public class TradeOrderDO extends BaseDO {
|
||||
*/
|
||||
private Integer vipPrice;
|
||||
|
||||
/**
|
||||
* 赠送的优惠劵
|
||||
*
|
||||
* key: 优惠劵模版编号
|
||||
* value:对应的优惠券数量
|
||||
*
|
||||
* 目的:用于订单支付后赠送优惠券
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private Map<Long, Integer> giveCouponTemplateCounts;
|
||||
/**
|
||||
* 赠送的优惠劵编号
|
||||
*
|
||||
* 目的:用于后续取消或者售后订单时,需要扣减赠送
|
||||
*/
|
||||
@TableField(typeHandler = LongListTypeHandler.class)
|
||||
private List<Long> giveCouponIds;
|
||||
|
||||
/**
|
||||
* 秒杀活动编号
|
||||
*
|
||||
|
||||
@@ -268,11 +268,6 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 校验分佣模式:仅可后台手动设置推广员
|
||||
// if (BrokerageEnabledConditionEnum.ADMIN.getCondition().equals(tradeConfig.getBrokerageEnabledCondition())) {
|
||||
// throw exception(BROKERAGE_BIND_CONDITION_ADMIN);
|
||||
// }
|
||||
|
||||
// 校验分销关系绑定模式
|
||||
if (BrokerageBindModeEnum.REGISTER.getMode().equals(tradeConfig.getBrokerageBindMode())) {
|
||||
// 判断是否为新用户:注册时间在 30 秒内的,都算新用户
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package cn.iocoder.yudao.module.trade.service.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
|
||||
@@ -10,9 +9,10 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 交易订单【写】Service 接口
|
||||
*
|
||||
@@ -187,13 +187,22 @@ public interface TradeOrderUpdateService {
|
||||
*/
|
||||
void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId);
|
||||
|
||||
// TODO 芋艿:拼团取消,不调这个接口哈;
|
||||
/**
|
||||
* 取消支付订单
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param orderId 订单编号
|
||||
* @param userId 用户编号
|
||||
* @param orderId 订单编号
|
||||
* @param cancelType 取消类型
|
||||
*/
|
||||
void cancelPaidOrder(Long userId, Long orderId);
|
||||
void cancelPaidOrder(Long userId, Long orderId, Integer cancelType);
|
||||
|
||||
/**
|
||||
* 更新下单赠送的优惠券编号到订单
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param orderId 订单编号
|
||||
* @param giveCouponIds 赠送的优惠券编号列表
|
||||
*/
|
||||
void updateOrderGiveCouponIds(Long userId, Long orderId, List<Long> giveCouponIds);
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
|
||||
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
|
||||
@@ -111,6 +113,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
private ProductCommentApi productCommentApi;
|
||||
@Resource
|
||||
public SocialClientApi socialClientApi;
|
||||
@Resource
|
||||
public PayRefundApi payRefundApi;
|
||||
|
||||
@Resource
|
||||
private TradeOrderProperties tradeOrderProperties;
|
||||
@@ -197,6 +201,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
|
||||
order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
|
||||
order.setUserIp(getClientIP()).setTerminal(getTerminal());
|
||||
// 使用 + 赠送优惠券
|
||||
order.setGiveCouponTemplateCounts(calculateRespBO.getGiveCouponTemplateCounts());
|
||||
// 支付 + 退款信息
|
||||
order.setAdjustPrice(0).setPayStatus(false);
|
||||
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
|
||||
@@ -854,15 +860,46 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cancelPaidOrder(Long userId, Long orderId) {
|
||||
// TODO @puhui999:需要校验状态;已支付的情况下,才可以。
|
||||
public void cancelPaidOrder(Long userId, Long orderId, Integer cancelType) {
|
||||
// 1.1 这里校验下 cancelType 只允许拼团关闭;
|
||||
if (ObjUtil.notEqual(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType(), cancelType)) {
|
||||
return;
|
||||
}
|
||||
// 1.2 检验订单存在
|
||||
TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId);
|
||||
if (order == null) {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
|
||||
|
||||
// TODO @puhui999:需要退款
|
||||
// 1.3 校验订单是否支付
|
||||
if (!order.getPayStatus()) {
|
||||
throw exception(ORDER_CANCEL_PAID_FAIL, "已支付");
|
||||
}
|
||||
// 1.3 校验订单是否已退款
|
||||
if (ObjUtil.equal(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
|
||||
throw exception(ORDER_CANCEL_PAID_FAIL, "未退款");
|
||||
}
|
||||
|
||||
// 2.1 取消订单
|
||||
cancelOrder0(order, TradeOrderCancelTypeEnum.COMBINATION_CLOSE);
|
||||
// 2.2 创建退款单
|
||||
payRefundApi.createRefund(new PayRefundCreateReqDTO()
|
||||
.setAppKey(tradeOrderProperties.getPayAppKey()).setUserIp(getClientIP()) // 支付应用
|
||||
.setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
|
||||
.setMerchantRefundId(String.valueOf(order.getId()))
|
||||
.setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice()));// 价格信息
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOrderGiveCouponIds(Long userId, Long orderId, List<Long> giveCouponIds) {
|
||||
// 1. 检验订单存在
|
||||
TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId);
|
||||
if (order == null) {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 2. 更新订单赠送的优惠券编号列表
|
||||
tradeOrderMapper.updateById(new TradeOrderDO().setId(orderId).setGiveCouponIds(giveCouponIds));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package cn.iocoder.yudao.module.trade.service.order.handler;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -17,6 +21,12 @@ import java.util.List;
|
||||
@Component
|
||||
public class TradeCouponOrderHandler implements TradeOrderHandler {
|
||||
|
||||
@Resource
|
||||
@Lazy // 延迟加载,避免循环依赖
|
||||
private TradeOrderUpdateService orderUpdateService;
|
||||
@Resource
|
||||
private TradeOrderQueryService orderQueryService;
|
||||
|
||||
@Resource
|
||||
private CouponApi couponApi;
|
||||
|
||||
@@ -31,12 +41,30 @@ public class TradeCouponOrderHandler implements TradeOrderHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
|
||||
if (order.getCouponId() == null || order.getCouponId() <= 0) {
|
||||
public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
|
||||
if (CollUtil.isEmpty(order.getGiveCouponTemplateCounts())) {
|
||||
return;
|
||||
}
|
||||
// 退回优惠劵
|
||||
couponApi.returnUsedCoupon(order.getCouponId());
|
||||
// 赠送优惠券
|
||||
List<Long> couponIds = couponApi.takeCouponsByAdmin(order.getGiveCouponTemplateCounts(), order.getUserId());
|
||||
if (CollUtil.isEmpty(couponIds)) {
|
||||
return;
|
||||
}
|
||||
orderUpdateService.updateOrderGiveCouponIds(order.getUserId(), order.getId(), couponIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
|
||||
// 情况一:退还订单使用的优惠券
|
||||
if (order.getCouponId() != null && order.getCouponId() > 0) {
|
||||
// 退回优惠劵
|
||||
couponApi.returnUsedCoupon(order.getCouponId());
|
||||
}
|
||||
// 情况二:收回赠送的优惠券
|
||||
if (CollUtil.isEmpty(order.getGiveCouponIds())) {
|
||||
return;
|
||||
}
|
||||
couponApi.invalidateCouponsByAdmin(order.getGiveCouponIds(), order.getUserId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 价格计算 Response BO
|
||||
@@ -67,6 +68,21 @@ public class TradePriceCalculateRespBO {
|
||||
*/
|
||||
private Long bargainActivityId;
|
||||
|
||||
/**
|
||||
* 是否包邮
|
||||
*/
|
||||
private Boolean freeDelivery;
|
||||
|
||||
/**
|
||||
* 赠送的优惠劵
|
||||
*
|
||||
* key: 优惠劵模版编号
|
||||
* value:对应的优惠券数量
|
||||
*
|
||||
* 目的:用于订单支付后赠送优惠券
|
||||
*/
|
||||
private Map<Long, Integer> giveCouponTemplateCounts;
|
||||
|
||||
/**
|
||||
* 订单价格
|
||||
*/
|
||||
@@ -213,8 +229,19 @@ public class TradePriceCalculateRespBO {
|
||||
*/
|
||||
private Long categoryId;
|
||||
|
||||
// ========== 物流相关字段 =========
|
||||
|
||||
/**
|
||||
* 运费模板 Id
|
||||
* 配送方式数组
|
||||
*
|
||||
* 对应 DeliveryTypeEnum 枚举
|
||||
*/
|
||||
private List<Integer> deliveryTypes;
|
||||
|
||||
/**
|
||||
* 物流配置模板编号
|
||||
*
|
||||
* 对应 TradeDeliveryExpressTemplateDO 的 id 编号
|
||||
*/
|
||||
private Long deliveryTemplateId;
|
||||
|
||||
@@ -234,7 +261,7 @@ public class TradePriceCalculateRespBO {
|
||||
private List<ProductPropertyValueDetailRespDTO> properties;
|
||||
|
||||
/**
|
||||
* 使用的积分
|
||||
* 赠送的积分
|
||||
*/
|
||||
private Integer givePoint;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_MIN_PRICE;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_SPU;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH;
|
||||
|
||||
/**
|
||||
* 优惠劵的 {@link TradePriceCalculator} 实现类
|
||||
@@ -65,8 +66,9 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
|
||||
|
||||
// 3.1 计算可以优惠的金额
|
||||
Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
|
||||
Assert.isTrue(couponPrice < totalPayPrice,
|
||||
"优惠劵({}) 的优惠金额({}),不能大于订单总金额({})", coupon.getId(), couponPrice, totalPayPrice);
|
||||
if (couponPrice <= totalPayPrice) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH);
|
||||
}
|
||||
// 3.2 计算分摊的优惠金额
|
||||
List<Integer> divideCouponPrices = TradePriceCalculatorHelper.dividePrice(orderItems, couponPrice);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.member.api.address.MemberAddressApi;
|
||||
import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
|
||||
@@ -17,11 +18,11 @@ import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplate
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO.OrderItem;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -55,7 +56,11 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
if (param.getDeliveryType() == null) {
|
||||
return;
|
||||
}
|
||||
// TODO @puhui999:需要校验,是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈
|
||||
// 校验是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈
|
||||
if (CollectionUtils.anyMatch(result.getItems(), item -> !item.getDeliveryTypes().contains(param.getDeliveryType()))) {
|
||||
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL);
|
||||
}
|
||||
|
||||
if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
|
||||
calculateByPickUp(param);
|
||||
} else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
|
||||
@@ -90,7 +95,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况二:快递模版
|
||||
// 情况二:活动包邮
|
||||
if (Boolean.TRUE.equals(result.getFreeDelivery())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况三:快递模版
|
||||
// 2.1 过滤出已选中的商品 SKU
|
||||
List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
|
||||
Set<Long> deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId);
|
||||
@@ -124,7 +134,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
Map<Long, List<OrderItem>> template2ItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
|
||||
// 依次计算快递运费
|
||||
for (Map.Entry<Long, List<OrderItem>> entry : template2ItemMap.entrySet()) {
|
||||
Long templateId = entry.getKey();
|
||||
Long templateId = entry.getKey();
|
||||
List<OrderItem> orderItems = entry.getValue();
|
||||
DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId);
|
||||
if (templateBO == null) {
|
||||
@@ -144,8 +154,8 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
/**
|
||||
* 按配送方式来计算运费
|
||||
*
|
||||
* @param orderItems SKU 商品项目
|
||||
* @param chargeMode 配送计费方式
|
||||
* @param orderItems SKU 商品项目
|
||||
* @param chargeMode 配送计费方式
|
||||
* @param templateCharge 快递运费配置
|
||||
*/
|
||||
private void calculateExpressFeeByChargeMode(List<OrderItem> orderItems, Integer chargeMode,
|
||||
|
||||
@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -31,8 +32,7 @@ public class TradePriceCalculatorHelper {
|
||||
List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) {
|
||||
// 创建 PriceCalculateRespDTO 对象
|
||||
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO();
|
||||
result.setType(getOrderType(param));
|
||||
result.setPromotions(new ArrayList<>());
|
||||
result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCouponTemplateCounts(new LinkedHashMap<>());
|
||||
|
||||
// 创建它的 OrderItem 属性
|
||||
result.setItems(new ArrayList<>(param.getItems().size()));
|
||||
@@ -60,7 +60,7 @@ public class TradePriceCalculatorHelper {
|
||||
.setWeight(sku.getWeight()).setVolume(sku.getVolume());
|
||||
// spu 信息
|
||||
orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId())
|
||||
.setDeliveryTemplateId(spu.getDeliveryTemplateId())
|
||||
.setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId())
|
||||
.setGivePoint(spu.getGiveIntegral()).setUsePoint(0);
|
||||
if (StrUtil.isBlank(orderItem.getPicUrl())) {
|
||||
orderItem.setPicUrl(spu.getPicUrl());
|
||||
|
||||
@@ -3,23 +3,30 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
|
||||
import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice;
|
||||
|
||||
// TODO @puhui999:相关的单测,建议改一改
|
||||
|
||||
/**
|
||||
* 满减送活动的 {@link TradePriceCalculator} 实现类
|
||||
*
|
||||
@@ -52,7 +59,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
private void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result,
|
||||
RewardActivityMatchRespDTO rewardActivity) {
|
||||
// 1.1 获得满减送的订单项(商品)列表
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, rewardActivity);
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchActivityOrderItems(result, rewardActivity);
|
||||
if (CollUtil.isEmpty(orderItems)) {
|
||||
return;
|
||||
}
|
||||
@@ -61,7 +68,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
if (rule == null) {
|
||||
TradePriceCalculatorHelper.addNotMatchPromotion(result, orderItems,
|
||||
rewardActivity.getId(), rewardActivity.getName(), PromotionTypeEnum.REWARD_ACTIVITY.getType(),
|
||||
getRewardActivityNotMeetTip(rewardActivity));
|
||||
getRewardActivityNotMeetTip(rewardActivity, orderItems));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,6 +91,36 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
TradePriceCalculatorHelper.recountPayPrice(orderItem);
|
||||
}
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
|
||||
// 4.1 记录赠送的积分
|
||||
if (rule.getPoint() != null && rule.getPoint() > 0) {
|
||||
List<Integer> dividePoints = TradePriceCalculatorHelper.dividePrice(orderItems, rule.getPoint());
|
||||
for (int i = 0; i < orderItems.size(); i++) {
|
||||
// 商品可能赠送了积分,所以这里要加上
|
||||
TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i);
|
||||
orderItem.setGivePoint(orderItem.getGivePoint() + dividePoints.get(i));
|
||||
}
|
||||
}
|
||||
// 4.2 记录订单是否包邮
|
||||
if (Boolean.TRUE.equals(rule.getFreeDelivery())) {
|
||||
// 只要满足一个活动包邮那么这单就包邮
|
||||
result.setFreeDelivery(true);
|
||||
}
|
||||
// 4.3 记录赠送的优惠券
|
||||
if (CollUtil.isNotEmpty(rule.getGiveCouponTemplateCounts())) {
|
||||
for (Map.Entry<Long, Integer> entry : rule.getGiveCouponTemplateCounts().entrySet()) {
|
||||
Map<Long, Integer> giveCouponTemplateCounts = result.getGiveCouponTemplateCounts();
|
||||
// TODO @puhui999:是不是有一种可能性,这个 key 没有,别的 key 有哈。
|
||||
// TODO 这里还有一种简化的写法。就是下面,大概两行就可以啦
|
||||
// result.getGiveCouponTemplateCounts().put(entry.getKey(),
|
||||
// result.getGiveCouponTemplateCounts().getOrDefault(entry.getKey(), 0) + entry.getValue());
|
||||
if (giveCouponTemplateCounts.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券
|
||||
result.setGiveCouponTemplateCounts(rule.getGiveCouponTemplateCounts());
|
||||
} else { // 情况二:别的满减活动送过同类优惠券,则直接增加数量
|
||||
giveCouponTemplateCounts.put(entry.getKey(), giveCouponTemplateCounts.get(entry.getKey()) + entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,10 +130,23 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
* @param rewardActivity 满减送活动
|
||||
* @return 订单项(商品)列表
|
||||
*/
|
||||
private List<TradePriceCalculateRespBO.OrderItem> filterMatchCouponOrderItems(TradePriceCalculateRespBO result,
|
||||
RewardActivityMatchRespDTO rewardActivity) {
|
||||
return filterList(result.getItems(),
|
||||
orderItem -> CollUtil.contains(rewardActivity.getSpuIds(), orderItem.getSpuId()));
|
||||
private List<TradePriceCalculateRespBO.OrderItem> filterMatchActivityOrderItems(TradePriceCalculateRespBO result,
|
||||
RewardActivityMatchRespDTO rewardActivity) {
|
||||
// 情况一:全部商品都可以参与
|
||||
if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope())) {
|
||||
return result.getItems();
|
||||
}
|
||||
// 情况二:指定商品参与
|
||||
if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope())) {
|
||||
return filterList(result.getItems(),
|
||||
orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getSpuId()));
|
||||
}
|
||||
// 情况三:指定商品类型参与
|
||||
if (PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) {
|
||||
return filterList(result.getItems(),
|
||||
orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getCategoryId()));
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,14 +179,30 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得满减送活动部匹配时的提示
|
||||
* 获得满减送活动不匹配时的提示
|
||||
*
|
||||
* @param rewardActivity 满减送活动
|
||||
* @return 提示
|
||||
*/
|
||||
private String getRewardActivityNotMeetTip(RewardActivityMatchRespDTO rewardActivity) {
|
||||
// TODO 芋艿:后面再想想;应该找第一个规则,算下还差多少即可。
|
||||
return "TODO";
|
||||
private String getRewardActivityNotMeetTip(RewardActivityMatchRespDTO rewardActivity,
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems) {
|
||||
// 1. 计算数量和价格
|
||||
Integer count = TradePriceCalculatorHelper.calculateTotalCount(orderItems);
|
||||
Integer price = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
|
||||
assert count != null && price != null;
|
||||
|
||||
// 2. 构建不满足时的提示信息:按最低档规则算
|
||||
String meetTip = "满减送:购满 {} {},可以减 {} 元";
|
||||
List<RewardActivityMatchRespDTO.Rule> rules = new ArrayList<>(rewardActivity.getRules());
|
||||
rules.sort(Comparator.comparing(RewardActivityMatchRespDTO.Rule::getLimit)); // 按优惠门槛升序
|
||||
RewardActivityMatchRespDTO.Rule rule = rules.get(0);
|
||||
if (PromotionConditionTypeEnum.PRICE.getType().equals(rewardActivity.getConditionType())) {
|
||||
return StrUtil.format(meetTip, rule.getLimit(), "元", MoneyUtils.fenToYuanStr(rule.getDiscountPrice()));
|
||||
}
|
||||
if (PromotionConditionTypeEnum.COUNT.getType().equals(rewardActivity.getConditionType())) {
|
||||
return StrUtil.format(meetTip, rule.getLimit(), "件", MoneyUtils.fenToYuanStr(rule.getDiscountPrice()));
|
||||
}
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
@@ -13,6 +14,7 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
@@ -47,7 +49,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
|
||||
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
|
||||
.setType(TradeOrderTypeEnum.NORMAL.getType())
|
||||
.setPrice(new TradePriceCalculateRespBO.Price())
|
||||
.setPromotions(new ArrayList<>())
|
||||
.setPromotions(new ArrayList<>()).setGiveCouponTemplateCounts(new LinkedHashMap<>())
|
||||
.setItems(asList(
|
||||
new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
|
||||
.setPrice(100).setSpuId(1L),
|
||||
@@ -60,16 +62,22 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
|
||||
TradePriceCalculatorHelper.recountPayPrice(result.getItems());
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
|
||||
// mock 方法(限时折扣 DiscountActivity 信息)
|
||||
// mock 方法(满减送 RewardActivity 信息)
|
||||
when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L, 3L)))).thenReturn(asList(
|
||||
randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号")
|
||||
.setSpuIds(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType())
|
||||
.setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(200).setDiscountPrice(70)))),
|
||||
.setConditionType(PromotionConditionTypeEnum.PRICE.getType())
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
|
||||
.setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(20).setDiscountPrice(70)
|
||||
.setFreeDelivery(false)))),
|
||||
randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号")
|
||||
.setSpuIds(singletonList(3L)).setConditionType(PromotionConditionTypeEnum.COUNT.getType())
|
||||
.setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10),
|
||||
new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60), // 最大可满足,因为是 4 个
|
||||
new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100))))
|
||||
.setConditionType(PromotionConditionTypeEnum.COUNT.getType())
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L))
|
||||
.setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10)
|
||||
.setPoint(50).setFreeDelivery(false),
|
||||
new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60)
|
||||
.setPoint(100).setFreeDelivery(false), // 最大可满足,因为是 4 个
|
||||
new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100)
|
||||
.setFreeDelivery(false))))
|
||||
));
|
||||
|
||||
// 调用
|
||||
@@ -94,6 +102,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
|
||||
assertEquals(orderItem01.getCouponPrice(), 0);
|
||||
assertEquals(orderItem01.getPointPrice(), 0);
|
||||
assertEquals(orderItem01.getPayPrice(), 160);
|
||||
assertEquals(orderItem01.getGivePoint(), 0);
|
||||
// 断言:SKU 2
|
||||
TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
|
||||
assertEquals(orderItem02.getSkuId(), 20L);
|
||||
@@ -104,6 +113,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
|
||||
assertEquals(orderItem02.getCouponPrice(), 0);
|
||||
assertEquals(orderItem02.getPointPrice(), 0);
|
||||
assertEquals(orderItem02.getPayPrice(), 120);
|
||||
assertEquals(orderItem02.getGivePoint(), 0);
|
||||
// 断言:SKU 3
|
||||
TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
|
||||
assertEquals(orderItem03.getSkuId(), 30L);
|
||||
@@ -114,6 +124,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
|
||||
assertEquals(orderItem03.getCouponPrice(), 0);
|
||||
assertEquals(orderItem03.getPointPrice(), 0);
|
||||
assertEquals(orderItem03.getPayPrice(), 60);
|
||||
assertEquals(orderItem03.getGivePoint(), 100);
|
||||
// 断言:Promotion 部分(第一个)
|
||||
assertEquals(result.getPromotions().size(), 2);
|
||||
TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
|
||||
@@ -175,7 +186,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
|
||||
// mock 方法(限时折扣 DiscountActivity 信息)
|
||||
when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L)))).thenReturn(singletonList(
|
||||
randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号")
|
||||
.setSpuIds(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType())
|
||||
.setProductScopeValues(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType())
|
||||
.setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(351).setDiscountPrice(70))))
|
||||
));
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ CREATE TABLE IF NOT EXISTS "trade_order"
|
||||
"give_point" int NULL,
|
||||
"refund_point" int NULL,
|
||||
"vip_price" int NULL,
|
||||
"give_coupons_map" varchar NULL,
|
||||
"seckill_activity_id" long NULL,
|
||||
"bargain_activity_id" long NULL,
|
||||
"bargain_record_id" long NULL,
|
||||
|
||||
Reference in New Issue
Block a user