diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java index c724df8c1..789a4526d 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO; import jakarta.validation.Valid; +import java.util.List; import java.util.Map; /** @@ -36,23 +37,21 @@ public interface CouponApi { */ CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO); - // TODO @puhui999:可能需要根据 TradeOrderDO 的建议,进行修改;需要返回优惠劵编号 /** * 【管理员】给指定用户批量发送优惠券 * - * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 + * @param giveCoupons key: 优惠劵模版编号,value:对应的数量 * @param userId 用户编号 + * @return 优惠券编号列表 */ - // TODO @puhui999:giveCouponsMap 可能改成 giveCoupons 更合适?优惠劵模版编号、数量 - void takeCouponsByAdmin(Map giveCouponsMap, Long userId); + List takeCouponsByAdmin(Map giveCoupons, Long userId); - // TODO @puhui999:可能需要根据 TradeOrderDO 的建议,进行修改 giveCouponsMap 参数 /** * 【管理员】作废指定用户的指定优惠劵 * - * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 + * @param giveCouponIds 赠送的优惠券编号 * @param userId 用户编号 */ - void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId); + void invalidateCouponsByAdmin(List giveCouponIds, Long userId); } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java index 93b5691fb..d8d5ef135 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java @@ -86,27 +86,17 @@ public class RewardActivityMatchRespDTO { * 是否包邮 */ private Boolean freeDelivery; - // TODO @puhui999:建议不返回 + 去掉 givePoint、giveCoupon 字段哈。 - /** - * 是否赠送积分 - */ - private Boolean givePoint; /** * 赠送的积分 */ private Integer point; - /** - * 是否赠送优惠券 - */ - private Boolean giveCoupon; - // TODO @puhui999:giveCoupons 即可 /** * 赠送的优惠劵 * * key: 优惠劵模版编号 * value:对应的优惠券数量 */ - private Map giveCouponsMap; + private Map giveCoupons; } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java index 831d4b5a0..bef4db225 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java @@ -17,9 +17,7 @@ public enum CouponStatusEnum implements IntArrayValuable { UNUSED(1, "未使用"), USED(2, "已使用"), - EXPIRE(3, "已过期"), - // TODO @puhui999:捉摸了下,貌似搞成逻辑删除好了?不然好多地方的 status 都要做一些变动。可能未来加个 invalidateType 来标识,是管理后台删除,还是取消回收。或者优惠劵的 change log 可能更好。 - INVALID(4, "已作废"); + EXPIRE(3, "已过期"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CouponStatusEnum::getStatus).toArray(); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java index 22fea4525..edc8f1b7f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java @@ -11,6 +11,7 @@ import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import java.util.List; import java.util.Map; /** @@ -43,13 +44,13 @@ public class CouponApiImpl implements CouponApi { } @Override - public void takeCouponsByAdmin(Map giveCouponsMap, Long userId) { - couponService.takeCouponsByAdmin(giveCouponsMap, userId); + public List takeCouponsByAdmin(Map giveCoupons, Long userId) { + return couponService.takeCouponsByAdmin(giveCoupons, userId); } @Override - public void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId) { - couponService.invalidateCouponsByAdmin(giveCouponsMap, userId); + public void invalidateCouponsByAdmin(List giveCouponIds, Long userId) { + couponService.invalidateCouponsByAdmin(giveCouponIds, userId); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java index 913b84510..a06b92338 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java @@ -8,6 +8,7 @@ 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.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.github.yulichang.toolkit.MPJWrappers; import org.apache.ibatis.annotations.Mapper; @@ -72,15 +73,6 @@ public interface CouponMapper extends BaseMapperX { ); } - default List selectListByTemplateIdAndUserIdAndTakeType(Long templateId, Collection userIds, - Integer takeType) { - return selectList(new LambdaQueryWrapperX() - .eq(CouponDO::getTemplateId, templateId) - .eq(CouponDO::getTakeType, takeType) - .in(CouponDO::getUserId, userIds) - ); - } - default Map selectCountByUserIdAndTemplateIdIn(Long userId, Collection templateIds) { String templateIdAlias = "templateId"; String countAlias = "count"; @@ -116,4 +108,11 @@ public interface CouponMapper extends BaseMapperX { ); } + default List selectListByIdAndUserIdAndTakeType(Long couponId, Long userId, Integer takeType) { + return selectList(new LambdaQueryWrapper() + .eq(CouponDO::getId, couponId) + .eq(CouponDO::getUserId, userId) + .eq(CouponDO::getTakeType, takeType)); + } + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java index 97c1412ca..622b09a5b 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java @@ -38,14 +38,6 @@ public interface CouponService { */ void validCoupon(CouponDO coupon); - /** - * 获得优惠劵分页 - * - * @param pageReqVO 分页查询 - * @return 优惠劵分页 - */ - PageResult getCouponPage(CouponPageReqVO pageReqVO); - /** * 使用优惠劵 * @@ -69,57 +61,43 @@ public interface CouponService { */ void deleteCoupon(Long id); - /** - * 获得用户的优惠劵列表 - * - * @param userId 用户编号 - * @param status 优惠劵状态 - * @return 优惠劵列表 - */ - List getCouponList(Long userId, Integer status); - - /** - * 获得未使用的优惠劵数量 - * - * @param userId 用户编号 - * @return 未使用的优惠劵数量 - */ - Long getUnusedCouponCount(Long userId); - /** * 领取优惠券 * * @param templateId 优惠券模板编号 * @param userIds 用户编号列表 * @param takeType 领取方式 + * @return key: userId, value: 优惠券编号列表 */ - void takeCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType); + Map> takeCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType); /** * 【管理员】给用户发送优惠券 * * @param templateId 优惠券模板编号 * @param userIds 用户编号列表 + * @return key: userId, value: 优惠券编号列表 */ - default void takeCouponByAdmin(Long templateId, Set userIds) { - takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN); + default Map> takeCouponByAdmin(Long templateId, Set userIds) { + return takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN); } /** * 【管理员】给指定用户批量发送优惠券 * - * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 + * @param giveCoupons key: 优惠劵模版编号,value:对应的数量 * @param userId 用户编号 + * @return 优惠券编号列表 */ - void takeCouponsByAdmin(Map giveCouponsMap, Long userId); + List takeCouponsByAdmin(Map giveCoupons, Long userId); /** - * 【管理员】收回给指定用户批量发送优惠券 + * 【管理员】作废指定用户的指定优惠劵 * - * @param giveCouponsMap key: 优惠劵编号,value:对应的优惠券数量 + * @param giveCouponIds 赠送的优惠券编号 * @param userId 用户编号 */ - void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId); + void invalidateCouponsByAdmin(List giveCouponIds, Long userId); /** * 【会员】领取优惠券 @@ -138,6 +116,49 @@ public interface CouponService { */ void takeCouponByRegister(Long userId); + /** + * 过期优惠券 + * + * @return 过期数量 + */ + int expireCoupon(); + + //======================= 查询相关 ======================= + + /** + * 获得未使用的优惠劵数量 + * + * @param userId 用户编号 + * @return 未使用的优惠劵数量 + */ + Long getUnusedCouponCount(Long userId); + + /** + * 获得优惠劵分页 + * + * @param pageReqVO 分页查询 + * @return 优惠劵分页 + */ + PageResult getCouponPage(CouponPageReqVO pageReqVO); + + /** + * 获得用户的优惠劵列表 + * + * @param userId 用户编号 + * @param status 优惠劵状态 + * @return 优惠劵列表 + */ + List getCouponList(Long userId, Integer status); + + /** + * 统计会员领取优惠券的数量 + * + * @param templateIds 优惠券模板编号列表 + * @param userId 用户编号 + * @return 领取优惠券的数量 + */ + Map getTakeCountMapByTemplateIds(Collection templateIds, Long userId); + /** * 获取会员领取指定优惠券的数量 * @@ -150,15 +171,6 @@ public interface CouponService { return MapUtil.getInt(map, templateId, 0); } - /** - * 统计会员领取优惠券的数量 - * - * @param templateIds 优惠券模板编号列表 - * @param userId 用户编号 - * @return 领取优惠券的数量 - */ - Map getTakeCountMapByTemplateIds(Collection templateIds, Long userId); - /** * 获取用户匹配的优惠券列表 * @@ -168,13 +180,6 @@ public interface CouponService { */ List getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO); - /** - * 过期优惠券 - * - * @return 过期数量 - */ - int expireCoupon(); - /** * 获取用户是否可以领取优惠券 * diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index 666a310e7..ecc1adb46 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.service.coupon; import cn.hutool.core.collection.CollStreamUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; @@ -31,6 +32,7 @@ import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; import static java.util.Arrays.asList; @@ -75,20 +77,6 @@ public class CouponServiceImpl implements CouponService { } } - @Override - public PageResult getCouponPage(CouponPageReqVO pageReqVO) { - // 获得用户编号 - if (StrUtil.isNotEmpty(pageReqVO.getNickname())) { - List users = memberUserApi.getUserListByNickname(pageReqVO.getNickname()); - if (CollUtil.isEmpty(users)) { - return PageResult.empty(); - } - pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId)); - } - // 分页查询 - return couponMapper.selectPage(pageReqVO); - } - @Override public void useCoupon(Long id, Long userId, Long orderId) { // 校验优惠劵 @@ -145,27 +133,9 @@ public class CouponServiceImpl implements CouponService { couponTemplateService.updateCouponTemplateTakeCount(coupon.getTemplateId(), -1); } - @Override - public List getCouponList(Long userId, Integer status) { - return couponMapper.selectListByUserIdAndStatus(userId, status); - } - - private CouponDO validateCouponExists(Long id) { - CouponDO coupon = couponMapper.selectById(id); - if (coupon == null) { - throw exception(COUPON_NOT_EXISTS); - } - return coupon; - } - - @Override - public Long getUnusedCouponCount(Long userId) { - return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus()); - } - @Override @Transactional(rollbackFor = Exception.class) - public void takeCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType) { + public Map> takeCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType) { CouponTemplateDO template = couponTemplateService.getCouponTemplate(templateId); // 1. 过滤掉达到领取限制的用户 removeTakeLimitUser(userIds, template); @@ -173,40 +143,45 @@ public class CouponServiceImpl implements CouponService { validateCouponTemplateCanTake(template, userIds, takeType); // 3. 批量保存优惠劵 - couponMapper.insertBatch(convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId))); + List couponList = convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId)); + couponMapper.insertBatch(couponList); // 4. 增加优惠劵模板的领取数量 couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size()); + + return convertMultiMap(couponList, CouponDO::getUserId, CouponDO::getId); } @Override - public void takeCouponsByAdmin(Map giveCouponsMap, Long userId) { - if (CollUtil.isEmpty(giveCouponsMap)) { - return; + public List takeCouponsByAdmin(Map giveCoupons, Long userId) { + if (CollUtil.isEmpty(giveCoupons)) { + return Collections.emptyList(); } + List couponIds = new ArrayList<>(); // 循环发放 - for (Map.Entry entry : giveCouponsMap.entrySet()) { + for (Map.Entry entry : giveCoupons.entrySet()) { try { for (int i = 0; i < entry.getValue(); i++) { - getSelf().takeCoupon(entry.getKey(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.ADMIN); + Map> userCouponIdsMap = getSelf().takeCoupon(entry.getKey(), CollUtil.newHashSet(userId), + CouponTakeTypeEnum.ADMIN); + findAndThen(userCouponIdsMap, userId, couponIds::addAll); } } catch (Exception e) { log.error("[takeCouponsByAdmin][coupon({}) 优惠券发放失败]", entry, e); } } + return couponIds; } @Override - public void invalidateCouponsByAdmin(Map giveCouponsMap, Long userId) { + public void invalidateCouponsByAdmin(List giveCouponIds, Long userId) { // 循环收回 - for (Map.Entry entry : giveCouponsMap.entrySet()) { + for (Long couponId : giveCouponIds) { try { - for (int i = 0; i < entry.getValue(); i++) { - getSelf().takeBackCoupon(entry.getKey(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.ADMIN); - } + getSelf().takeBackCoupon(couponId, userId, CouponTakeTypeEnum.ADMIN); } catch (Exception e) { - log.error("[takeBackCouponsByAdmin][coupon({}) 收回优惠券失败]", entry, e); + log.error("[invalidateCouponsByAdmin][couponId({}) 收回优惠券失败]", couponId, e); } } } @@ -214,32 +189,36 @@ public class CouponServiceImpl implements CouponService { /** * 【管理员】收回优惠券 * - * @param templateId 模版编号 - * @param userIds 用户编号列表 + * @param couponId 模版编号 + * @param userId 用户编号 * @param takeType 领取方式 */ @Transactional(rollbackFor = Exception.class) - public void takeBackCoupon(Long templateId, Set userIds, CouponTakeTypeEnum takeType) { - CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(templateId); - // 1.1 校验模板 + public void takeBackCoupon(Long couponId, Long userId, CouponTakeTypeEnum takeType) { + // 1.1 校验优惠券 + CouponDO coupon = couponMapper.selectByIdAndUserId(couponId, userId); + if (coupon == null) { + throw exception(COUPON_NOT_EXISTS); + } + // 1.2 校验模板 + CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(coupon.getTemplateId()); if (couponTemplate == null) { throw exception(COUPON_TEMPLATE_NOT_EXISTS); } - // 1.2 校验领取方式 + // 1.3 校验领取方式 if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) { throw exception(COUPON_TEMPLATE_CANNOT_TAKE); } - // 2.1 过滤出还未使用的赠送的优惠券 - List couponList = couponMapper.selectListByTemplateIdAndUserIdAndTakeType(templateId, userIds, - takeType.getValue()); - List unUsedCouponList = filterList(couponList, item -> !CouponStatusEnum.USED.getStatus().equals(item.getStatus())); + // 2.1 校验优惠券是否已经使用,如若使用则先不管 + if (ObjUtil.equal(coupon.getStatus(), CouponStatusEnum.USED.getStatus())) { + return; + } // 2.2 减少优惠劵模板的领取数量 - couponTemplateService.updateCouponTemplateTakeCount(templateId, unUsedCouponList.size() * -1); - // 2.3 批量更新优惠劵状态 - couponMapper.updateById(convertList(unUsedCouponList, item -> new CouponDO().setId(item.getId()) - .setStatus(CouponStatusEnum.INVALID.getStatus()))); - + couponTemplateService.updateCouponTemplateTakeCount(couponTemplate.getId(), -1); + // 2.3 批量作废优惠劵 + // TODO @puhui999:捉摸了下,貌似搞成逻辑删除好了?不然好多地方的 status 都要做一些变动。可能未来加个 invalidateType 来标识,是管理后台删除,还是取消回收。或者优惠劵的 change log 可能更好。 + couponMapper.deleteById(couponId); } @Override @@ -251,24 +230,6 @@ public class CouponServiceImpl implements CouponService { } } - @Override - public Map getTakeCountMapByTemplateIds(Collection templateIds, Long userId) { - if (CollUtil.isEmpty(templateIds)) { - return Collections.emptyMap(); - } - return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds); - } - - @Override - public List getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) { - List list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId, - CouponStatusEnum.UNUSED.getStatus(), - matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds()); - // 兜底逻辑:如果 CouponExpireJob 未执行,status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤 - list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())); - return list; - } - @Override public int expireCoupon() { // 1. 查询待过期的优惠券 @@ -293,27 +254,6 @@ public class CouponServiceImpl implements CouponService { return count; } - @Override - public Map getUserCanCanTakeMap(Long userId, List templates) { - // 1. 未登录时,都显示可以领取 - Map userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true); - if (userId == null) { - return userCanTakeMap; - } - - // 2.1 过滤领取数量无限制的 - Set templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1); - // 2.2 检查用户领取的数量是否超过限制 - if (CollUtil.isNotEmpty(templateIds)) { - Map couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId); - for (CouponTemplateDO template : templates) { - Integer takeCount = couponTakeCountMap.get(template.getId()); - userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount()); - } - } - return userCanTakeMap; - } - /** * 过期单个优惠劵 * @@ -385,11 +325,84 @@ public class CouponServiceImpl implements CouponService { userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount()); } + //======================= 查询相关 ======================= + + @Override + public Long getUnusedCouponCount(Long userId) { + return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus()); + } + + @Override + public PageResult getCouponPage(CouponPageReqVO pageReqVO) { + // 获得用户编号 + if (StrUtil.isNotEmpty(pageReqVO.getNickname())) { + List users = memberUserApi.getUserListByNickname(pageReqVO.getNickname()); + if (CollUtil.isEmpty(users)) { + return PageResult.empty(); + } + pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId)); + } + // 分页查询 + return couponMapper.selectPage(pageReqVO); + } + + @Override + public List getCouponList(Long userId, Integer status) { + return couponMapper.selectListByUserIdAndStatus(userId, status); + } + + @Override + public Map getTakeCountMapByTemplateIds(Collection templateIds, Long userId) { + if (CollUtil.isEmpty(templateIds)) { + return Collections.emptyMap(); + } + return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds); + } + + @Override + public List getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) { + List list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId, + CouponStatusEnum.UNUSED.getStatus(), + matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds()); + // 兜底逻辑:如果 CouponExpireJob 未执行,status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤 + list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())); + return list; + } + + @Override + public Map getUserCanCanTakeMap(Long userId, List templates) { + // 1. 未登录时,都显示可以领取 + Map userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true); + if (userId == null) { + return userCanTakeMap; + } + + // 2.1 过滤领取数量无限制的 + Set templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1); + // 2.2 检查用户领取的数量是否超过限制 + if (CollUtil.isNotEmpty(templateIds)) { + Map couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId); + for (CouponTemplateDO template : templates) { + Integer takeCount = couponTakeCountMap.get(template.getId()); + userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount()); + } + } + return userCanTakeMap; + } + @Override public CouponDO getCoupon(Long userId, Long id) { return couponMapper.selectByIdAndUserId(id, userId); } + private CouponDO validateCouponExists(Long id) { + CouponDO coupon = couponMapper.selectById(id); + if (coupon == null) { + throw exception(COUPON_NOT_EXISTS); + } + return coupon; + } + /** * 获得自身的代理对象,解决 AOP 生效问题 * @@ -398,4 +411,5 @@ public class CouponServiceImpl implements CouponService { private CouponServiceImpl getSelf() { return SpringUtil.getBean(getClass()); } + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java index 82b6d6117..1409561d5 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.order; import cn.iocoder.yudao.framework.common.enums.TerminalEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO; import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; @@ -18,6 +19,7 @@ import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.*; import java.time.LocalDateTime; +import java.util.List; import java.util.Map; /** @@ -294,17 +296,23 @@ public class TradeOrderDO extends BaseDO { */ private Integer vipPrice; - // TODO @puhui999:项了下,貌似这里存储 List giveCouponIds 更合适。因为优惠劵赠送到最后是对应的编号,然后从而进行取消? /** * 赠送的优惠劵 * * key: 优惠劵编号 * value:对应的优惠券数量 * - * 目的:用于后续取消或者售后订单时,需要扣减赠送 + * 目的:用于订单支付后赠送优惠券 */ @TableField(typeHandler = JacksonTypeHandler.class) private Map giveCouponsMap; + /** + * 赠送的优惠劵编号 + * + * 目的:用于后续取消或者售后订单时,需要扣减赠送 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List giveCouponIds; /** * 秒杀活动编号 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java index 4508138ff..56b7cbc56 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java @@ -11,6 +11,8 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderI import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import jakarta.validation.constraints.NotNull; +import java.util.List; + /** * 交易订单【写】Service 接口 * @@ -194,4 +196,13 @@ public interface TradeOrderUpdateService { */ void cancelPaidOrder(Long userId, Long orderId, Integer cancelType); + /** + * 更新下单赠送的优惠券编号到订单 + * + * @param userId 用户编号 + * @param orderId 订单编号 + * @param giveCouponIds 赠送的优惠券编号列表 + */ + void updateOrderGiveCouponIds(Long userId, Long orderId, List giveCouponIds); + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index c9c1e685b..bdae8f227 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -202,7 +202,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); order.setUserIp(getClientIP()).setTerminal(getTerminal()); // 使用 + 赠送优惠券 - order.setGiveCouponsMap(calculateRespBO.getGiveCouponsMap()); + order.setGiveCouponsMap(calculateRespBO.getGiveCoupons()); // 支付 + 退款信息 order.setAdjustPrice(0).setPayStatus(false); order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); @@ -890,6 +890,22 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { .setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice()));// 价格信息 } + @Override + public void updateOrderGiveCouponIds(Long userId, Long orderId, List giveCouponIds) { + // 1.1 检验订单存在 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + // 1.2 校验订单是否支付 + if (!order.getPayStatus()) { + throw exception(ORDER_CANCEL_PAID_FAIL, "已支付"); + } + + // 2. 更新订单赠送的优惠券编号列表 + tradeOrderMapper.updateById(new TradeOrderDO().setId(orderId).setGiveCouponIds(giveCouponIds)); + } + /** * 创建单个订单的评论 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java index 3b1df5e0e..3a98a6c9e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java @@ -5,7 +5,10 @@ import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; +import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.util.List; @@ -18,6 +21,12 @@ import java.util.List; @Component public class TradeCouponOrderHandler implements TradeOrderHandler { + @Resource + @Lazy // 延迟加载,避免循环依赖 + private TradeOrderUpdateService orderUpdateService; + @Resource + private TradeOrderQueryService orderQueryService; + @Resource private CouponApi couponApi; @@ -37,7 +46,11 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { return; } // 赠送优惠券 - couponApi.takeCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); + List couponIds = couponApi.takeCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); + if (CollUtil.isEmpty(couponIds)) { + return; + } + orderUpdateService.updateOrderGiveCouponIds(order.getUserId(), order.getId(), couponIds); } @Override @@ -48,10 +61,10 @@ public class TradeCouponOrderHandler implements TradeOrderHandler { couponApi.returnUsedCoupon(order.getCouponId()); } // 情况二:收回赠送的优惠券 - if (CollUtil.isEmpty(order.getGiveCouponsMap())) { + if (CollUtil.isEmpty(order.getGiveCouponIds())) { return; } - couponApi.invalidateCouponsByAdmin(order.getGiveCouponsMap(), order.getUserId()); + couponApi.invalidateCouponsByAdmin(order.getGiveCouponIds(), order.getUserId()); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java index e53613d26..68fa58b37 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java @@ -79,7 +79,7 @@ public class TradePriceCalculateRespBO { * key: 优惠劵编号,value:对应的优惠券数量 * 目的:用于后续取消或者售后订单时,需要扣减赠送 */ - private Map giveCouponsMap; + private Map giveCoupons; /** * 订单价格 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index 6fa639c5a..195ef8718 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java @@ -32,7 +32,7 @@ public class TradePriceCalculatorHelper { List spuList, List skuList) { // 创建 PriceCalculateRespDTO 对象 TradePriceCalculateRespBO result = new TradePriceCalculateRespBO(); - result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCouponsMap(new LinkedHashMap<>()); + result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCoupons(new LinkedHashMap<>()); // 创建它的 OrderItem 属性 result.setItems(new ArrayList<>(param.getItems().size())); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index 6b333df47..f62b65eb9 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -93,7 +93,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator TradePriceCalculatorHelper.recountAllPrice(result); // 4.1 记录赠送的积分 - if (Boolean.TRUE.equals(rule.getGivePoint())) { + if (rule.getPoint() != null && rule.getPoint() > 0) { List dividePoints = TradePriceCalculatorHelper.dividePrice(orderItems, rule.getPoint()); for (int i = 0; i < orderItems.size(); i++) { // 商品可能赠送了积分,所以这里要加上 @@ -107,13 +107,13 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator result.setFreeDelivery(true); } // 4.3 记录赠送的优惠券 - if (Boolean.TRUE.equals(rule.getGiveCoupon())) { - for (Map.Entry entry : rule.getGiveCouponsMap().entrySet()) { - Map giveCouponsMap = result.getGiveCouponsMap(); - if (giveCouponsMap.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券 - result.setGiveCouponsMap(rule.getGiveCouponsMap()); + if (CollUtil.isNotEmpty(rule.getGiveCoupons())) { + for (Map.Entry entry : rule.getGiveCoupons().entrySet()) { + Map giveCoupons = result.getGiveCoupons(); + if (giveCoupons.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券 + result.setGiveCoupons(rule.getGiveCoupons()); } else { // 情况二:别的满减活动送过同类优惠券,则直接增加数量 - giveCouponsMap.put(entry.getKey(), giveCouponsMap.get(entry.getKey()) + entry.getValue()); + giveCoupons.put(entry.getKey(), giveCoupons.get(entry.getKey()) + entry.getValue()); } } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java index 3ae34514d..f1f31e3c8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java @@ -49,7 +49,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() .setType(TradeOrderTypeEnum.NORMAL.getType()) .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()).setGiveCouponsMap(new LinkedHashMap<>()) + .setPromotions(new ArrayList<>()).setGiveCoupons(new LinkedHashMap<>()) .setItems(asList( new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) .setPrice(100).setSpuId(1L), @@ -68,16 +68,16 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest .setConditionType(PromotionConditionTypeEnum.PRICE.getType()) .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)) .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(20).setDiscountPrice(70) - .setGivePoint(false).setFreeDelivery(false)))), + .setFreeDelivery(false)))), randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号") .setConditionType(PromotionConditionTypeEnum.COUNT.getType()) .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L)) .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10) - .setGivePoint(true).setPoint(50).setFreeDelivery(false), - new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60).setGivePoint(true) + .setPoint(50).setFreeDelivery(false), + new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60) .setPoint(100).setFreeDelivery(false), // 最大可满足,因为是 4 个 new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100) - .setGivePoint(false).setFreeDelivery(false)))) + .setFreeDelivery(false)))) )); // 调用