Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into develop-tmp
# Conflicts: # yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java # yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java # yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java # yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java # yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java
This commit is contained in:
@@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.promotion.api.combination;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
@@ -33,13 +33,13 @@ public interface CombinationRecordApi {
|
||||
CombinationRecordCreateRespDTO createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 查询拼团记录是否成功
|
||||
* 基于订单编号,查询拼团记录
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param orderId 订单编号
|
||||
* @return 拼团是否成功
|
||||
* @return 拼团记录
|
||||
*/
|
||||
boolean isCombinationRecordSuccess(Long userId, Long orderId);
|
||||
CombinationRecordRespDTO getCombinationRecordByOrderId(Long userId, Long orderId);
|
||||
|
||||
/**
|
||||
* 【下单前】校验是否满足拼团活动条件
|
||||
|
@@ -0,0 +1,110 @@
|
||||
package cn.iocoder.yudao.module.promotion.api.combination.dto;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 拼团记录 Response DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class CombinationRecordRespDTO {
|
||||
|
||||
/**
|
||||
* 编号,主键自增
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 拼团活动编号
|
||||
*
|
||||
* 关联 CombinationActivityDO 的 id 字段
|
||||
*/
|
||||
private Long activityId;
|
||||
/**
|
||||
* 拼团商品单价
|
||||
*
|
||||
* 冗余 CombinationProductDO 的 combinationPrice 字段
|
||||
*/
|
||||
private Integer combinationPrice;
|
||||
/**
|
||||
* SPU 编号
|
||||
*/
|
||||
private Long spuId;
|
||||
/**
|
||||
* 商品名字
|
||||
*/
|
||||
private String spuName;
|
||||
/**
|
||||
* 商品图片
|
||||
*/
|
||||
private String picUrl;
|
||||
/**
|
||||
* SKU 编号
|
||||
*/
|
||||
private Long skuId;
|
||||
/**
|
||||
* 购买的商品数量
|
||||
*/
|
||||
private Integer count;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nickname;
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 团长编号
|
||||
*/
|
||||
private Long headId;
|
||||
/**
|
||||
* 开团状态
|
||||
*
|
||||
* 关联 {@link CombinationRecordStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 订单编号
|
||||
*/
|
||||
private Long orderId;
|
||||
/**
|
||||
* 开团需要人数
|
||||
*
|
||||
* 关联 CombinationActivityDO 的 userSize 字段
|
||||
*/
|
||||
private Integer userSize;
|
||||
/**
|
||||
* 已加入拼团人数
|
||||
*/
|
||||
private Integer userCount;
|
||||
/**
|
||||
* 是否虚拟成团
|
||||
*/
|
||||
private Boolean virtualGroup;
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private LocalDateTime expireTime;
|
||||
/**
|
||||
* 开始时间 (订单付款后开始的时间)
|
||||
*/
|
||||
private LocalDateTime startTime;
|
||||
/**
|
||||
* 结束时间(成团时间/失败时间)
|
||||
*/
|
||||
private LocalDateTime endTime;
|
||||
|
||||
}
|
@@ -2,7 +2,6 @@ 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;
|
||||
@@ -15,6 +14,15 @@ import java.util.Map;
|
||||
*/
|
||||
public interface CouponApi {
|
||||
|
||||
/**
|
||||
* 获得用户的优惠劵列表
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param status 优惠劵状态
|
||||
* @return 优惠劵列表
|
||||
*/
|
||||
List<CouponRespDTO> getCouponListByUserId(Long userId, Integer status);
|
||||
|
||||
/**
|
||||
* 使用优惠劵
|
||||
*
|
||||
@@ -29,14 +37,6 @@ public interface CouponApi {
|
||||
*/
|
||||
void returnUsedCoupon(Long id);
|
||||
|
||||
/**
|
||||
* 校验优惠劵
|
||||
*
|
||||
* @param validReqDTO 校验请求
|
||||
* @return 优惠劵
|
||||
*/
|
||||
CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO);
|
||||
|
||||
/**
|
||||
* 【管理员】给指定用户批量发送优惠券
|
||||
*
|
||||
|
@@ -1,27 +0,0 @@
|
||||
package cn.iocoder.yudao.module.promotion.api.coupon.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 优惠劵使用 Request DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class CouponValidReqDTO {
|
||||
|
||||
/**
|
||||
* 优惠劵编号
|
||||
*/
|
||||
@NotNull(message = "优惠劵编号不能为空")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Long userId;
|
||||
|
||||
}
|
@@ -21,8 +21,6 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1_013_002_000, "Banner 不存在");
|
||||
|
||||
// ========== Coupon 相关 1-013-003-000 ============
|
||||
ErrorCode COUPON_NO_MATCH_SPU = new ErrorCode(1_013_003_000, "优惠劵没有可使用的商品!");
|
||||
ErrorCode COUPON_NO_MATCH_MIN_PRICE = new ErrorCode(1_013_003_001, "所结算的商品中未满足使用的金额");
|
||||
|
||||
// ========== 优惠劵模板 1-013-004-000 ==========
|
||||
ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_013_004_000, "优惠劵模板不存在");
|
||||
|
@@ -1,19 +1,17 @@
|
||||
package cn.iocoder.yudao.module.promotion.api.combination;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
|
||||
import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COMBINATION_RECORD_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 拼团活动 API 实现类
|
||||
*
|
||||
@@ -37,12 +35,9 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCombinationRecordSuccess(Long userId, Long orderId) {
|
||||
public CombinationRecordRespDTO getCombinationRecordByOrderId(Long userId, Long orderId) {
|
||||
CombinationRecordDO record = combinationRecordService.getCombinationRecord(userId, orderId);
|
||||
if (record == null) {
|
||||
throw exception(COMBINATION_RECORD_NOT_EXISTS);
|
||||
}
|
||||
return CombinationRecordStatusEnum.isSuccess(record.getStatus());
|
||||
return BeanUtils.toBean(record, CombinationRecordRespDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -1,11 +1,9 @@
|
||||
package cn.iocoder.yudao.module.promotion.api.coupon;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
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 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;
|
||||
@@ -26,6 +24,11 @@ public class CouponApiImpl implements CouponApi {
|
||||
@Resource
|
||||
private CouponService couponService;
|
||||
|
||||
@Override
|
||||
public List<CouponRespDTO> getCouponListByUserId(Long userId, Integer status) {
|
||||
return BeanUtils.toBean(couponService.getCouponList(userId, status), CouponRespDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useCoupon(CouponUseReqDTO useReqDTO) {
|
||||
couponService.useCoupon(useReqDTO.getId(), useReqDTO.getUserId(),
|
||||
@@ -37,12 +40,6 @@ public class CouponApiImpl implements CouponApi {
|
||||
couponService.returnUsedCoupon(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouponRespDTO validateCoupon(CouponValidReqDTO validReqDTO) {
|
||||
CouponDO coupon = couponService.validCoupon(validReqDTO.getId(), validReqDTO.getUserId());
|
||||
return CouponConvert.INSTANCE.convert(coupon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId) {
|
||||
return couponService.takeCouponsByAdmin(giveCoupons, userId);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.combination;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
@@ -16,18 +17,20 @@ import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordSe
|
||||
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 java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.hutool.core.collection.CollectionUtil.newArrayList;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
|
||||
@Tag(name = "管理后台 - 拼团活动")
|
||||
@@ -87,6 +90,23 @@ public class CombinationActivityController {
|
||||
return success(CombinationActivityConvert.INSTANCE.convert(activity, products));
|
||||
}
|
||||
|
||||
@GetMapping("/list-by-ids")
|
||||
@Operation(summary = "获得拼团活动列表,基于活动编号数组")
|
||||
@Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
|
||||
public CommonResult<List<CombinationActivityRespVO>> getCombinationActivityListByIds(@RequestParam("ids") List<Long> ids) {
|
||||
// 1. 获得开启的活动列表
|
||||
List<CombinationActivityDO> activityList = combinationActivityService.getCombinationActivityListByIds(ids);
|
||||
activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus()));
|
||||
if (CollUtil.isEmpty(activityList)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
// 2. 拼接返回
|
||||
List<CombinationProductDO> productList = combinationActivityService.getCombinationProductListByActivityIds(
|
||||
convertList(activityList, CombinationActivityDO::getId));
|
||||
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(convertList(activityList, CombinationActivityDO::getSpuId));
|
||||
return success(CombinationActivityConvert.INSTANCE.convertList(activityList, productList, spuList));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得拼团活动分页")
|
||||
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
|
||||
|
@@ -27,4 +27,14 @@ public class CombinationActivityRespVO extends CombinationActivityBaseVO {
|
||||
@Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<CombinationProductRespVO> products;
|
||||
|
||||
@Schema(description = "商品 SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "一个白菜")
|
||||
private String spuName; // 从 SPU 的 name 读取
|
||||
@Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
|
||||
private String picUrl; // 从 SPU 的 picUrl 读取
|
||||
@Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
|
||||
private Integer marketPrice; // 从 SPU 的 marketPrice 读取
|
||||
|
||||
@Schema(description = "拼团金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
private Integer combinationPrice; // 从 products 获取最小 price 读取
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
@@ -13,15 +14,17 @@ import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
|
||||
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 java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
|
||||
@Tag(name = "管理后台 - 秒杀活动")
|
||||
@@ -89,11 +92,28 @@ public class SeckillActivityController {
|
||||
}
|
||||
|
||||
// 拼接数据
|
||||
List<SeckillProductDO> products = seckillActivityService.getSeckillProductListByActivityId(
|
||||
List<SeckillProductDO> products = seckillActivityService.getSeckillProductListByActivityIds(
|
||||
convertSet(pageResult.getList(), SeckillActivityDO::getId));
|
||||
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(
|
||||
convertSet(pageResult.getList(), SeckillActivityDO::getSpuId));
|
||||
return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, products, spuList));
|
||||
}
|
||||
|
||||
@GetMapping("/list-by-ids")
|
||||
@Operation(summary = "获得秒杀活动列表,基于活动编号数组")
|
||||
@Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
|
||||
public CommonResult<List<SeckillActivityRespVO>> getCombinationActivityListByIds(@RequestParam("ids") List<Long> ids) {
|
||||
// 1. 获得开启的活动列表
|
||||
List<SeckillActivityDO> activityList = seckillActivityService.getSeckillActivityListByIds(ids);
|
||||
activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus()));
|
||||
if (CollUtil.isEmpty(activityList)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
// 2. 拼接返回
|
||||
List<SeckillProductDO> productList = seckillActivityService.getSeckillProductListByActivityIds(
|
||||
convertList(activityList, SeckillActivityDO::getId));
|
||||
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(convertList(activityList, SeckillActivityDO::getSpuId));
|
||||
return success(SeckillActivityConvert.INSTANCE.convertList(activityList, productList, spuList));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -54,4 +54,7 @@ public class SeckillActivityRespVO extends SeckillActivityBaseVO {
|
||||
example = "50")
|
||||
private Integer marketPrice;
|
||||
|
||||
@Schema(description = "拼团金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
private Integer seckillPrice; // 从 products 获取最小 price 读取
|
||||
|
||||
}
|
||||
|
@@ -14,24 +14,20 @@ import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivity
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
|
||||
import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
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 org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@Tag(name = "用户 APP - 拼团活动")
|
||||
@@ -40,45 +36,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
|
||||
@Validated
|
||||
public class AppCombinationActivityController {
|
||||
|
||||
/**
|
||||
* {@link AppCombinationActivityRespVO} 缓存,通过它异步刷新 {@link #getCombinationActivityList0(Integer)} 所要的首页数据
|
||||
*/
|
||||
private final LoadingCache<Integer, List<AppCombinationActivityRespVO>> combinationActivityListCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
|
||||
new CacheLoader<Integer, List<AppCombinationActivityRespVO>>() {
|
||||
|
||||
@Override
|
||||
public List<AppCombinationActivityRespVO> load(Integer count) {
|
||||
return getCombinationActivityList0(count);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@Resource
|
||||
private CombinationActivityService activityService;
|
||||
|
||||
@Resource
|
||||
private ProductSpuApi spuApi;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获得拼团活动列表", description = "用于小程序首页")
|
||||
@Parameter(name = "count", description = "需要展示的数量", example = "6")
|
||||
public CommonResult<List<AppCombinationActivityRespVO>> getCombinationActivityList(
|
||||
@RequestParam(name = "count", defaultValue = "6") Integer count) {
|
||||
return success(combinationActivityListCache.getUnchecked(count));
|
||||
}
|
||||
|
||||
private List<AppCombinationActivityRespVO> getCombinationActivityList0(Integer count) {
|
||||
List<CombinationActivityDO> activityList = activityService.getCombinationActivityListByCount(count);
|
||||
if (CollUtil.isEmpty(activityList)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 拼接返回
|
||||
List<CombinationProductDO> productList = activityService.getCombinationProductListByActivityIds(
|
||||
convertList(activityList, CombinationActivityDO::getId));
|
||||
List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, CombinationActivityDO::getSpuId));
|
||||
return CombinationActivityConvert.INSTANCE.convertAppList(activityList, productList, spuList);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得拼团活动分页")
|
||||
public CommonResult<PageResult<AppCombinationActivityRespVO>> getCombinationActivityPage(PageParam pageParam) {
|
||||
@@ -93,6 +56,23 @@ public class AppCombinationActivityController {
|
||||
return success(CombinationActivityConvert.INSTANCE.convertAppPage(pageResult, productList, spuList));
|
||||
}
|
||||
|
||||
@GetMapping("/list-by-ids")
|
||||
@Operation(summary = "获得拼团活动列表,基于活动编号数组")
|
||||
@Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
|
||||
public CommonResult<List<AppCombinationActivityRespVO>> getCombinationActivityListByIds(@RequestParam("ids") List<Long> ids) {
|
||||
// 1. 获得开启的活动列表
|
||||
List<CombinationActivityDO> activityList = activityService.getCombinationActivityListByIds(ids);
|
||||
activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus()));
|
||||
if (CollUtil.isEmpty(activityList)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
// 2. 拼接返回
|
||||
List<CombinationProductDO> productList = activityService.getCombinationProductListByActivityIds(
|
||||
convertList(activityList, CombinationActivityDO::getId));
|
||||
List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, CombinationActivityDO::getSpuId));
|
||||
return success(CombinationActivityConvert.INSTANCE.convertAppList(activityList, productList, spuList));
|
||||
}
|
||||
|
||||
@GetMapping("/get-detail")
|
||||
@Operation(summary = "获得拼团活动明细")
|
||||
@Parameter(name = "id", description = "活动编号", required = true, example = "1024")
|
||||
|
@@ -19,15 +19,14 @@ public class AppCombinationActivityRespVO {
|
||||
@Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
private Long spuId;
|
||||
|
||||
@Schema(description = "商品 SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "一个白菜")
|
||||
private String spuName; // 从 SPU 的 name 读取
|
||||
@Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
|
||||
// 从 SPU 的 picUrl 读取
|
||||
private String picUrl;
|
||||
|
||||
private String picUrl; // 从 SPU 的 picUrl 读取
|
||||
@Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
|
||||
// 从 SPU 的 marketPrice 读取
|
||||
private Integer marketPrice;
|
||||
private Integer marketPrice; // 从 SPU 的 marketPrice 读取
|
||||
|
||||
@Schema(description = "拼团金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
private Integer combinationPrice;
|
||||
private Integer combinationPrice; // 从 products 获取最小 price 读取
|
||||
|
||||
}
|
||||
|
@@ -5,7 +5,9 @@ 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.framework.security.core.annotations.PreAuthenticated;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.*;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponTakeReqVO;
|
||||
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.dal.dataobject.coupon.CouponTemplateDO;
|
||||
@@ -15,13 +17,12 @@ import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
|
||||
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.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
@@ -56,14 +57,6 @@ public class AppCouponController {
|
||||
return success(canTakeAgain);
|
||||
}
|
||||
|
||||
@GetMapping("/match-list")
|
||||
@Operation(summary = "获得匹配指定商品的优惠劵列表", description = "用于下单页,展示优惠劵列表")
|
||||
public CommonResult<List<AppCouponMatchRespVO>> getMatchCouponList(AppCouponMatchReqVO matchReqVO) {
|
||||
// todo: 优化:优惠金额倒序
|
||||
List<AppCouponMatchRespVO> list = couponService.getMatchCouponList(getLoginUserId(), matchReqVO);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "我的优惠劵列表")
|
||||
@PreAuthenticated
|
||||
|
@@ -1,30 +0,0 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "用户 App - 优惠劵的匹配 Request VO")
|
||||
@Data
|
||||
public class AppCouponMatchReqVO {
|
||||
|
||||
@Schema(description = "商品金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "商品金额不能为空")
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "商品 SPU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
|
||||
@NotEmpty(message = "商品 SPU 编号不能为空")
|
||||
private List<Long> spuIds;
|
||||
|
||||
@Schema(description = "商品 SKU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
|
||||
@NotEmpty(message = "商品 SKU 编号不能为空")
|
||||
private List<Long> skuIds;
|
||||
|
||||
@Schema(description = "分类编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[10, 20]")
|
||||
@NotEmpty(message = "分类编号不能为空")
|
||||
private List<Long> categoryIds;
|
||||
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "用户 App - 优惠劵 Response VO")
|
||||
@Data
|
||||
public class AppCouponMatchRespVO extends AppCouponRespVO {
|
||||
|
||||
@Schema(description = "是否匹配", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean match;
|
||||
|
||||
@Schema(description = "匹配条件的提示", example = "所结算商品没有符合条件的商品")
|
||||
private String description;
|
||||
|
||||
}
|
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@@ -42,7 +41,6 @@ public class AppCouponRespVO {
|
||||
private Integer discountPercent;
|
||||
|
||||
@Schema(description = "优惠金额", example = "10")
|
||||
@Min(value = 0, message = "优惠金额需要大于等于 0")
|
||||
private Integer discountPrice;
|
||||
|
||||
@Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
|
||||
|
@@ -23,6 +23,7 @@ import com.google.common.cache.LoadingCache;
|
||||
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 org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -30,11 +31,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
@@ -86,7 +87,7 @@ public class AppSeckillActivityController {
|
||||
|
||||
// 2.1 查询满足当前阶段的活动
|
||||
List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByConfigIdAndStatus(config.getId(), CommonStatusEnum.ENABLE.getStatus());
|
||||
List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(
|
||||
List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityIds(
|
||||
convertList(activityList, SeckillActivityDO::getId));
|
||||
// 2.2 获取 spu 信息
|
||||
List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, SeckillActivityDO::getSpuId));
|
||||
@@ -101,7 +102,7 @@ public class AppSeckillActivityController {
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(PageResult.empty(pageResult.getTotal()));
|
||||
}
|
||||
List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(
|
||||
List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityIds(
|
||||
convertList(pageResult.getList(), SeckillActivityDO::getId));
|
||||
|
||||
// 2. 拼接数据
|
||||
@@ -149,4 +150,21 @@ public class AppSeckillActivityController {
|
||||
return success(SeckillActivityConvert.INSTANCE.convert3(activity, productList, startTime, endTime));
|
||||
}
|
||||
|
||||
@GetMapping("/list-by-ids")
|
||||
@Operation(summary = "获得拼团活动列表,基于活动编号数组")
|
||||
@Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
|
||||
public CommonResult<List<AppSeckillActivityRespVO>> getCombinationActivityListByIds(@RequestParam("ids") List<Long> ids) {
|
||||
// 1. 获得开启的活动列表
|
||||
List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByIds(ids);
|
||||
activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus()));
|
||||
if (CollUtil.isEmpty(activityList)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
// 2. 拼接返回
|
||||
List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityIds(
|
||||
convertList(activityList, SeckillActivityDO::getId));
|
||||
List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, SeckillActivityDO::getSpuId));
|
||||
return success(SeckillActivityConvert.INSTANCE.convertAppList(activityList, productList, spuList));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -16,6 +16,9 @@ public class AppSeckillActivityRespVO {
|
||||
@Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
private Long spuId;
|
||||
|
||||
@Schema(description = "商品 SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "一个白菜")
|
||||
private String spuName; // 从 SPU 的 name 读取
|
||||
|
||||
@Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 picUrl 读取
|
||||
example = "https://www.iocoder.cn/xx.png")
|
||||
private String picUrl;
|
||||
|
@@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
@@ -127,40 +128,42 @@ public interface CombinationActivityConvert {
|
||||
.setSpuName(spu.getName()).setPicUrl(sku.getPicUrl());
|
||||
}
|
||||
|
||||
List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list);
|
||||
|
||||
default List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list,
|
||||
List<CombinationProductDO> productList,
|
||||
List<ProductSpuRespDTO> spuList) {
|
||||
List<AppCombinationActivityRespVO> activityList = convertAppList(list);
|
||||
default List<CombinationActivityRespVO> convertList(List<CombinationActivityDO> list,
|
||||
List<CombinationProductDO> productList,
|
||||
List<ProductSpuRespDTO> spuList) {
|
||||
List<CombinationActivityRespVO> activityList = BeanUtils.toBean(list, CombinationActivityRespVO.class);
|
||||
Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
|
||||
Map<Long, List<CombinationProductDO>> productMap = convertMultiMap(productList, CombinationProductDO::getActivityId);
|
||||
return CollectionUtils.convertList(activityList, item -> {
|
||||
// 设置 product 信息
|
||||
item.setCombinationPrice(getMinValue(productMap.get(item.getId()), CombinationProductDO::getCombinationPrice));
|
||||
// 设置 SPU 信息
|
||||
findAndThen(spuMap, item.getSpuId(), spu -> item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
|
||||
findAndThen(spuMap, item.getSpuId(), spu -> item.setSpuName(spu.getName())
|
||||
.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
PageResult<AppCombinationActivityRespVO> convertAppPage(PageResult<CombinationActivityDO> result);
|
||||
default List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list,
|
||||
List<CombinationProductDO> productList,
|
||||
List<ProductSpuRespDTO> spuList) {
|
||||
List<AppCombinationActivityRespVO> activityList = BeanUtils.toBean(list, AppCombinationActivityRespVO.class);
|
||||
Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
|
||||
Map<Long, List<CombinationProductDO>> productMap = convertMultiMap(productList, CombinationProductDO::getActivityId);
|
||||
return CollectionUtils.convertList(activityList, item -> {
|
||||
// 设置 product 信息
|
||||
item.setCombinationPrice(getMinValue(productMap.get(item.getId()), CombinationProductDO::getCombinationPrice));
|
||||
// 设置 SPU 信息
|
||||
findAndThen(spuMap, item.getSpuId(), spu -> item.setSpuName(spu.getName())
|
||||
.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
default PageResult<AppCombinationActivityRespVO> convertAppPage(PageResult<CombinationActivityDO> result,
|
||||
List<CombinationProductDO> productList,
|
||||
List<ProductSpuRespDTO> spuList) {
|
||||
PageResult<AppCombinationActivityRespVO> appPage = convertAppPage(result);
|
||||
Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
|
||||
Map<Long, List<CombinationProductDO>> productMap = convertMultiMap(productList, CombinationProductDO::getActivityId);
|
||||
List<AppCombinationActivityRespVO> list = CollectionUtils.convertList(appPage.getList(), item -> {
|
||||
// 设置 product 信息
|
||||
item.setCombinationPrice(getMinValue(productMap.get(item.getId()), CombinationProductDO::getCombinationPrice));
|
||||
// 设置 SPU 信息
|
||||
findAndThen(spuMap, item.getSpuId(), spu -> item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
|
||||
return item;
|
||||
});
|
||||
appPage.setList(list);
|
||||
return appPage;
|
||||
return new PageResult<>(convertAppList(result.getList(), productList, spuList), result.getTotal());
|
||||
}
|
||||
|
||||
AppCombinationActivityDetailRespVO convert2(CombinationActivityDO combinationActivity);
|
||||
|
@@ -4,9 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
|
||||
@@ -16,7 +14,6 @@ import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠劵 Convert
|
||||
|
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.convert.seckill;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
|
||||
@@ -87,6 +88,38 @@ public interface SeckillActivityConvert {
|
||||
return CollectionUtils.convertList(products, item -> convert(activity, item).setActivityStatus(activity.getStatus()));
|
||||
}
|
||||
|
||||
default List<SeckillActivityRespVO> convertList(List<SeckillActivityDO> list,
|
||||
List<SeckillProductDO> productList,
|
||||
List<ProductSpuRespDTO> spuList) {
|
||||
List<SeckillActivityRespVO> activityList = BeanUtils.toBean(list, SeckillActivityRespVO.class);
|
||||
Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
|
||||
Map<Long, List<SeckillProductDO>> productMap = convertMultiMap(productList, SeckillProductDO::getActivityId);
|
||||
return CollectionUtils.convertList(activityList, item -> {
|
||||
// 设置 product 信息
|
||||
item.setSeckillPrice(getMinValue(productMap.get(item.getId()), SeckillProductDO::getSeckillPrice));
|
||||
// 设置 SPU 信息
|
||||
findAndThen(spuMap, item.getSpuId(), spu -> item.setSpuName(spu.getName())
|
||||
.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
default List<AppSeckillActivityRespVO> convertAppList(List<SeckillActivityDO> list,
|
||||
List<SeckillProductDO> productList,
|
||||
List<ProductSpuRespDTO> spuList) {
|
||||
List<AppSeckillActivityRespVO> activityList = BeanUtils.toBean(list, AppSeckillActivityRespVO.class);
|
||||
Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
|
||||
Map<Long, List<SeckillProductDO>> productMap = convertMultiMap(productList, SeckillProductDO::getActivityId);
|
||||
return CollectionUtils.convertList(activityList, item -> {
|
||||
// 设置 product 信息
|
||||
item.setSeckillPrice(getMinValue(productMap.get(item.getId()), SeckillProductDO::getSeckillPrice));
|
||||
// 设置 SPU 信息
|
||||
findAndThen(spuMap, item.getSpuId(), spu -> item.setSpuName(spu.getName())
|
||||
.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
List<SeckillProductRespVO> convertList2(List<SeckillProductDO> list);
|
||||
|
||||
List<AppSeckillActivityRespVO> convertList3(List<SeckillActivityDO> activityList);
|
||||
|
@@ -1,13 +1,11 @@
|
||||
package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.github.yulichang.toolkit.MPJWrappers;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@@ -16,8 +14,6 @@ import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
|
||||
|
@@ -100,14 +100,6 @@ public interface CombinationActivityService {
|
||||
*/
|
||||
List<CombinationActivityDO> getCombinationActivityListByIds(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 获取正在进行的活动分页数据
|
||||
*
|
||||
* @param count 需要的数量
|
||||
* @return 拼团活动分页
|
||||
*/
|
||||
List<CombinationActivityDO> getCombinationActivityListByCount(Integer count);
|
||||
|
||||
/**
|
||||
* 获取正在进行的活动分页数据
|
||||
*
|
||||
|
@@ -225,11 +225,6 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
|
||||
return combinationActivityMapper.selectList(CombinationActivityDO::getId, ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CombinationActivityDO> getCombinationActivityListByCount(Integer count) {
|
||||
return combinationActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus(), count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<CombinationActivityDO> getCombinationActivityPage(PageParam pageParam) {
|
||||
return combinationActivityMapper.selectPage(pageParam, CommonStatusEnum.ENABLE.getStatus());
|
||||
|
@@ -4,8 +4,6 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
|
||||
@@ -19,26 +17,6 @@ import java.util.*;
|
||||
*/
|
||||
public interface CouponService {
|
||||
|
||||
/**
|
||||
* 校验优惠劵,包括状态、有限期
|
||||
* <p>
|
||||
* 1. 如果校验通过,则返回优惠劵信息
|
||||
* 2. 如果校验不通过,则直接抛出业务异常
|
||||
*
|
||||
* @param id 优惠劵编号
|
||||
* @param userId 用户编号
|
||||
* @return 优惠劵信息
|
||||
*/
|
||||
CouponDO validCoupon(Long id, Long userId);
|
||||
|
||||
/**
|
||||
* 校验优惠劵,包括状态、有限期
|
||||
*
|
||||
* @param coupon 优惠劵
|
||||
* @see #validCoupon(Long, Long) 逻辑相同,只是入参不同
|
||||
*/
|
||||
void validCoupon(CouponDO coupon);
|
||||
|
||||
/**
|
||||
* 使用优惠劵
|
||||
*
|
||||
@@ -172,15 +150,6 @@ public interface CouponService {
|
||||
return MapUtil.getInt(map, templateId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户匹配的优惠券列表
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param matchReqVO 匹配参数
|
||||
* @return 优惠券列表
|
||||
*/
|
||||
List<AppCouponMatchRespVO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);
|
||||
|
||||
/**
|
||||
* 获取用户是否可以领取优惠券
|
||||
*
|
||||
|
@@ -12,13 +12,10 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
|
||||
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.dal.dataobject.coupon.CouponTemplateDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
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;
|
||||
@@ -58,18 +55,9 @@ public class CouponServiceImpl implements CouponService {
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@Override
|
||||
public CouponDO validCoupon(Long id, Long userId) {
|
||||
CouponDO coupon = couponMapper.selectByIdAndUserId(id, userId);
|
||||
if (coupon == null) {
|
||||
throw exception(COUPON_NOT_EXISTS);
|
||||
}
|
||||
validCoupon(coupon);
|
||||
return coupon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validCoupon(CouponDO coupon) {
|
||||
public void useCoupon(Long id, Long userId, Long orderId) {
|
||||
// 校验状态
|
||||
CouponDO coupon = couponMapper.selectByIdAndUserId(id, userId);
|
||||
if (ObjectUtil.notEqual(coupon.getStatus(), CouponStatusEnum.UNUSED.getStatus())) {
|
||||
throw exception(COUPON_STATUS_NOT_UNUSED);
|
||||
}
|
||||
@@ -77,12 +65,6 @@ public class CouponServiceImpl implements CouponService {
|
||||
if (!LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())) {
|
||||
throw exception(COUPON_VALID_TIME_NOT_NOW);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useCoupon(Long id, Long userId, Long orderId) {
|
||||
// 校验优惠劵
|
||||
validCoupon(id, userId);
|
||||
|
||||
// 更新状态
|
||||
int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
|
||||
@@ -286,10 +268,8 @@ public class CouponServiceImpl implements CouponService {
|
||||
if (couponTemplate == null) {
|
||||
throw exception(COUPON_TEMPLATE_NOT_EXISTS);
|
||||
}
|
||||
// 校验剩余数量(仅在 CouponTakeTypeEnum.USER 用户领取时)
|
||||
if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeCount())
|
||||
&& couponTemplate.getTotalCount() != null
|
||||
&& couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
|
||||
// 校验剩余数量
|
||||
if (couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
|
||||
throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
|
||||
}
|
||||
// 校验"固定日期"的有效期类型是否过期
|
||||
@@ -299,7 +279,7 @@ public class CouponServiceImpl implements CouponService {
|
||||
}
|
||||
}
|
||||
// 校验领取方式
|
||||
if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
|
||||
if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) {
|
||||
throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
|
||||
}
|
||||
}
|
||||
@@ -311,7 +291,7 @@ public class CouponServiceImpl implements CouponService {
|
||||
* @param couponTemplate 优惠劵模版
|
||||
*/
|
||||
private void removeTakeLimitUser(Set<Long> userIds, CouponTemplateDO couponTemplate) {
|
||||
if (couponTemplate.getTakeLimitCount() == null || couponTemplate.getTakeLimitCount() <= 0) {
|
||||
if (couponTemplate.getTakeLimitCount() <= 0) {
|
||||
return;
|
||||
}
|
||||
// 查询已领过券的用户
|
||||
@@ -358,48 +338,6 @@ public class CouponServiceImpl implements CouponService {
|
||||
return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AppCouponMatchRespVO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
|
||||
List<AppCouponMatchRespVO> couponMatchist = new ArrayList<>();
|
||||
List<CouponDO> list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId,
|
||||
CouponStatusEnum.UNUSED.getStatus());
|
||||
for (CouponDO couponDO : list) {
|
||||
AppCouponMatchRespVO appCouponMatchRespVO = CouponConvert.INSTANCE.convert2(couponDO);
|
||||
Integer productScope = appCouponMatchRespVO.getProductScope();
|
||||
List<Long> productScopeValues = appCouponMatchRespVO.getProductScopeValues();
|
||||
Integer usePrice = appCouponMatchRespVO.getUsePrice();
|
||||
if(matchReqVO.getPrice() < usePrice){
|
||||
// 价格小于等于,满足价格使用条件
|
||||
appCouponMatchRespVO.setMatch(false);
|
||||
appCouponMatchRespVO.setDescription("未达到使用门槛");
|
||||
}else if(!LocalDateTimeUtils.isBetween(appCouponMatchRespVO.getValidStartTime(), appCouponMatchRespVO.getValidEndTime())) {
|
||||
//判断时间
|
||||
appCouponMatchRespVO.setMatch(false);
|
||||
appCouponMatchRespVO.setDescription("使用时间未到");
|
||||
}else if (PromotionProductScopeEnum.ALL.getScope().equals(productScope)){
|
||||
appCouponMatchRespVO.setMatch(true);
|
||||
}else if (PromotionProductScopeEnum.SPU.getScope().equals(productScope)){
|
||||
boolean spu = new HashSet<>(productScopeValues).containsAll(matchReqVO.getSpuIds());
|
||||
if(spu){
|
||||
appCouponMatchRespVO.setMatch(true);
|
||||
}else {
|
||||
appCouponMatchRespVO.setMatch(false);
|
||||
appCouponMatchRespVO.setDescription("与商品不匹配");
|
||||
}
|
||||
}else if (PromotionProductScopeEnum.CATEGORY.getScope().equals(productScope)){
|
||||
boolean category = new HashSet<>(productScopeValues).containsAll(matchReqVO.getCategoryIds());
|
||||
if(category){
|
||||
appCouponMatchRespVO.setMatch(true);
|
||||
}else {
|
||||
appCouponMatchRespVO.setMatch(false);
|
||||
appCouponMatchRespVO.setDescription("与商品类型不匹配");
|
||||
}
|
||||
}
|
||||
couponMatchist.add(appCouponMatchRespVO);
|
||||
}
|
||||
return couponMatchist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
|
||||
// 1. 未登录时,都显示可以领取
|
||||
|
@@ -8,8 +8,8 @@ import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.Se
|
||||
import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -98,7 +98,7 @@ public interface SeckillActivityService {
|
||||
* @param activityIds 活动编号
|
||||
* @return 活动商品列表
|
||||
*/
|
||||
List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> activityIds);
|
||||
List<SeckillProductDO> getSeckillProductListByActivityIds(Collection<Long> activityIds);
|
||||
|
||||
/**
|
||||
* 通过活动时段编号获取指定 status 的秒杀活动
|
||||
@@ -139,4 +139,12 @@ public interface SeckillActivityService {
|
||||
*/
|
||||
List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
|
||||
|
||||
/**
|
||||
* 获得拼团活动列表
|
||||
*
|
||||
* @param ids 拼团活动编号数组
|
||||
* @return 拼团活动的列表
|
||||
*/
|
||||
List<SeckillActivityDO> getSeckillActivityListByIds(Collection<Long> ids);
|
||||
|
||||
}
|
||||
|
@@ -51,6 +51,7 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1_011_000_110, "退款失败,售后单状态不是【待退款】");
|
||||
ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY =
|
||||
new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
|
||||
ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS = new ErrorCode(1_011_000_112, "订单拼团中,无法申请售后");
|
||||
|
||||
// ========== Cart 模块 1-011-002-000 ==========
|
||||
ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
|
||||
@@ -61,7 +62,7 @@ public interface ErrorCodeConstants {
|
||||
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, "该优惠劵无法使用,原因:优惠金额超过订单金额");
|
||||
ErrorCode PRICE_CALCULATE_COUPON_CAN_NOT_USE = new ErrorCode(1_011_003_007, "该优惠劵无法使用,原因:{}」");
|
||||
|
||||
// ========== 物流 Express 模块 1-011-004-000 ==========
|
||||
ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
|
||||
|
@@ -44,7 +44,7 @@ public class AppBrokerageWithdrawCreateReqVO {
|
||||
private String name;
|
||||
@Schema(description = "提现银行", example = "1")
|
||||
@NotNull(message = "提现银行不能为空", groups = {Bank.class})
|
||||
private Integer bankName;
|
||||
private String bankName;
|
||||
@Schema(description = "开户地址", example = "海淀支行")
|
||||
private String bankAddress;
|
||||
|
||||
|
@@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.trade.controller.app.order.vo;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "用户 App - 交易订单结算信息 Response VO")
|
||||
@@ -20,6 +20,9 @@ public class AppTradeOrderSettlementRespVO {
|
||||
@Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<Item> items;
|
||||
|
||||
@Schema(description = "优惠劵数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<Coupon> coupons; // 可用 + 不可用
|
||||
|
||||
@Schema(description = "费用", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Price price;
|
||||
|
||||
@@ -117,7 +120,6 @@ public class AppTradeOrderSettlementRespVO {
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "地区编号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "地区编号不能为空")
|
||||
private Long areaId;
|
||||
@Schema(description = "地区名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "上海上海市普陀区")
|
||||
private String areaName;
|
||||
@@ -130,4 +132,43 @@ public class AppTradeOrderSettlementRespVO {
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "优惠劵信息")
|
||||
@Data
|
||||
public static class Coupon {
|
||||
|
||||
@Schema(description = "优惠劵编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "优惠劵名", requiredMode = Schema.RequiredMode.REQUIRED, example = "春节送送送")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "是否设置满多少金额可用", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") // 单位:分;0 - 不限制
|
||||
private Integer usePrice;
|
||||
|
||||
@Schema(description = "固定日期 - 生效开始时间")
|
||||
private LocalDateTime validStartTime;
|
||||
|
||||
@Schema(description = "固定日期 - 生效结束时间")
|
||||
private LocalDateTime validEndTime;
|
||||
|
||||
@Schema(description = "优惠类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer discountType;
|
||||
|
||||
@Schema(description = "折扣百分比", example = "80") // 例如说,80% 为 80
|
||||
private Integer discountPercent;
|
||||
|
||||
@Schema(description = "优惠金额", example = "10")
|
||||
private Integer discountPrice;
|
||||
|
||||
@Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
|
||||
private Integer discountLimitPrice;
|
||||
|
||||
@Schema(description = "是否可用", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean match;
|
||||
|
||||
@Schema(description = "不可用原因", example = "优惠劵已过期")
|
||||
private String mismatchReason;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -8,6 +8,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
|
||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
|
||||
@@ -26,6 +29,7 @@ import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleWayEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.framework.aftersale.core.annotations.AfterSaleLog;
|
||||
import cn.iocoder.yudao.module.trade.framework.aftersale.core.utils.AfterSaleLogUtils;
|
||||
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
|
||||
@@ -71,6 +75,8 @@ public class AfterSaleServiceImpl implements AfterSaleService {
|
||||
|
||||
@Resource
|
||||
private PayRefundApi payRefundApi;
|
||||
@Resource
|
||||
private CombinationRecordApi combinationRecordApi;
|
||||
|
||||
@Resource
|
||||
private TradeOrderProperties tradeOrderProperties;
|
||||
@@ -148,6 +154,14 @@ public class AfterSaleServiceImpl implements AfterSaleService {
|
||||
&& !TradeOrderStatusEnum.haveDelivered(order.getStatus())) {
|
||||
throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED);
|
||||
}
|
||||
// 如果是拼团订单,则进行中不允许售后
|
||||
if (TradeOrderTypeEnum.isCombination(order.getType())) {
|
||||
CombinationRecordRespDTO combinationRecord = combinationRecordApi.getCombinationRecordByOrderId(
|
||||
order.getUserId(), order.getId());
|
||||
if (combinationRecord != null && CombinationRecordStatusEnum.isInProgress(combinationRecord.getStatus())) {
|
||||
throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
return orderItem;
|
||||
}
|
||||
|
||||
|
@@ -887,7 +887,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
.setAppKey(tradeOrderProperties.getPayAppKey()).setUserIp(getClientIP()) // 支付应用
|
||||
.setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
|
||||
.setMerchantRefundId(String.valueOf(order.getId()))
|
||||
.setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice()));// 价格信息
|
||||
.setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice())); // 价格信息
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.trade.service.order.handler;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
||||
@@ -84,7 +86,9 @@ public class TradeCombinationOrderHandler implements TradeOrderHandler {
|
||||
return;
|
||||
}
|
||||
// 校验订单拼团是否成功
|
||||
if (!combinationRecordApi.isCombinationRecordSuccess(order.getUserId(), order.getId())) {
|
||||
CombinationRecordRespDTO combinationRecord = combinationRecordApi.getCombinationRecordByOrderId(order.getUserId(), order.getId());
|
||||
Assert.notNull(combinationRecord, "订单({})对应的拼团记录不存在", order.getId());
|
||||
if (!CombinationRecordStatusEnum.isSuccess(combinationRecord.getStatus())) {
|
||||
throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -45,9 +46,13 @@ public class TradePriceCalculateRespBO {
|
||||
private List<Promotion> promotions;
|
||||
|
||||
/**
|
||||
* 优惠劵编号
|
||||
* 使用的优惠劵编号
|
||||
*/
|
||||
private Long couponId;
|
||||
/**
|
||||
* 用户的优惠劵列表(可用 + 不可用)
|
||||
*/
|
||||
private List<Coupon> coupons;
|
||||
|
||||
/**
|
||||
* 会员剩余积分
|
||||
@@ -340,4 +345,62 @@ public class TradePriceCalculateRespBO {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 优惠劵信息
|
||||
*/
|
||||
@Data
|
||||
public static class Coupon {
|
||||
|
||||
/**
|
||||
* 优惠劵编号
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 优惠劵名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 是否设置满多少金额可用,单位:分
|
||||
*/
|
||||
private Integer usePrice;
|
||||
|
||||
/**
|
||||
* 生效开始时间
|
||||
*/
|
||||
private LocalDateTime validStartTime;
|
||||
/**
|
||||
* 生效结束时间
|
||||
*/
|
||||
private LocalDateTime validEndTime;
|
||||
|
||||
/**
|
||||
* 优惠类型
|
||||
*/
|
||||
private Integer discountType;
|
||||
/**
|
||||
* 折扣百分比
|
||||
*/
|
||||
private Integer discountPercent;
|
||||
/**
|
||||
* 优惠金额,单位:分
|
||||
*/
|
||||
private Integer discountPrice;
|
||||
/**
|
||||
* 折扣上限,单位:分
|
||||
*/
|
||||
private Integer discountLimitPrice;
|
||||
|
||||
/**
|
||||
* 是否匹配
|
||||
*/
|
||||
private Boolean match;
|
||||
/**
|
||||
* 不匹配的原因
|
||||
*/
|
||||
private String mismatchReason;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -1,31 +1,31 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
|
||||
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.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
|
||||
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_CAN_NOT_USE;
|
||||
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} 实现类
|
||||
@@ -41,34 +41,37 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 1.1 校验优惠劵
|
||||
// 只有【普通】订单,才允许使用优惠劵
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
if (param.getCouponId() != null) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.1 加载用户的优惠劵列表
|
||||
List<CouponRespDTO> coupons = couponApi.getCouponListByUserId(param.getUserId(), CouponStatusEnum.UNUSED.getStatus());
|
||||
coupons.removeIf(coupon -> LocalDateTimeUtils.beforeNow(coupon.getValidEndTime()));
|
||||
// 1.2 计算优惠劵的使用条件
|
||||
result.setCoupons(calculateCoupons(coupons, result));
|
||||
|
||||
// 2. 校验优惠劵是否可用
|
||||
if (param.getCouponId() == null) {
|
||||
return;
|
||||
}
|
||||
CouponRespDTO coupon = couponApi.validateCoupon(new CouponValidReqDTO()
|
||||
.setId(param.getCouponId()).setUserId(param.getUserId()));
|
||||
Assert.notNull(coupon, "校验通过的优惠劵({}),不能为空", param.getCouponId());
|
||||
// 1.2 只有【普通】订单,才允许使用优惠劵
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER);
|
||||
TradePriceCalculateRespBO.Coupon couponBO = CollUtil.findOne(result.getCoupons(), item -> item.getId().equals(param.getCouponId()));
|
||||
CouponRespDTO coupon = CollUtil.findOne(coupons, item -> item.getId().equals(param.getCouponId()));
|
||||
if (couponBO == null || coupon == null) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_CAN_NOT_USE, "优惠劵不存在");
|
||||
}
|
||||
|
||||
// 2.1 获得匹配的商品 SKU 数组
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
|
||||
if (CollUtil.isEmpty(orderItems)) {
|
||||
throw exception(COUPON_NO_MATCH_SPU);
|
||||
}
|
||||
// 2.2 计算是否满足优惠劵的使用金额
|
||||
Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
|
||||
if (totalPayPrice < coupon.getUsePrice()) {
|
||||
throw exception(COUPON_NO_MATCH_MIN_PRICE);
|
||||
if (Boolean.FALSE.equals(couponBO.getMatch())) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_CAN_NOT_USE, couponBO.getMismatchReason());
|
||||
}
|
||||
|
||||
// 3.1 计算可以优惠的金额
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
|
||||
Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
|
||||
Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
|
||||
if (couponPrice <= totalPayPrice) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH);
|
||||
}
|
||||
// 3.2 计算分摊的优惠金额
|
||||
List<Integer> divideCouponPrices = TradePriceCalculatorHelper.dividePrice(orderItems, couponPrice);
|
||||
|
||||
@@ -76,7 +79,7 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
|
||||
result.setCouponId(param.getCouponId());
|
||||
// 4.2 记录优惠明细
|
||||
TradePriceCalculatorHelper.addPromotion(result, orderItems,
|
||||
param.getCouponId(), coupon.getName(), PromotionTypeEnum.COUPON.getType(),
|
||||
param.getCouponId(), couponBO.getName(), PromotionTypeEnum.COUPON.getType(),
|
||||
StrUtil.format("优惠劵:省 {} 元", TradePriceCalculatorHelper.formatPrice(couponPrice)),
|
||||
divideCouponPrices);
|
||||
// 4.3 更新 SKU 优惠金额
|
||||
@@ -88,6 +91,43 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算用户的优惠劵列表(可用 + 不可用)
|
||||
*
|
||||
* @param coupons 优惠劵
|
||||
* @param result 计算结果
|
||||
* @return 优惠劵列表
|
||||
*/
|
||||
private List<TradePriceCalculateRespBO.Coupon> calculateCoupons(List<CouponRespDTO> coupons,
|
||||
TradePriceCalculateRespBO result) {
|
||||
return convertList(coupons, coupon -> {
|
||||
TradePriceCalculateRespBO.Coupon matchCoupon = BeanUtils.toBean(coupon, TradePriceCalculateRespBO.Coupon.class);
|
||||
// 1.1 优惠劵未到使用时间
|
||||
if (LocalDateTimeUtils.afterNow(coupon.getValidStartTime())) {
|
||||
return matchCoupon.setMatch(false).setMismatchReason("优惠劵未到使用时间");
|
||||
}
|
||||
// 1.2 优惠劵没有匹配的商品
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
|
||||
if (CollUtil.isEmpty(orderItems)) {
|
||||
return matchCoupon.setMatch(false).setMismatchReason("优惠劵没有匹配的商品");
|
||||
}
|
||||
// 1.3 差 %1$,.2f 元可用优惠劵
|
||||
Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
|
||||
if (totalPayPrice < coupon.getUsePrice()) {
|
||||
return matchCoupon.setMatch(false)
|
||||
.setMismatchReason(String.format("差 %1$,.2f 元可用优惠劵", (coupon.getUsePrice() - totalPayPrice) / 100D));
|
||||
}
|
||||
// 1.4 优惠金额超过订单金额
|
||||
Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
|
||||
if (couponPrice >= totalPayPrice) {
|
||||
return matchCoupon.setMatch(false).setMismatchReason("优惠金额超过订单金额");
|
||||
}
|
||||
|
||||
// 2. 满足条件
|
||||
return matchCoupon.setMatch(true);
|
||||
});
|
||||
}
|
||||
|
||||
private Integer getCouponPrice(CouponRespDTO coupon, Integer totalPayPrice) {
|
||||
if (PromotionDiscountTypeEnum.PRICE.getType().equals(coupon.getDiscountType())) { // 减价
|
||||
return coupon.getDiscountPrice();
|
||||
|
@@ -1,12 +1,13 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
|
||||
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;
|
||||
@@ -14,8 +15,10 @@ import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -69,8 +72,10 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest {
|
||||
CouponRespDTO coupon = randomPojo(CouponRespDTO.class, o -> o.setId(1024L).setName("程序员节")
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
|
||||
.setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType())
|
||||
.setDiscountPercent(50).setDiscountLimitPrice(70));
|
||||
when(couponApi.validateCoupon(eq(new CouponValidReqDTO().setId(1024L).setUserId(233L)))).thenReturn(coupon);
|
||||
.setDiscountPercent(50).setDiscountLimitPrice(70))
|
||||
.setValidStartTime(addTime(Duration.ofDays(1))).setValidEndTime(addTime(Duration.ofDays(2)));
|
||||
when(couponApi.getCouponListByUserId(eq(233L), eq(CouponStatusEnum.UNUSED.getStatus())))
|
||||
.thenReturn(ListUtil.toList(coupon));
|
||||
|
||||
// 调用
|
||||
tradeCouponPriceCalculator.calculate(param, result);
|
||||
|
Reference in New Issue
Block a user