mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-22 15:21:53 +08:00
Merge branch 'feature/mall_product' of https://gitee.com/zhijiantianya/ruoyi-vue-pro
This commit is contained in:
commit
8f86f65884
@ -17,7 +17,7 @@ import java.lang.annotation.*;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Constraint(
|
||||
validatedBy = InEnumValidator.class
|
||||
validatedBy = {InEnumValidator.class, InEnumCollectionValidator.class}
|
||||
)
|
||||
public @interface InEnum {
|
||||
|
||||
|
@ -0,0 +1,42 @@
|
||||
package cn.iocoder.yudao.framework.common.validation;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class InEnumCollectionValidator implements ConstraintValidator<InEnum, Collection<Integer>> {
|
||||
|
||||
private List<Integer> values;
|
||||
|
||||
@Override
|
||||
public void initialize(InEnum annotation) {
|
||||
IntArrayValuable[] values = annotation.value().getEnumConstants();
|
||||
if (values.length == 0) {
|
||||
this.values = Collections.emptyList();
|
||||
} else {
|
||||
this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Collection<Integer> list, ConstraintValidatorContext context) {
|
||||
// 校验通过
|
||||
if (CollUtil.containsAll(values, list)) {
|
||||
return true;
|
||||
}
|
||||
// 校验不通过,自定义提示语句(因为,注解上的 value 是枚举类,无法获得枚举类的实际值)
|
||||
context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
|
||||
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
|
||||
.replaceAll("\\{value}", CollUtil.join(list, ","))).addConstraintViolation(); // 重新添加错误提示语句
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,13 @@ public interface CouponApi {
|
||||
*/
|
||||
void useCoupon(@Valid CouponUseReqDTO useReqDTO);
|
||||
|
||||
/**
|
||||
* 退还已使用的优惠券
|
||||
*
|
||||
* @param id 优惠券编号
|
||||
*/
|
||||
void returnUsedCoupon(Long id);
|
||||
|
||||
/**
|
||||
* 校验优惠劵
|
||||
*
|
||||
|
@ -71,9 +71,9 @@ public class CouponRespDTO {
|
||||
*/
|
||||
private Integer productScope;
|
||||
/**
|
||||
* 商品 SPU 编号的数组
|
||||
* 商品范围编号的数组
|
||||
*/
|
||||
private List<Long> productSpuIds;
|
||||
private List<Long> productScopeValues;
|
||||
// ========== 使用规则 END ==========
|
||||
|
||||
// ========== 使用效果 BEGIN ==========
|
||||
|
@ -27,12 +27,17 @@ public interface ErrorCodeConstants {
|
||||
// ========== 优惠劵模板 1013004000 ==========
|
||||
ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1013004000, "优惠劵模板不存在");
|
||||
ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1013004001, "发放数量不能小于已领取数量({})");
|
||||
ErrorCode COUPON_TEMPLATE_NOT_ENOUGH = new ErrorCode(1013004002, "当前剩余数量不够领取");
|
||||
ErrorCode COUPON_TEMPLATE_USER_ALREADY_TAKE = new ErrorCode(1013004003, "用户已领取过此优惠券");
|
||||
ErrorCode COUPON_TEMPLATE_EXPIRED = new ErrorCode(1013004004, "优惠券已过期");
|
||||
ErrorCode COUPON_TEMPLATE_CANNOT_TAKE = new ErrorCode(1013004005, "领取方式不正确");
|
||||
|
||||
// ========== 优惠劵模板 1013005000 ==========
|
||||
// ========== 优惠劵 1013005000 ==========
|
||||
ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1013005000, "优惠券不存在");
|
||||
ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1013005001, "回收优惠劵失败,优惠劵已被使用");
|
||||
ErrorCode COUPON_STATUS_NOT_UNUSED = new ErrorCode(1013005002, "优惠劵不处于待使用状态");
|
||||
ErrorCode COUPON_VALID_TIME_NOT_NOW = new ErrorCode(1013005003, "优惠券不在使用时间范围内");
|
||||
ErrorCode COUPON_STATUS_NOT_USED = new ErrorCode(1013005004, "优惠劵不是已使用状态");
|
||||
|
||||
// ========== 满减送活动 1013006000 ==========
|
||||
ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1013006000, "满减送活动不存在");
|
||||
|
@ -15,8 +15,9 @@ import java.util.Arrays;
|
||||
@AllArgsConstructor
|
||||
public enum PromotionProductScopeEnum implements IntArrayValuable {
|
||||
|
||||
ALL(1, "全部商品参与"),
|
||||
SPU(2, "指定商品参与"),
|
||||
ALL(1, "通用卷"), // 全部商品
|
||||
SPU(2, "商品卷"), // 指定商品
|
||||
CATEGORY(3, "品类卷"), // 指定商品
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionProductScopeEnum::getScope).toArray();
|
||||
|
@ -15,8 +15,9 @@ import java.util.Arrays;
|
||||
@Getter
|
||||
public enum CouponTakeTypeEnum implements IntArrayValuable {
|
||||
|
||||
BY_USER(1, "直接领取"), // 用户可在首页、每日领劵直接领取
|
||||
BY_ADMIN(2, "指定发放"), // 后台指定会员赠送优惠劵
|
||||
USER(1, "直接领取"), // 用户可在首页、每日领劵直接领取
|
||||
ADMIN(2, "指定发放"), // 后台指定会员赠送优惠劵
|
||||
REGISTER(3, "新人券"), // 注册时自动领取
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CouponTakeTypeEnum::getValue).toArray();
|
||||
|
@ -28,6 +28,11 @@ public class CouponApiImpl implements CouponApi {
|
||||
useReqDTO.getOrderId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnUsedCoupon(Long id) {
|
||||
couponService.returnUsedCoupon(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouponRespDTO validateCoupon(CouponValidReqDTO validReqDTO) {
|
||||
CouponDO coupon = couponService.validCoupon(validReqDTO.getId(), validReqDTO.getUserId());
|
||||
|
@ -8,6 +8,7 @@ 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.CouponPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponSendReqVO;
|
||||
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;
|
||||
@ -21,7 +22,6 @@ import org.springframework.web.bind.annotation.*;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
@ -64,12 +64,20 @@ public class CouponController {
|
||||
if (CollUtil.isEmpty(pageResulVO.getList())) {
|
||||
return success(pageResulVO);
|
||||
}
|
||||
|
||||
// 读取用户信息,进行拼接
|
||||
Set<Long> userIds = convertSet(pageResult.getList(), CouponDO::getUserId);
|
||||
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
|
||||
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(convertSet(pageResult.getList(), CouponDO::getUserId));
|
||||
pageResulVO.getList().forEach(itemRespVO -> MapUtils.findAndThen(userMap, itemRespVO.getUserId(),
|
||||
userRespDTO -> itemRespVO.setNickname(userRespDTO.getNickname())));
|
||||
return success(pageResulVO);
|
||||
}
|
||||
|
||||
@PostMapping("/send")
|
||||
@Operation(summary = "发送优惠劵")
|
||||
@PreAuthorize("@ss.hasPermission('promotion:coupon:send')")
|
||||
public CommonResult<Boolean> sendCoupon(@Valid @RequestBody CouponSendReqVO reqVO) {
|
||||
couponService.takeCouponByAdmin(reqVO.getTemplateId(), reqVO.getUserIds());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -75,5 +75,4 @@ public class CouponTemplateController {
|
||||
PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage(pageVO);
|
||||
return success(CouponTemplateConvert.INSTANCE.convertPage(pageResult));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public class CouponBaseVO {
|
||||
// ========== 基本信息 BEGIN ==========
|
||||
@Schema(description = "优惠劵模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "优惠劵模板编号不能为空")
|
||||
private Integer templateId;
|
||||
private Long templateId;
|
||||
|
||||
@Schema(description = "优惠劵名", requiredMode = Schema.RequiredMode.REQUIRED, example = "春节送送送")
|
||||
@NotNull(message = "优惠劵名不能为空")
|
||||
@ -67,8 +67,8 @@ public class CouponBaseVO {
|
||||
@InEnum(PromotionProductScopeEnum.class)
|
||||
private Integer productScope;
|
||||
|
||||
@Schema(description = "商品 SPU 编号的数组", example = "1,3")
|
||||
private List<Long> productSpuIds;
|
||||
@Schema(description = "商品范围编号的数组", example = "1,3")
|
||||
private List<Long> productScopeValues;
|
||||
// ========== 使用规则 END ==========
|
||||
|
||||
// ========== 使用效果 BEGIN ==========
|
||||
|
@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Set;
|
||||
|
||||
@Schema(description = "管理后台 - 优惠劵发放 Request VO")
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
public class CouponSendReqVO {
|
||||
|
||||
@Schema(description = "优惠劵模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "优惠劵模板编号不能为空")
|
||||
private Long templateId;
|
||||
|
||||
@Schema(description = "用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
|
||||
@NotEmpty(message = "用户编号列表不能为空")
|
||||
private Set<Long> userIds;
|
||||
|
||||
}
|
@ -54,8 +54,8 @@ public class CouponTemplateBaseVO {
|
||||
@InEnum(PromotionProductScopeEnum.class)
|
||||
private Integer productScope;
|
||||
|
||||
@Schema(description = "商品 SPU 编号的数组", example = "1,3")
|
||||
private List<Long> productSpuIds;
|
||||
@Schema(description = "商品范围编号的数组", example = "[1, 3]")
|
||||
private List<Long> productScopeValues;
|
||||
|
||||
@Schema(description = "生效日期类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "生效日期类型不能为空")
|
||||
@ -95,11 +95,11 @@ public class CouponTemplateBaseVO {
|
||||
@Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
|
||||
private Integer discountLimitPrice;
|
||||
|
||||
@AssertTrue(message = "商品 SPU 编号的数组不能为空")
|
||||
@AssertTrue(message = "商品范围编号的数组不能为空")
|
||||
@JsonIgnore
|
||||
public boolean isProductSpuIdsValid() {
|
||||
public boolean isProductScopeValuesValid() {
|
||||
return Objects.equals(productScope, PromotionProductScopeEnum.ALL.getScope()) // 全部范围时,可以为空
|
||||
|| CollUtil.isNotEmpty(productSpuIds);
|
||||
|| CollUtil.isNotEmpty(productScopeValues);
|
||||
}
|
||||
|
||||
@AssertTrue(message = "生效开始时间不能为空")
|
||||
|
@ -1,6 +1,8 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@ -8,6 +10,7 @@ import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@ -30,4 +33,8 @@ public class CouponTemplatePageReqVO extends PageParam {
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
@Schema(description = "可以领取的类型", example = "[1,2, 3]")
|
||||
@InEnum(value = CouponTakeTypeEnum.class, message = "可以领取的类型,必须是 {value}")
|
||||
private List<Integer> canTakeTypes;
|
||||
|
||||
}
|
||||
|
@ -28,8 +28,8 @@ public class AppCouponTemplateRespVO {
|
||||
// @InEnum(PromotionProductScopeEnum.class)
|
||||
// private Integer productScope;
|
||||
//
|
||||
// @Schema(description = "商品 SPU 编号的数组", example = "1,3")
|
||||
// private List<Long> productSpuIds;
|
||||
// @Schema(description = "商品范围编号的数组", example = "1,3")
|
||||
// private List<Long> productScopeValues;
|
||||
|
||||
@Schema(description = "生效日期类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer validityType;
|
||||
|
@ -4,9 +4,14 @@ 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.dal.dataobject.coupon.CouponDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 优惠劵 Convert
|
||||
*
|
||||
@ -21,4 +26,27 @@ public interface CouponConvert {
|
||||
|
||||
CouponRespDTO convert(CouponDO bean);
|
||||
|
||||
default CouponDO convert(CouponTemplateDO template, Long userId) {
|
||||
CouponDO couponDO = new CouponDO()
|
||||
.setTemplateId(template.getId())
|
||||
.setName(template.getName())
|
||||
.setTakeType(template.getTakeType())
|
||||
.setUsePrice(template.getUsePrice())
|
||||
.setProductScope(template.getProductScope())
|
||||
.setProductScopeValues(template.getProductScopeValues())
|
||||
.setDiscountType(template.getDiscountType())
|
||||
.setDiscountPercent(template.getDiscountPercent())
|
||||
.setDiscountPrice(template.getDiscountPrice())
|
||||
.setDiscountLimitPrice(template.getDiscountLimitPrice())
|
||||
.setStatus(CouponStatusEnum.UNUSED.getStatus())
|
||||
.setUserId(userId);
|
||||
if (CouponTemplateValidityTypeEnum.DATE.getType().equals(template.getValidityType())) {
|
||||
couponDO.setValidStartTime(template.getValidStartTime());
|
||||
couponDO.setValidEndTime(template.getValidEndTime());
|
||||
} else if (CouponTemplateValidityTypeEnum.TERM.getType().equals(template.getValidityType())) {
|
||||
couponDO.setValidStartTime(LocalDateTime.now().plusDays(template.getFixedStartTerm()));
|
||||
couponDO.setValidEndTime(LocalDateTime.now().plusDays(template.getFixedEndTerm()));
|
||||
}
|
||||
return couponDO;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public class CouponDO extends BaseDO {
|
||||
*
|
||||
* 关联 {@link CouponTemplateDO#getId()}
|
||||
*/
|
||||
private Integer templateId;
|
||||
private Long templateId;
|
||||
/**
|
||||
* 优惠劵名
|
||||
*
|
||||
@ -89,12 +89,12 @@ public class CouponDO extends BaseDO {
|
||||
*/
|
||||
private Integer productScope;
|
||||
/**
|
||||
* 商品 SPU 编号的数组
|
||||
* 商品范围编号的数组
|
||||
*
|
||||
* 冗余 {@link CouponTemplateDO#getProductSpuIds()}
|
||||
* 冗余 {@link CouponTemplateDO#getProductScopeValues()}
|
||||
*/
|
||||
@TableField(typeHandler = LongListTypeHandler.class)
|
||||
private List<Long> productSpuIds;
|
||||
private List<Long> productScopeValues;
|
||||
// ========== 使用规则 END ==========
|
||||
|
||||
// ========== 使用效果 BEGIN ==========
|
||||
|
@ -85,10 +85,10 @@ public class CouponTemplateDO extends BaseDO {
|
||||
*/
|
||||
private Integer productScope;
|
||||
/**
|
||||
* 商品 SPU 编号的数组
|
||||
* 商品范围编号的数组
|
||||
*/
|
||||
@TableField(typeHandler = LongListTypeHandler.class)
|
||||
private List<Long> productSpuIds;
|
||||
private List<Long> productScopeValues;
|
||||
/**
|
||||
* 生效日期类型
|
||||
*
|
||||
|
@ -55,4 +55,11 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
|
||||
.eq(CouponDO::getStatus, status));
|
||||
}
|
||||
|
||||
default List<CouponDO> selectListByTemplateIdAndUserId(Long templateId, Collection<Long> userIds) {
|
||||
return selectList(new LambdaQueryWrapperX<CouponDO>()
|
||||
.eq(CouponDO::getTemplateId, templateId)
|
||||
.in(CouponDO::getUserId, userIds)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,19 @@
|
||||
package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 优惠劵模板 Mapper
|
||||
*
|
||||
@ -17,11 +23,23 @@ import org.apache.ibatis.annotations.Param;
|
||||
public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
|
||||
|
||||
default PageResult<CouponTemplateDO> selectPage(CouponTemplatePageReqVO reqVO) {
|
||||
// 构建可领取的查询条件, 好啰嗦 ( ╯-_-)╯┴—┴
|
||||
Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = null;
|
||||
if (CollUtil.isNotEmpty(reqVO.getCanTakeTypes())) {
|
||||
canTakeConsumer = w ->
|
||||
w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
|
||||
.in(CouponTemplateDO::getTakeType, reqVO.getCanTakeTypes()) // 2. 领取方式一致
|
||||
.and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime) // 3. 未过期
|
||||
.or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
|
||||
.apply(" take_count < total_count "); // 4. 剩余数量大于 0
|
||||
}
|
||||
// 执行分页查询
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<CouponTemplateDO>()
|
||||
.likeIfPresent(CouponTemplateDO::getName, reqVO.getName())
|
||||
.eqIfPresent(CouponTemplateDO::getStatus, reqVO.getStatus())
|
||||
.eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType())
|
||||
.betweenIfPresent(CouponTemplateDO::getCreateTime, reqVO.getCreateTime())
|
||||
.and(canTakeConsumer != null, canTakeConsumer)
|
||||
.orderByDesc(CouponTemplateDO::getId));
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.coupon;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
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.dal.dataobject.coupon.CouponDO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 优惠劵 Service 接口
|
||||
@ -51,6 +54,13 @@ public interface CouponService {
|
||||
*/
|
||||
void useCoupon(Long id, Long userId, Long orderId);
|
||||
|
||||
/**
|
||||
* 退还已使用的优惠券
|
||||
*
|
||||
* @param id 优惠券编号
|
||||
*/
|
||||
void returnUsedCoupon(Long id);
|
||||
|
||||
/**
|
||||
* 回收优惠劵
|
||||
*
|
||||
@ -75,4 +85,43 @@ public interface CouponService {
|
||||
*/
|
||||
Long getUnusedCouponCount(Long userId);
|
||||
|
||||
/**
|
||||
* 领取优惠券
|
||||
*
|
||||
* @param templateId 优惠券模板编号
|
||||
* @param userIds 用户编号列表
|
||||
* @param takeType 领取方式
|
||||
*/
|
||||
void takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType);
|
||||
|
||||
/**
|
||||
* 【管理员】给用户发送优惠券
|
||||
*
|
||||
* @param templateId 优惠券模板编号
|
||||
* @param userIds 用户编号列表
|
||||
*/
|
||||
default void takeCouponByAdmin(Long templateId, Set<Long> userIds) {
|
||||
takeCoupon(templateId, userIds, CouponTakeTypeEnum.ADMIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【会员】领取优惠券
|
||||
*
|
||||
* @param templateId 优惠券模板编号
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
default void takeCouponByUser(Long templateId, Long userId) {
|
||||
takeCoupon(templateId, CollUtil.newHashSet(userId), CouponTakeTypeEnum.USER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【系统】给用户发送新人券
|
||||
*
|
||||
* @param templateId 优惠券模板编号
|
||||
* @param userId 用户编号列表
|
||||
*/
|
||||
default void takeCouponByRegister(Long templateId, Long userId) {
|
||||
takeCoupon(templateId, CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
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.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
@ -9,9 +11,13 @@ 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.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.coupon.CouponStatusEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@ -19,9 +25,12 @@ import org.springframework.validation.annotation.Validated;
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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.convertList;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
@ -84,6 +93,7 @@ public class CouponServiceImpl implements CouponService {
|
||||
public void useCoupon(Long id, Long userId, Long orderId) {
|
||||
// 校验优惠劵
|
||||
validCoupon(id, userId);
|
||||
|
||||
// 更新状态
|
||||
int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
|
||||
new CouponDO().setStatus(CouponStatusEnum.USED.getStatus())
|
||||
@ -93,6 +103,31 @@ public class CouponServiceImpl implements CouponService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnUsedCoupon(Long id) {
|
||||
// 校验存在
|
||||
CouponDO coupon = couponMapper.selectById(id);
|
||||
if (coupon == null) {
|
||||
throw exception(COUPON_NOT_EXISTS);
|
||||
}
|
||||
// 校验状态
|
||||
if (ObjectUtil.notEqual(coupon.getTemplateId(), CouponStatusEnum.USED.getStatus())) {
|
||||
throw exception(COUPON_STATUS_NOT_USED);
|
||||
}
|
||||
|
||||
// 退还
|
||||
Integer status = LocalDateTimeUtils.beforeNow(coupon.getValidEndTime())
|
||||
? CouponStatusEnum.EXPIRE.getStatus() // 退还时可能已经过期了
|
||||
: CouponStatusEnum.UNUSED.getStatus();
|
||||
int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
|
||||
new CouponDO().setStatus(status));
|
||||
if (updateCount == 0) {
|
||||
throw exception(COUPON_STATUS_NOT_USED);
|
||||
}
|
||||
|
||||
// TODO 增加优惠券变动记录?
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteCoupon(Long id) {
|
||||
@ -125,4 +160,72 @@ public class CouponServiceImpl implements CouponService {
|
||||
return couponMapper.selectCountByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void takeCoupon(Long templateId, Set<Long> userIds, CouponTakeTypeEnum takeType) {
|
||||
CouponTemplateDO template = couponTemplateService.getCouponTemplate(templateId);
|
||||
// 1. 过滤掉达到领取限制的用户
|
||||
removeTakeLimitUser(userIds, template);
|
||||
// 2. 校验优惠劵是否可以领取
|
||||
validateCouponTemplateCanTake(template, userIds, takeType);
|
||||
|
||||
// 3. 批量保存优惠劵
|
||||
couponMapper.insertBatch(convertList(userIds, userId -> CouponConvert.INSTANCE.convert(template, userId)));
|
||||
|
||||
// 3. 增加优惠劵模板的领取数量
|
||||
couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验优惠券是否可以领取
|
||||
*
|
||||
* @param couponTemplate 优惠券模板
|
||||
* @param userIds 领取人列表
|
||||
* @param takeType 领取方式
|
||||
*/
|
||||
private void validateCouponTemplateCanTake(CouponTemplateDO couponTemplate, Set<Long> userIds, CouponTakeTypeEnum takeType) {
|
||||
// 如果所有用户都领取过,则抛出异常
|
||||
if (CollUtil.isEmpty(userIds)) {
|
||||
throw exception(COUPON_TEMPLATE_USER_ALREADY_TAKE);
|
||||
}
|
||||
|
||||
// 校验模板
|
||||
if (couponTemplate == null) {
|
||||
throw exception(COUPON_TEMPLATE_NOT_EXISTS);
|
||||
}
|
||||
// 校验剩余数量
|
||||
if (couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
|
||||
throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
|
||||
}
|
||||
// 校验"固定日期"的有效期类型是否过期
|
||||
if (CouponTemplateValidityTypeEnum.DATE.getType().equals(couponTemplate.getValidityType())) {
|
||||
if (LocalDateTimeUtils.beforeNow(couponTemplate.getValidEndTime())) {
|
||||
throw exception(COUPON_TEMPLATE_EXPIRED);
|
||||
}
|
||||
}
|
||||
// 校验领取方式
|
||||
if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) {
|
||||
throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤掉达到领取上线的用户
|
||||
*
|
||||
* @param userIds 用户编号数组
|
||||
* @param couponTemplate 优惠劵模版
|
||||
*/
|
||||
private void removeTakeLimitUser(Set<Long> userIds, CouponTemplateDO couponTemplate) {
|
||||
if (couponTemplate.getTakeLimitCount() <= 0) {
|
||||
return;
|
||||
}
|
||||
// 查询已领过券的用户
|
||||
List<CouponDO> alreadyTakeCoupons = couponMapper.selectListByTemplateIdAndUserId(couponTemplate.getId(), userIds);
|
||||
if (CollUtil.isEmpty(alreadyTakeCoupons)) {
|
||||
return;
|
||||
}
|
||||
// 移除达到领取限制的用户
|
||||
Map<Long, Integer> userTakeCountMap = CollStreamUtil.groupBy(alreadyTakeCoupons, CouponDO::getUserId, Collectors.summingInt(c -> 1));
|
||||
userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public interface CouponTemplateService {
|
||||
/**
|
||||
* 更新优惠劵模板的状态
|
||||
*
|
||||
* @param id 编号
|
||||
* @param id 编号
|
||||
* @param status 状态
|
||||
*/
|
||||
void updateCouponTemplateStatus(Long id, Integer status);
|
||||
@ -64,7 +64,7 @@ public interface CouponTemplateService {
|
||||
/**
|
||||
* 更新优惠劵模板的领取数量
|
||||
*
|
||||
* @param id 优惠劵模板编号
|
||||
* @param id 优惠劵模板编号
|
||||
* @param incrCount 增加数量
|
||||
*/
|
||||
void updateCouponTemplateTakeCount(Long id, int incrCount);
|
||||
|
@ -14,7 +14,8 @@ import org.springframework.validation.annotation.Validated;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL;
|
||||
|
||||
/**
|
||||
* 优惠劵模板 Service 实现类
|
||||
|
@ -33,6 +33,7 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1011000022, "交易订单发货失败,拼团未成功");
|
||||
ErrorCode ORDER_DELIVERY_FAIL_BARGAIN_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1011000023, "交易订单发货失败,砍价未成功");
|
||||
ErrorCode ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS = new ErrorCode(1011000024, "交易订单发货失败,发货类型不是快递");
|
||||
ErrorCode ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID = new ErrorCode(1011000025, "交易订单取消失败,订单不是【待支付】状态");
|
||||
|
||||
// ========== After Sale 模块 1011000100 ==========
|
||||
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1011000100, "售后单不存在");
|
||||
|
@ -27,6 +27,8 @@ import org.springframework.web.bind.annotation.*;
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.validation.Valid;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -82,6 +84,24 @@ public class TradeAfterSaleController {
|
||||
MemberUserRespDTO user = memberUserApi.getUser(afterSale.getUserId());
|
||||
// 获取售后日志
|
||||
List<TradeAfterSaleLogRespDTO> logs = afterSaleLogService.getLog(afterSale.getId());
|
||||
// TODO 方便测试看效果,review 后移除
|
||||
if (logs == null) {
|
||||
logs = new ArrayList<>();
|
||||
}
|
||||
for (int i = 1; i <= 6; i++) {
|
||||
TradeAfterSaleLogRespDTO respVO = new TradeAfterSaleLogRespDTO();
|
||||
respVO.setId((long) i);
|
||||
respVO.setUserId((long) i);
|
||||
respVO.setUserType(1);
|
||||
respVO.setAfterSaleId(id);
|
||||
respVO.setOrderId((long) i);
|
||||
respVO.setOrderItemId((long) i);
|
||||
respVO.setBeforeStatus((i - 1) * 10);
|
||||
respVO.setAfterStatus(i * 10);
|
||||
respVO.setContent("66+6");
|
||||
respVO.setCreateTime(LocalDateTime.now());
|
||||
logs.add(respVO);
|
||||
}
|
||||
return success(TradeAfterSaleConvert.INSTANCE.convert(afterSale, order, orderItems, user, logs));
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,6 @@ import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "管理后台 - 交易订单")
|
||||
@RestController
|
||||
@ -70,7 +69,7 @@ public class TradeOrderController {
|
||||
TradeOrderDO order = tradeOrderQueryService.getOrder(id);
|
||||
// 查询订单项
|
||||
List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(id);
|
||||
|
||||
// orderLog
|
||||
// 拼接数据
|
||||
MemberUserRespDTO user = memberUserApi.getUser(order.getUserId());
|
||||
return success(TradeOrderConvert.INSTANCE.convert(order, orderItems, user));
|
||||
@ -82,7 +81,7 @@ public class TradeOrderController {
|
||||
@PreAuthorize("@ss.hasPermission('trade:order:query')")
|
||||
public CommonResult<List<?>> getOrderExpressTrackList(@RequestParam("id") Long id) {
|
||||
return success(TradeOrderConvert.INSTANCE.convertList02(
|
||||
tradeOrderQueryService.getExpressTrackList(id, getLoginUserId())));
|
||||
tradeOrderQueryService.getExpressTrackList(id)));
|
||||
}
|
||||
|
||||
@PutMapping("/delivery")
|
||||
@ -117,11 +116,4 @@ public class TradeOrderController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// TODO @puhui999 订单物流详情
|
||||
// TODO @puhui999 【前台】订单取消
|
||||
// TODO @puhui999 【后台】订单取消
|
||||
// TODO @puhui999 【前台】订单核销
|
||||
// TODO @puhui999 【前台】订单删除
|
||||
// TODO @puhui999 【后台】订单统计
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.Prod
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 交易订单的详情 Response VO")
|
||||
@ -24,6 +25,27 @@ public class TradeOrderDetailRespVO extends TradeOrderBaseVO {
|
||||
*/
|
||||
private MemberUserRespVO user;
|
||||
|
||||
/**
|
||||
* TODO 订单操作日志, 先模拟一波;返回 logs,简洁,然后复数哈
|
||||
*/
|
||||
private List<OrderLog> orderLog;
|
||||
|
||||
// TODO @puhui999:swagger 注解
|
||||
@Data
|
||||
public static class OrderLog {
|
||||
|
||||
/**
|
||||
* 内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "管理后台 - 交易订单的详情的订单项目")
|
||||
@Data
|
||||
public static class Item extends TradeOrderItemBaseVO {
|
||||
|
@ -61,5 +61,5 @@ public class TradeOrderPageReqVO extends PageParam {
|
||||
@Schema(description = "订单来源", example = "10")
|
||||
@InEnum(value = TerminalEnum.class, message = "订单来源 {value}")
|
||||
private Integer terminal;
|
||||
|
||||
// TODO 添加配送方式筛选
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
@Validated
|
||||
public class AppDeliverPickUpStoreController {
|
||||
|
||||
// TODO 待实现[门店自提]:如果 latitude、longitude 非空,计算经纬度,并排序。计算的库,可以使用 hutool 的 DistanceUtil 计算。
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获得自提门店列表")
|
||||
public CommonResult<List<AppDeliveryPickUpStoreRespVO>> getDeliveryPickUpStoreList(
|
||||
@ -50,6 +51,7 @@ public class AppDeliverPickUpStoreController {
|
||||
return success(list);
|
||||
}
|
||||
|
||||
// TODO 待实现[门店自提]:
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得自提门店")
|
||||
@Parameter(name = "id", description = "门店编号")
|
||||
|
@ -49,9 +49,6 @@ public class AppTradeOrderController {
|
||||
@Resource
|
||||
private DeliveryExpressService deliveryExpressService;
|
||||
|
||||
@Resource
|
||||
private ProductPropertyValueApi productPropertyValueApi;
|
||||
|
||||
@Resource
|
||||
private TradeOrderProperties tradeOrderProperties;
|
||||
|
||||
@ -78,6 +75,7 @@ public class AppTradeOrderController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// TODO @芋艿:如果拼团活动、秒杀活动、砍价活动时,是不是要额外在返回活动之类的信息;
|
||||
@GetMapping("/get-detail")
|
||||
@Operation(summary = "获得交易订单")
|
||||
@Parameter(name = "id", description = "交易订单编号")
|
||||
@ -93,6 +91,7 @@ public class AppTradeOrderController {
|
||||
// 查询物流公司
|
||||
DeliveryExpressDO express = order.getLogisticsId() != null && order.getLogisticsId() > 0 ?
|
||||
deliveryExpressService.getDeliveryExpress(order.getLogisticsId()) : null;
|
||||
// TODO @puhui999:如果门店自提,信息的拼接;
|
||||
// 最终组合
|
||||
return success(TradeOrderConvert.INSTANCE.convert02(order, orderItems, tradeOrderProperties, express));
|
||||
}
|
||||
@ -141,7 +140,7 @@ public class AppTradeOrderController {
|
||||
@PutMapping("/receive")
|
||||
@Operation(summary = "确认交易订单收货")
|
||||
@Parameter(name = "id", description = "交易订单编号")
|
||||
public CommonResult<Boolean> takeOrder(@RequestParam("id") Long id) {
|
||||
public CommonResult<Boolean> receiveOrder(@RequestParam("id") Long id) {
|
||||
tradeOrderUpdateService.receiveOrder(getLoginUserId(), id);
|
||||
return success(true);
|
||||
}
|
||||
@ -150,7 +149,7 @@ public class AppTradeOrderController {
|
||||
@Operation(summary = "取消交易订单")
|
||||
@Parameter(name = "id", description = "交易订单编号")
|
||||
public CommonResult<Boolean> cancelOrder(@RequestParam("id") Long id) {
|
||||
// TODO @芋艿:未实现,mock 用
|
||||
tradeOrderUpdateService.cancelOrder(getLoginUserId(), id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
|
@ -72,16 +72,18 @@ public interface TradeAfterSaleConvert {
|
||||
|
||||
default TradeAfterSaleDetailRespVO convert(TradeAfterSaleDO afterSale, TradeOrderDO order, List<TradeOrderItemDO> orderItems,
|
||||
MemberUserRespDTO user, List<TradeAfterSaleLogRespDTO> logs) {
|
||||
TradeAfterSaleDetailRespVO respVO = convert(afterSale, orderItems, convertList1(logs));
|
||||
TradeAfterSaleDetailRespVO respVO = convert(afterSale, orderItems);
|
||||
// 处理用户信息
|
||||
respVO.setUser(convert(user));
|
||||
// 处理订单信息
|
||||
respVO.setOrder(convert(order));
|
||||
// 处理售后日志
|
||||
respVO.setAfterSaleLog(convertList1(logs));
|
||||
return respVO;
|
||||
}
|
||||
List<TradeAfterSaleLogRespVO> convertList1(List<TradeAfterSaleLogRespDTO> list);
|
||||
@Mapping(target = "id", source = "afterSale.id")
|
||||
TradeAfterSaleDetailRespVO convert(TradeAfterSaleDO afterSale, List<TradeOrderItemDO> orderItems, List<TradeAfterSaleLogRespVO> logs);
|
||||
TradeAfterSaleDetailRespVO convert(TradeAfterSaleDO afterSale, List<TradeOrderItemDO> orderItems);
|
||||
TradeOrderBaseVO convert(TradeOrderDO order);
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package cn.iocoder.yudao.module.trade.convert.order;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
@ -37,8 +36,10 @@ import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
|
||||
@ -86,7 +87,14 @@ public interface TradeOrderConvert {
|
||||
default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) {
|
||||
return new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(list));
|
||||
}
|
||||
|
||||
default ProductSkuUpdateStockReqDTO convertNegative(List<TradeOrderItemDO> list) {
|
||||
List<ProductSkuUpdateStockReqDTO.Item> items = TradeOrderConvert.INSTANCE.convertList(list);
|
||||
items.forEach(item -> item.setIncrCount(-item.getIncrCount()));
|
||||
return new ProductSkuUpdateStockReqDTO(items);
|
||||
}
|
||||
List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
|
||||
|
||||
@Mappings({
|
||||
@Mapping(source = "skuId", target = "id"),
|
||||
@Mapping(source = "count", target = "incrCount"),
|
||||
@ -137,6 +145,15 @@ public interface TradeOrderConvert {
|
||||
orderVO.setReceiverAreaName(AreaUtils.format(order.getReceiverAreaId()));
|
||||
// 处理用户信息
|
||||
orderVO.setUser(convert(user));
|
||||
// TODO puhui999:模拟订单操作日志
|
||||
ArrayList<TradeOrderDetailRespVO.OrderLog> orderLogs = new ArrayList<>();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
TradeOrderDetailRespVO.OrderLog orderLog = new TradeOrderDetailRespVO.OrderLog();
|
||||
orderLog.setContent("订单操作" + i);
|
||||
orderLog.setCreateTime(LocalDateTime.now());
|
||||
orderLogs.add(orderLog);
|
||||
}
|
||||
orderVO.setOrderLog(orderLogs);
|
||||
return orderVO;
|
||||
}
|
||||
|
||||
|
@ -143,6 +143,8 @@ public class TradeOrderItemDO extends BaseDO {
|
||||
* 对应 taobao 的 trade.point_fee 字段
|
||||
*/
|
||||
private Integer pointPrice;
|
||||
// TODO @芋艿:如果商品 vip 折扣时,到底是新增一个 vipPrice 记录优惠记录,还是 vipDiscountPrice,记录 vip 的优惠;还是直接使用 vipPrice;
|
||||
// 目前 crmeb 的选择,单独一个 vipPrice 记录优惠价格;感觉不一定合理,可以在看看有赞的;
|
||||
|
||||
// ========== 售后基本信息 ==========
|
||||
|
||||
|
@ -98,6 +98,8 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
|
||||
return afterSale;
|
||||
}
|
||||
|
||||
// TODO 芋艿:拼团失败,要不要发起售后的方式退款?还是走取消逻辑?
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createAfterSale(Long userId, AppTradeAfterSaleCreateReqVO createReqVO) {
|
||||
|
@ -66,7 +66,7 @@ public interface TradeOrderQueryService {
|
||||
Long getOrderCount(Long userId, Integer status, Boolean commonStatus);
|
||||
|
||||
/**
|
||||
* 获得订单的物流轨迹
|
||||
* 【前台】获得订单的物流轨迹
|
||||
*
|
||||
* @param id 订单编号
|
||||
* @param userId 用户编号
|
||||
@ -74,6 +74,14 @@ public interface TradeOrderQueryService {
|
||||
*/
|
||||
List<ExpressTrackRespDTO> getExpressTrackList(Long id, Long userId);
|
||||
|
||||
/**
|
||||
* 【后台】获得订单的物流轨迹
|
||||
*
|
||||
* @param id 订单编号
|
||||
* @return 物流轨迹数组
|
||||
*/
|
||||
List<ExpressTrackRespDTO> getExpressTrackList(Long id);
|
||||
|
||||
// =================== Order Item ===================
|
||||
|
||||
/**
|
||||
|
@ -106,6 +106,27 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
return getExpressTrackList(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExpressTrackRespDTO> getExpressTrackList(Long id) {
|
||||
// 查询订单
|
||||
TradeOrderDO order = tradeOrderMapper.selectById(id);
|
||||
if (order == null) {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
return getExpressTrackList(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得订单的物流轨迹
|
||||
*
|
||||
* @param order 订单
|
||||
* @return 物流轨迹
|
||||
*/
|
||||
private List<ExpressTrackRespDTO> getExpressTrackList(TradeOrderDO order) {
|
||||
// 查询物流公司
|
||||
if (order.getLogisticsId() == null) {
|
||||
return Collections.emptyList();
|
||||
|
@ -117,4 +117,11 @@ public interface TradeOrderUpdateService {
|
||||
*/
|
||||
Long createOrderItemComment(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 【会员】取消订单
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param id 订单编号
|
||||
*/
|
||||
void cancelOrder(Long userId, Long id);
|
||||
}
|
||||
|
@ -96,6 +96,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
private TradePriceService tradePriceService;
|
||||
@Resource
|
||||
private DeliveryExpressService deliveryExpressService;
|
||||
@Resource
|
||||
private TradeMessageService tradeMessageService;
|
||||
|
||||
@Resource
|
||||
private ProductSkuApi productSkuApi;
|
||||
@ -105,27 +107,22 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
private AddressApi addressApi;
|
||||
@Resource
|
||||
private CouponApi couponApi;
|
||||
|
||||
@Resource
|
||||
private CombinationRecordApi combinationRecordApi;
|
||||
@Resource
|
||||
private BargainRecordApi bargainRecordApi;
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
@Resource
|
||||
private MemberLevelApi memberLevelApi;
|
||||
@Resource
|
||||
private MemberPointApi memberPointApi;
|
||||
|
||||
@Resource
|
||||
private ProductCommentApi productCommentApi;
|
||||
@Resource
|
||||
private TradeMessageService tradeMessageService;
|
||||
|
||||
@Resource
|
||||
private TradeOrderProperties tradeOrderProperties;
|
||||
|
||||
@Resource
|
||||
private CombinationRecordApi combinationRecordApi;
|
||||
|
||||
@Resource
|
||||
private BargainRecordApi bargainRecordApi;
|
||||
|
||||
// =================== Order ===================
|
||||
|
||||
@Override
|
||||
@ -179,15 +176,17 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) {
|
||||
// 2. 价格计算
|
||||
// 1. 价格计算
|
||||
TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
|
||||
// 3.1 插入 TradeOrderDO 订单
|
||||
|
||||
// 2.1 插入 TradeOrderDO 订单
|
||||
TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO);
|
||||
// 3.2 插入 TradeOrderItemDO 订单项
|
||||
// 2.2 插入 TradeOrderItemDO 订单项
|
||||
List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO);
|
||||
// 订单创建完后的逻辑
|
||||
|
||||
// 3. 订单创建完后的逻辑
|
||||
afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO);
|
||||
// 3.3 校验订单类型
|
||||
// 3.1 拼团的特殊逻辑
|
||||
// TODO @puhui999:这个逻辑,先抽个小方法;未来要通过设计模式,把这些拼团之类的逻辑,抽象出去
|
||||
// 拼团
|
||||
if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
|
||||
@ -205,15 +204,19 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
|
||||
combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, orderItemDO, createReqVO, user));
|
||||
}
|
||||
// 3.2 秒杀的特殊逻辑
|
||||
// TODO 秒杀扣减库存是下单就扣除还是等待订单支付成功再扣除
|
||||
if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), order.getType())) {
|
||||
|
||||
}
|
||||
// 3.3 砍价的特殊逻辑
|
||||
|
||||
// TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
|
||||
return order;
|
||||
}
|
||||
|
||||
// TODO @puhui999:订单超时,自动取消;
|
||||
|
||||
/**
|
||||
* 校验收件地址是否存在
|
||||
*
|
||||
@ -239,7 +242,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
}
|
||||
TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address);
|
||||
order.setType(validateActivity(createReqVO));
|
||||
order.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的;
|
||||
order.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @puhui999: 参考支付订单,的 no 生成哈;
|
||||
order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
|
||||
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
|
||||
order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
|
||||
@ -251,6 +254,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
// 退款信息
|
||||
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
|
||||
tradeOrderMapper.insert(order);
|
||||
// TODO @puhui999:如果是门店订单,则需要生成核销码;
|
||||
return order;
|
||||
}
|
||||
|
||||
@ -291,7 +295,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems,
|
||||
TradePriceCalculateRespBO calculateRespBO) {
|
||||
// 下单时扣减商品库存
|
||||
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
|
||||
// TODO @puhui999:扣库存,需要前置;
|
||||
// 1)如果是秒杀商品:额外扣减秒杀的库存;
|
||||
// 2)如果是拼团活动:额外扣减拼团的库存;
|
||||
// 3)如果是砍价活动:额外扣减砍价的库存;
|
||||
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
|
||||
|
||||
// 删除购物车商品
|
||||
Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
|
||||
@ -299,9 +307,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
cartService.deleteCart(userId, cartIds);
|
||||
}
|
||||
|
||||
// 扣减积分 TODO 芋艿:待实现
|
||||
// 扣减积分 TODO 芋艿:待实现,需要前置;
|
||||
// 这个是不是应该放到支付成功之后?如果支付后的话,可能积分可以重复使用哈。资源类,都要预扣
|
||||
|
||||
// 有使用优惠券时更新
|
||||
// 有使用优惠券时更新 TODO 芋艿:需要前置;
|
||||
if (createReqVO.getCouponId() != null) {
|
||||
couponApi.useCoupon(new CouponUseReqDTO().setId(createReqVO.getCouponId()).setUserId(userId)
|
||||
.setOrderId(tradeOrderDO.getId()));
|
||||
@ -510,6 +519,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
// TODO 芋艿:lili 发送订单变化的消息
|
||||
|
||||
// TODO 芋艿:lili 发送商品被购买完成的数据
|
||||
|
||||
// TODO 芋艿:销售佣金的记录;
|
||||
|
||||
// TODO 芋艿:获得积分;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -529,11 +542,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
if (order.getPayStatus()) {
|
||||
throw exception(ORDER_UPDATE_PRICE_FAIL_PAID);
|
||||
}
|
||||
// TODO @puhui999:如果改价,需要校验下是否真的变化;
|
||||
|
||||
// 更新
|
||||
// TODO @puhui999:TradeOrderItemDO 需要做 adjustPrice 的分摊;另外,支付订单那的价格,需要 update 下;
|
||||
TradeOrderDO update = TradeOrderConvert.INSTANCE.convert(reqVO);
|
||||
update.setPayPrice(update.getPayPrice() + update.getAdjustPrice());
|
||||
// TODO @芋艿:改价时,赠送的积分,要不要做改动???
|
||||
tradeOrderMapper.updateById(update);
|
||||
}
|
||||
|
||||
@ -651,10 +666,46 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
|
||||
if (!anyMatch(orderItems, item -> Objects.equals(item.getCommentStatus(), Boolean.FALSE))) {
|
||||
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setCommentStatus(Boolean.TRUE));
|
||||
// TODO 待实现:已完成评价,要不要写一条订单日志?目前 crmeb 会写,有赞可以研究下
|
||||
}
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelOrder(Long userId, Long id) {
|
||||
// 校验存在
|
||||
TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId);
|
||||
if (order == null) {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
// 校验状态
|
||||
if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) {
|
||||
throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
|
||||
}
|
||||
|
||||
// 1.更新 TradeOrderDO 状态为已取消
|
||||
int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
|
||||
new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
|
||||
.setCancelTime(LocalDateTime.now())
|
||||
.setCancelType(TradeOrderCancelTypeEnum.MEMBER_CANCEL.getType()));
|
||||
if (updateCount == 0) {
|
||||
throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
|
||||
}
|
||||
|
||||
// 2.回滚库存
|
||||
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
|
||||
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
|
||||
|
||||
// 3.回滚优惠券
|
||||
couponApi.returnUsedCoupon(order.getCouponId());
|
||||
|
||||
// 4.回滚积分:积分是支付成功后才增加的吧? 回复:每个项目不同,目前看下来,确认收货貌似更合适,我再看看其它项目的业务选择;
|
||||
|
||||
// TODO 芋艿:OrderLog
|
||||
|
||||
// TODO 芋艿:lili 发送订单变化的消息
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定订单的所有订单项,是不是都售后成功
|
||||
*
|
||||
|
@ -41,6 +41,7 @@ public class TradePriceServiceImpl implements TradePriceService {
|
||||
@Resource
|
||||
private List<TradePriceCalculator> priceCalculators;
|
||||
|
||||
// TODO @疯狂:需要搞个 TradePriceCalculator,计算赠送积分;
|
||||
@Override
|
||||
public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) {
|
||||
// 1.1 获得商品 SKU 数组
|
||||
|
@ -102,7 +102,10 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
|
||||
Predicate<TradePriceCalculateRespBO.OrderItem> matchPredicate = TradePriceCalculateRespBO.OrderItem::getSelected;
|
||||
if (PromotionProductScopeEnum.SPU.getScope().equals(coupon.getProductScope())) {
|
||||
matchPredicate = matchPredicate // 额外加如下条件
|
||||
.and(orderItem -> coupon.getProductSpuIds().contains(orderItem.getSpuId()));
|
||||
.and(orderItem -> coupon.getProductScopeValues().contains(orderItem.getSpuId()));
|
||||
} else if (PromotionProductScopeEnum.CATEGORY.getScope().equals(coupon.getProductScope())) {
|
||||
matchPredicate = matchPredicate // 额外加如下条件
|
||||
.and(orderItem -> coupon.getProductScopeValues().contains(orderItem.getCategoryId()));
|
||||
}
|
||||
return filterList(result.getItems(), matchPredicate);
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// TODO @芋艿:如果门店自提,需要校验是否开启;
|
||||
// 1.1 判断配送方式
|
||||
if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
|
||||
return;
|
||||
|
@ -65,7 +65,7 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest {
|
||||
|
||||
// mock 方法(优惠劵 Coupon 信息)
|
||||
CouponRespDTO coupon = randomPojo(CouponRespDTO.class, o -> o.setId(1024L).setName("程序员节")
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L))
|
||||
.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);
|
||||
|
Loading…
Reference in New Issue
Block a user