mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 02:28:03 +08:00 
			
		
		
		
	Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17
This commit is contained in:
		| @ -0,0 +1,42 @@ | ||||
| package cn.iocoder.yudao.module.promotion.api.point; | ||||
|  | ||||
| import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO; | ||||
|  | ||||
| /** | ||||
|  * 积分商城活动 API 接口 | ||||
|  * | ||||
|  * @author HUIHUI | ||||
|  */ | ||||
| public interface PointActivityApi { | ||||
|  | ||||
|     /** | ||||
|      * 【下单前】校验是否参与积分商城活动 | ||||
|      * | ||||
|      * 如果校验失败,则抛出业务异常 | ||||
|      * | ||||
|      * @param activityId 活动编号 | ||||
|      * @param skuId      SKU 编号 | ||||
|      * @param count      数量 | ||||
|      * @return 积分商城商品信息 | ||||
|      */ | ||||
|     PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count); | ||||
|  | ||||
|     /** | ||||
|      * 更新积分商城商品库存(减少) | ||||
|      * | ||||
|      * @param id    活动编号 | ||||
|      * @param skuId sku 编号 | ||||
|      * @param count 数量(正数) | ||||
|      */ | ||||
|     void updatePointStockDecr(Long id, Long skuId, Integer count); | ||||
|  | ||||
|     /** | ||||
|      * 更新积分商城商品库存(增加) | ||||
|      * | ||||
|      * @param id    活动编号 | ||||
|      * @param skuId sku 编号 | ||||
|      * @param count 数量(正数) | ||||
|      */ | ||||
|     void updatePointStockIncr(Long id, Long skuId, Integer count); | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,24 @@ | ||||
| package cn.iocoder.yudao.module.promotion.api.point.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| /** | ||||
|  * 校验参与积分商城 Response DTO | ||||
|  */ | ||||
| @Data | ||||
| public class PointValidateJoinRespDTO { | ||||
|  | ||||
|     /** | ||||
|      * 可兑换次数 | ||||
|      */ | ||||
|     private Integer count; | ||||
|     /** | ||||
|      * 所需兑换积分 | ||||
|      */ | ||||
|     private Integer point; | ||||
|     /** | ||||
|      * 所需兑换金额,单位:分 | ||||
|      */ | ||||
|     private Integer price; | ||||
|  | ||||
| } | ||||
| @ -50,6 +50,10 @@ public interface ErrorCodeConstants { | ||||
|     ErrorCode POINT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_007_002, "积分商城活动已关闭,不能修改"); | ||||
|     ErrorCode POINT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_007_003, "积分商城活动未关闭或未结束,不能删除"); | ||||
|     ErrorCode POINT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_007_004, "积分商城活动已关闭,不能重复关闭"); | ||||
|     ErrorCode POINT_ACTIVITY_JOIN_ACTIVITY_STATUS_CLOSED = new ErrorCode(1_013_007_005, "积分商品兑换失败,原因:积分商城活动已关闭"); | ||||
|     ErrorCode POINT_ACTIVITY_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_007_006, "积分商品兑换失败,原因:单次限购超出"); | ||||
|     ErrorCode POINT_ACTIVITY_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_007_007, "积分商品兑换失败,原因:商品不存在"); | ||||
|     ErrorCode POINT_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_007_008, "积分商品兑换失败,原因:积分商品库存不足"); | ||||
|  | ||||
|     // ========== 秒杀活动 1-013-008-000 ========== | ||||
|     ErrorCode SECKILL_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_008_000, "秒杀活动不存在"); | ||||
|  | ||||
| @ -0,0 +1,36 @@ | ||||
| package cn.iocoder.yudao.module.promotion.api.point; | ||||
|  | ||||
| import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO; | ||||
| import cn.iocoder.yudao.module.promotion.service.point.PointActivityService; | ||||
| import jakarta.annotation.Resource; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
|  | ||||
| /** | ||||
|  * 积分商城活动 Api 接口实现类 | ||||
|  * | ||||
|  * @author HUIHUI | ||||
|  */ | ||||
| @Service | ||||
| @Validated | ||||
| public class PointActivityApiImpl implements PointActivityApi { | ||||
|  | ||||
|     @Resource | ||||
|     private PointActivityService pointActivityService; | ||||
|  | ||||
|     @Override | ||||
|     public PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count) { | ||||
|         return pointActivityService.validateJoinPointActivity(activityId, skuId, count); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void updatePointStockDecr(Long id, Long skuId, Integer count) { | ||||
|         pointActivityService.updatePointStockDecr(id, skuId, count); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void updatePointStockIncr(Long id, Long skuId, Integer count) { | ||||
|         pointActivityService.updatePointStockIncr(id, skuId, count); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -76,8 +76,11 @@ public class AppPointActivityController { | ||||
|  | ||||
|         // 2. 拼接数据 | ||||
|         List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds(Collections.singletonList(id)); | ||||
|         AppPointActivityDetailRespVO respVO = BeanUtils.toBean(activity, AppPointActivityDetailRespVO.class); | ||||
|         respVO.setProducts(BeanUtils.toBean(products, AppPointActivityDetailRespVO.Product.class)); | ||||
|         PointProductDO minProduct = getMinObject(products, PointProductDO::getPoint); | ||||
|         assert minProduct != null; | ||||
|         AppPointActivityDetailRespVO respVO = BeanUtils.toBean(activity, AppPointActivityDetailRespVO.class) | ||||
|                 .setProducts(BeanUtils.toBean(products, AppPointActivityDetailRespVO.Product.class)) | ||||
|                 .setPoint(minProduct.getPoint()).setPrice(minProduct.getPrice()); | ||||
|         return success(respVO); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -30,6 +30,14 @@ public class AppPointActivityDetailRespVO { | ||||
|     @Schema(description = "商品信息数组", requiredMode = Schema.RequiredMode.REQUIRED) | ||||
|     private List<Product> products; | ||||
|  | ||||
|     //======================= 显示所需兑换积分最少的 SKU 信息 ======================= | ||||
|  | ||||
|     @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED) | ||||
|     private Integer point; | ||||
|  | ||||
|     @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860") | ||||
|     private Integer price; | ||||
|  | ||||
|     @Schema(description = "商品信息") | ||||
|     @Data | ||||
|     public static class Product { | ||||
|  | ||||
| @ -1,10 +1,12 @@ | ||||
| package cn.iocoder.yudao.module.promotion.dal.mysql.point; | ||||
|  | ||||
| import cn.hutool.core.lang.Assert; | ||||
| 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.point.vo.activity.PointActivityPageReqVO; | ||||
| import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO; | ||||
| import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | ||||
| import org.apache.ibatis.annotations.Mapper; | ||||
|  | ||||
| /** | ||||
| @ -25,4 +27,33 @@ public interface PointActivityMapper extends BaseMapperX<PointActivityDO> { | ||||
|                 .orderByDesc(PointActivityDO::getId)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 更新活动库存(减少) | ||||
|      * | ||||
|      * @param id    活动编号 | ||||
|      * @param count 扣减的库存数量(正数) | ||||
|      * @return 影响的行数 | ||||
|      */ | ||||
|     default int updateStockDecr(Long id, int count) { | ||||
|         Assert.isTrue(count > 0); | ||||
|         return update(null, new LambdaUpdateWrapper<PointActivityDO>() | ||||
|                 .eq(PointActivityDO::getId, id) | ||||
|                 .ge(PointActivityDO::getStock, count) | ||||
|                 .setSql("stock = stock - " + count)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 更新活动库存(增加) | ||||
|      * | ||||
|      * @param id    活动编号 | ||||
|      * @param count 增加的库存数量(正数) | ||||
|      * @return 影响的行数 | ||||
|      */ | ||||
|     default int updateStockIncr(Long id, int count) { | ||||
|         Assert.isTrue(count > 0); | ||||
|         return update(null, new LambdaUpdateWrapper<PointActivityDO>() | ||||
|                 .eq(PointActivityDO::getId, id) | ||||
|                 .setSql("stock = stock + " + count)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -1,5 +1,6 @@ | ||||
| package cn.iocoder.yudao.module.promotion.dal.mysql.point; | ||||
|  | ||||
| import cn.hutool.core.lang.Assert; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||||
| import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO; | ||||
| import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | ||||
| @ -29,4 +30,37 @@ public interface PointProductMapper extends BaseMapperX<PointProductDO> { | ||||
|                 .eq(PointProductDO::getActivityId, pointProductDO.getActivityId())); | ||||
|     } | ||||
|  | ||||
|     default PointProductDO selectListByActivityIdAndSkuId(Long activityId, Long skuId) { | ||||
|         return selectOne(PointProductDO::getActivityId, activityId, | ||||
|                 PointProductDO::getSkuId, skuId); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 更新活动库存(减少) | ||||
|      * | ||||
|      * @param id    活动编号 | ||||
|      * @param count 扣减的库存数量(减少库存) | ||||
|      * @return 影响的行数 | ||||
|      */ | ||||
|     default int updateStockDecr(Long id, int count) { | ||||
|         Assert.isTrue(count > 0); | ||||
|         return update(null, new LambdaUpdateWrapper<PointProductDO>() | ||||
|                 .eq(PointProductDO::getId, id) | ||||
|                 .ge(PointProductDO::getStock, count) | ||||
|                 .setSql("stock = stock - " + count)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 更新活动库存(增加) | ||||
|      * | ||||
|      * @param id    活动编号 | ||||
|      * @param count 需要增加的库存(增加库存) | ||||
|      * @return 影响的行数 | ||||
|      */ | ||||
|     default int updateStockIncr(Long id, int count) { | ||||
|         Assert.isTrue(count > 0); | ||||
|         return update(null, new LambdaUpdateWrapper<PointProductDO>() | ||||
|                 .eq(PointProductDO::getId, id) | ||||
|                 .setSql("stock = stock + " + count)); | ||||
|     } | ||||
| } | ||||
| @ -1,6 +1,7 @@ | ||||
| package cn.iocoder.yudao.module.promotion.service.point; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO; | ||||
| import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO; | ||||
| import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivitySaveReqVO; | ||||
| import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO; | ||||
| @ -32,6 +33,24 @@ public interface PointActivityService { | ||||
|      */ | ||||
|     void updatePointActivity(@Valid PointActivitySaveReqVO updateReqVO); | ||||
|  | ||||
|     /** | ||||
|      * 更新积分商城商品库存(减少) | ||||
|      * | ||||
|      * @param id    活动编号 | ||||
|      * @param skuId sku 编号 | ||||
|      * @param count 数量(正数) | ||||
|      */ | ||||
|     void updatePointStockDecr(Long id, Long skuId, Integer count); | ||||
|  | ||||
|     /** | ||||
|      * 更新积分商城商品库存(增加) | ||||
|      * | ||||
|      * @param id    活动编号 | ||||
|      * @param skuId sku 编号 | ||||
|      * @param count 数量(正数) | ||||
|      */ | ||||
|     void updatePointStockIncr(Long id, Long skuId, Integer count); | ||||
|  | ||||
|     /** | ||||
|      * 关闭积分商城活动 | ||||
|      * | ||||
| @ -78,4 +97,16 @@ public interface PointActivityService { | ||||
|      */ | ||||
|     List<PointProductDO> getPointProductListByActivityIds(Collection<Long> activityIds); | ||||
|  | ||||
|     /** | ||||
|      * 【下单前】校验是否参与积分商城活动 | ||||
|      * | ||||
|      * 如果校验失败,则抛出业务异常 | ||||
|      * | ||||
|      * @param activityId 活动编号 | ||||
|      * @param skuId      SKU 编号 | ||||
|      * @param count      数量 | ||||
|      * @return 积分商城商品信息 | ||||
|      */ | ||||
|     PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count); | ||||
|  | ||||
| } | ||||
| @ -8,6 +8,7 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; | ||||
| import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; | ||||
| 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.api.point.dto.PointValidateJoinRespDTO; | ||||
| import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO; | ||||
| import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivitySaveReqVO; | ||||
| import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductSaveReqVO; | ||||
| @ -102,6 +103,43 @@ public class PointActivityServiceImpl implements PointActivityService { | ||||
|         updateSeckillProduct(updateObj, updateReqVO.getProducts()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public void updatePointStockDecr(Long id, Long skuId, Integer count) { | ||||
|         // 1.1 校验活动库存是否充足 | ||||
|         PointActivityDO activity = validatePointActivityExists(id); | ||||
|         if (count > activity.getStock()) { | ||||
|             throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL); | ||||
|         } | ||||
|         // 1.2 校验商品库存是否充足 | ||||
|         PointProductDO product = pointProductMapper.selectListByActivityIdAndSkuId(id, skuId); | ||||
|         if (product == null || count > product.getStock()) { | ||||
|             throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL); | ||||
|         } | ||||
|  | ||||
|         // 2.1 更新活动商品库存 | ||||
|         int updateCount = pointProductMapper.updateStockDecr(product.getId(), count); | ||||
|         if (updateCount == 0) { | ||||
|             throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL); | ||||
|         } | ||||
|  | ||||
|         // 2.2 更新活动库存 | ||||
|         updateCount = pointActivityMapper.updateStockDecr(id, count); | ||||
|         if (updateCount == 0) { | ||||
|             throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public void updatePointStockIncr(Long id, Long skuId, Integer count) { | ||||
|         PointProductDO product = pointProductMapper.selectListByActivityIdAndSkuId(id, skuId); | ||||
|         // 更新活动商品库存 | ||||
|         pointProductMapper.updateStockIncr(product.getId(), count); | ||||
|         // 更新活动库存 | ||||
|         pointActivityMapper.updateStockIncr(id, count); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public void closePointActivity(Long id) { | ||||
| @ -244,4 +282,28 @@ public class PointActivityServiceImpl implements PointActivityService { | ||||
|         return pointProductMapper.selectListByActivityId(activityIds); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count) { | ||||
|         // 1. 校验积分商城活动是否存在 | ||||
|         PointActivityDO activity = validatePointActivityExists(activityId); | ||||
|         if (CommonStatusEnum.isDisable(activity.getStatus())) { | ||||
|             throw exception(POINT_ACTIVITY_JOIN_ACTIVITY_STATUS_CLOSED); | ||||
|         } | ||||
|  | ||||
|         // 2.1 校验积分商城商品是否存在 | ||||
|         PointProductDO product = pointProductMapper.selectListByActivityIdAndSkuId(activityId, skuId); | ||||
|         if (product == null) { | ||||
|             throw exception(POINT_ACTIVITY_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS); | ||||
|         } | ||||
|         // 2.2 超过单次购买限制 | ||||
|         if (count > product.getCount()) { | ||||
|             throw exception(POINT_ACTIVITY_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED); | ||||
|         } | ||||
|         // 2.2 校验库存是否充足 | ||||
|         if (count > product.getStock()) { | ||||
|             throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL); | ||||
|         } | ||||
|         return BeanUtils.toBean(product, PointValidateJoinRespDTO.class); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -160,7 +160,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { | ||||
|     public void updateSeckillStockDecr(Long id, Long skuId, Integer count) { | ||||
|         // 1.1 校验活动库存是否充足 | ||||
|         SeckillActivityDO seckillActivity = validateSeckillActivityExists(id); | ||||
|         if (count > seckillActivity.getTotalStock()) { | ||||
|         if (count > seckillActivity.getStock()) { | ||||
|             throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL); | ||||
|         } | ||||
|         // 1.2 校验商品库存是否充足 | ||||
|  | ||||
| @ -58,11 +58,12 @@ public interface ErrorCodeConstants { | ||||
|  | ||||
|     // ========== Price 相关 1-011-003-000 ============ | ||||
|     ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1_011_003_000, "支付价格计算异常,原因:价格小于等于 0"); | ||||
|     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_CAN_NOT_USE = new ErrorCode(1_011_003_007, "该优惠劵无法使用,原因:{}」"); | ||||
|     ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_001, "计算快递运费异常,找不到对应的运费模板"); | ||||
|     ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_002, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵"); | ||||
|     ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_003, "参与秒杀的商品,超过了秒杀总限购数量"); | ||||
|     ErrorCode PRICE_CALCULATE_POINT_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_004, "参与积分活动的商品,超过了积分活动商品总限购数量"); | ||||
|     ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL = new ErrorCode(1_011_003_005, "计算快递运费异常,配送方式不匹配"); | ||||
|     ErrorCode PRICE_CALCULATE_COUPON_CAN_NOT_USE = new ErrorCode(1_011_003_006, "该优惠劵无法使用,原因:{}」"); | ||||
|  | ||||
|     // ========== 物流 Express 模块 1-011-004-000 ========== | ||||
|     ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在"); | ||||
|  | ||||
| @ -20,6 +20,7 @@ public enum TradeOrderTypeEnum implements IntArrayValuable { | ||||
|     SECKILL(1, "秒杀订单"), | ||||
|     BARGAIN(2, "砍价订单"), | ||||
|     COMBINATION(3, "拼团订单"), | ||||
|     POINT(4, "积分商城"), | ||||
|     ; | ||||
|  | ||||
|     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TradeOrderTypeEnum::getType).toArray(); | ||||
| @ -54,4 +55,8 @@ public enum TradeOrderTypeEnum implements IntArrayValuable { | ||||
|         return ObjectUtil.equal(type, COMBINATION.getType()); | ||||
|     } | ||||
|  | ||||
|     public static boolean isPoint(Integer type) { | ||||
|         return ObjectUtil.equal(type, POINT.getType()); | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -6,13 +6,13 @@ import cn.iocoder.yudao.framework.common.validation.Mobile; | ||||
| import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| import jakarta.validation.Valid; | ||||
| import jakarta.validation.constraints.AssertTrue; | ||||
| import jakarta.validation.constraints.Min; | ||||
| import jakarta.validation.constraints.NotEmpty; | ||||
| import jakarta.validation.constraints.NotNull; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| @Schema(description = "用户 App - 交易订单结算 Request VO") | ||||
| @ -62,6 +62,10 @@ public class AppTradeOrderSettlementReqVO { | ||||
|     @Schema(description = "砍价记录编号", example = "123") | ||||
|     private Long bargainRecordId; | ||||
|  | ||||
|     // ========== 积分商城活动相关字段 ========== | ||||
|     @Schema(description = "积分商城活动编号", example = "123") | ||||
|     private Long pointActivityId; | ||||
|  | ||||
|     @AssertTrue(message = "活动商品每次只能购买一种规格") | ||||
|     @JsonIgnore | ||||
|     public boolean isValidActivityItems() { | ||||
|  | ||||
| @ -219,7 +219,8 @@ public interface TradeOrderConvert { | ||||
|                 .setSeckillActivityId(settlementReqVO.getSeckillActivityId()) | ||||
|                 .setBargainRecordId(settlementReqVO.getBargainRecordId()) | ||||
|                 .setCombinationActivityId(settlementReqVO.getCombinationActivityId()) | ||||
|                 .setCombinationHeadId(settlementReqVO.getCombinationHeadId()); | ||||
|                 .setCombinationHeadId(settlementReqVO.getCombinationHeadId()) | ||||
|                 .setPointActivityId(settlementReqVO.getPointActivityId()); | ||||
|         // 商品项的构建 | ||||
|         Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId); | ||||
|         for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) { | ||||
|  | ||||
| @ -353,4 +353,11 @@ public class TradeOrderDO extends BaseDO { | ||||
|      */ | ||||
|     private Long combinationRecordId; | ||||
|  | ||||
|     /** | ||||
|      * 积分商城活动的编号 | ||||
|      * | ||||
|      * 关联 PointActivityDO 的 id 字段 | ||||
|      */ | ||||
|     private Long pointActivityId; | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -42,9 +42,9 @@ public interface TradeOrderQueryService { | ||||
|     /** | ||||
|      * 获得指定用户,指定活动,指定状态的交易订单 | ||||
|      * | ||||
|      * @param userId     用户编号 | ||||
|      * @param userId                用户编号 | ||||
|      * @param combinationActivityId 活动编号 | ||||
|      * @param status     订单状态 | ||||
|      * @param status                订单状态 | ||||
|      * @return 交易订单 | ||||
|      */ | ||||
|     TradeOrderDO getOrderByUserIdAndStatusAndCombination(Long userId, Long combinationActivityId, Integer status); | ||||
|  | ||||
| @ -245,7 +245,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | ||||
|         } | ||||
|  | ||||
|         // 3. 生成预支付 | ||||
|         createPayOrder(order, orderItems); | ||||
|         // 特殊情况:积分兑换时,可能支付金额为零 | ||||
|         if (order.getPayPrice() > 0) { | ||||
|             createPayOrder(order, orderItems); | ||||
|         } | ||||
|  | ||||
|         // 4. 插入订单日志 | ||||
|         TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus()); | ||||
|  | ||||
| @ -0,0 +1,71 @@ | ||||
| package cn.iocoder.yudao.module.trade.service.order.handler; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.Assert; | ||||
| import cn.iocoder.yudao.module.promotion.api.point.PointActivityApi; | ||||
| 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.enums.order.TradeOrderStatusEnum; | ||||
| import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; | ||||
| import jakarta.annotation.Resource; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * 积分商城活动订单的 {@link TradeOrderHandler} 实现类 | ||||
|  * | ||||
|  * @author HUIHUI | ||||
|  */ | ||||
| @Component | ||||
| public class TradePointOrderHandler implements TradeOrderHandler { | ||||
|  | ||||
|     @Resource | ||||
|     private PointActivityApi pointActivityApi; | ||||
|  | ||||
|     @Override | ||||
|     public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) { | ||||
|         if (!TradeOrderTypeEnum.isPoint(order.getType())) { | ||||
|             return; | ||||
|         } | ||||
|         // 明确校验一下 | ||||
|         Assert.isTrue(orderItems.size() == 1, "积分商城活动兑换商品兑换时,只允许选择一个商品"); | ||||
|  | ||||
|         // 扣减积分商城活动的库存 | ||||
|         pointActivityApi.updatePointStockDecr(order.getPointActivityId(), | ||||
|                 orderItems.get(0).getSkuId(), orderItems.get(0).getCount()); | ||||
|  | ||||
|         // 如果支付金额为 0,则直接设置为已支付 | ||||
|         if (Objects.equals(order.getPayPrice(), 0)) { | ||||
|             order.setPayStatus(true).setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) { | ||||
|         if (!TradeOrderTypeEnum.isPoint(order.getType())) { | ||||
|             return; | ||||
|         } | ||||
|         // 明确校验一下 | ||||
|         Assert.isTrue(orderItems.size() == 1, "积分商城活动兑换商品兑换时,只允许选择一个商品"); | ||||
|  | ||||
|         // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚 | ||||
|         orderItems = filterOrderItemListByNoneAfterSale(orderItems); | ||||
|         if (CollUtil.isEmpty(orderItems)) { | ||||
|             return; | ||||
|         } | ||||
|         afterCancelOrderItem(order, orderItems.get(0)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) { | ||||
|         if (!TradeOrderTypeEnum.isPoint(order.getType())) { | ||||
|             return; | ||||
|         } | ||||
|         // 恢复积分商城活动的库存 | ||||
|         pointActivityApi.updatePointStockIncr(order.getPointActivityId(), | ||||
|                 orderItem.getSkuId(), orderItem.getCount()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -69,7 +69,8 @@ public class TradePriceServiceImpl implements TradePriceService { | ||||
|                 .buildCalculateResp(calculateReqBO, spuList, skuList); | ||||
|         priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, calculateRespBO)); | ||||
|         // 2.2  如果最终支付金额小于等于 0,则抛出业务异常 | ||||
|         if (calculateRespBO.getPrice().getPayPrice() <= 0) { | ||||
|         if (calculateReqBO.getPointActivityId() == null // 积分订单,允许支付金额为 0 | ||||
|                 && calculateRespBO.getPrice().getPayPrice() <= 0) { | ||||
|             log.error("[calculatePrice][价格计算不正确,请求 calculateReqDTO({}),结果 priceCalculate({})]", | ||||
|                     calculateReqBO, calculateRespBO); | ||||
|             throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL); | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| package cn.iocoder.yudao.module.trade.service.price.bo; | ||||
|  | ||||
| import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; | ||||
| import lombok.Data; | ||||
|  | ||||
| import jakarta.validation.Valid; | ||||
| import jakarta.validation.constraints.Min; | ||||
| import jakarta.validation.constraints.NotNull; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
| @ -84,6 +84,12 @@ public class TradePriceCalculateReqBO { | ||||
|      */ | ||||
|     private Long bargainRecordId; | ||||
|  | ||||
|     // ========== 积分商城活动相关字段 ========== | ||||
|     /** | ||||
|      * 积分商城活动编号 | ||||
|      */ | ||||
|     private Long pointActivityId; | ||||
|  | ||||
|     /** | ||||
|      * 商品 SKU | ||||
|      */ | ||||
|  | ||||
| @ -0,0 +1,94 @@ | ||||
| package cn.iocoder.yudao.module.trade.service.price.calculator; | ||||
|  | ||||
| import cn.hutool.core.lang.Assert; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| 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.api.point.PointActivityApi; | ||||
| import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO; | ||||
| 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.order.TradeOrderQueryService; | ||||
| 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 lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.core.annotation.Order; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||
| import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_POINT_TOTAL_LIMIT_COUNT; | ||||
|  | ||||
| /** | ||||
|  * 积分商城的 {@link TradePriceCalculator} 实现类 | ||||
|  * | ||||
|  * @author owen | ||||
|  */ | ||||
| @Component | ||||
| @Order(TradePriceCalculator.ORDER_POINT_ACTIVITY) | ||||
| @Slf4j | ||||
| public class TradePointActivityPriceCalculator implements TradePriceCalculator { | ||||
|  | ||||
|     @Resource | ||||
|     private PointActivityApi pointActivityApi; | ||||
|     @Resource | ||||
|     private MemberUserApi memberUserApi; | ||||
|  | ||||
|     @Resource | ||||
|     private TradeOrderQueryService tradeOrderQueryService; | ||||
|  | ||||
|     @Override | ||||
|     public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { | ||||
|         // 1.1 判断订单类型是否为积分商城活动 | ||||
|         if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.POINT.getType())) { | ||||
|             return; | ||||
|         } | ||||
|         // 1.2 初始化积分 | ||||
|         MemberUserRespDTO user = memberUserApi.getUser(param.getUserId()); | ||||
|         result.setTotalPoint(user.getPoint()).setUsePoint(0); | ||||
|  | ||||
|         // 1.3 校验用户积分余额 | ||||
|         if (user.getPoint() == null || user.getPoint() <= 0) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Assert.isTrue(param.getItems().size() == 1, "积分商城兑换商品时,只允许选择一个商品"); | ||||
|         // 2. 校验是否可以参与积分商城活动 | ||||
|         TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0); | ||||
|         PointValidateJoinRespDTO activity = validateJoinSeckill( | ||||
|                 param.getUserId(), param.getPointActivityId(), | ||||
|                 orderItem.getSkuId(), orderItem.getCount()); | ||||
|  | ||||
|         // 3.1 记录优惠明细 | ||||
|         int discountPrice = orderItem.getPayPrice(); // 情况一:单使用积分兑换 | ||||
|         Assert.isTrue(activity.getPoint() >= 1, "积分商城商品兑换积分必须大于 1"); | ||||
|         result.setUsePoint(activity.getPoint() * orderItem.getCount()); | ||||
|         orderItem.setUsePoint(activity.getPoint() * orderItem.getCount()); | ||||
|         if (activity.getPrice() != null && activity.getPrice() > 0) { // 情况二:积分 + 金额 | ||||
|             discountPrice = orderItem.getPayPrice() - activity.getPrice() * orderItem.getCount(); | ||||
|         } | ||||
|         // 3.2 记录优惠明细 | ||||
|         TradePriceCalculatorHelper.addPromotion(result, orderItem, | ||||
|                 param.getPointActivityId(), "积分商城活动", PromotionTypeEnum.POINT.getType(), | ||||
|                 StrUtil.format("积分商城活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)), | ||||
|                 discountPrice); | ||||
|  | ||||
|         // 3.3 更新 SKU 优惠金额 | ||||
|         orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice); | ||||
|         TradePriceCalculatorHelper.recountPayPrice(orderItem); | ||||
|         TradePriceCalculatorHelper.recountAllPrice(result); | ||||
|     } | ||||
|  | ||||
|     private PointValidateJoinRespDTO validateJoinSeckill(Long userId, Long activityId, Long skuId, Integer count) { | ||||
|         // 1. 校验是否可以参与积分商城活动 | ||||
|         PointValidateJoinRespDTO pointValidateJoinRespDTO = pointActivityApi.validateJoinPointActivity(activityId, skuId, count); | ||||
|         // 2. 校验总限购数量,目前只有 trade 有具体下单的数据,需要交给 trade 价格计算使用 | ||||
|         int activityProductCount = tradeOrderQueryService.getSeckillProductCount(userId, activityId); | ||||
|         if (activityProductCount + count > pointValidateJoinRespDTO.getCount()) { | ||||
|             throw exception(PRICE_CALCULATE_POINT_TOTAL_LIMIT_COUNT); | ||||
|         } | ||||
|         return pointValidateJoinRespDTO; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -1,12 +1,14 @@ | ||||
| package cn.iocoder.yudao.module.trade.service.price.calculator; | ||||
|  | ||||
| import cn.hutool.core.util.BooleanUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.iocoder.yudao.module.member.api.config.MemberConfigApi; | ||||
| import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO; | ||||
| 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.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; | ||||
| @ -37,6 +39,10 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator { | ||||
|  | ||||
|     @Override | ||||
|     public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { | ||||
|         // 判断订单类型是否不为积分商城活动 | ||||
|         if (ObjectUtil.equal(result.getType(), TradeOrderTypeEnum.POINT.getType())) { | ||||
|             return; | ||||
|         } | ||||
|         // 0. 初始化积分 | ||||
|         MemberUserRespDTO user = memberUserApi.getUser(param.getUserId()); | ||||
|         result.setTotalPoint(user.getPoint()).setUsePoint(0); | ||||
|  | ||||
| @ -16,6 +16,7 @@ public interface TradePriceCalculator { | ||||
|     int ORDER_SECKILL_ACTIVITY = 8; | ||||
|     int ORDER_BARGAIN_ACTIVITY = 8; | ||||
|     int ORDER_COMBINATION_ACTIVITY = 8; | ||||
|     int ORDER_POINT_ACTIVITY = 8; | ||||
|  | ||||
|     int ORDER_DISCOUNT_ACTIVITY = 10; | ||||
|     int ORDER_REWARD_ACTIVITY = 20; | ||||
|  | ||||
| @ -90,6 +90,9 @@ public class TradePriceCalculatorHelper { | ||||
|         if (param.getBargainRecordId() != null) { | ||||
|             return TradeOrderTypeEnum.BARGAIN.getType(); | ||||
|         } | ||||
|         if (param.getPointActivityId() != null) { | ||||
|             return TradeOrderTypeEnum.POINT.getType(); | ||||
|         } | ||||
|         return TradeOrderTypeEnum.NORMAL.getType(); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -8,11 +8,10 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; | ||||
| import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; | ||||
| 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 static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||
| import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT; | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV