Merge branch 'feature/mall_product' of https://gitee.com/zhijiantianya/ruoyi-vue-pro

This commit is contained in:
YunaiV 2023-09-03 18:07:05 +08:00
commit 8f86f65884
43 changed files with 530 additions and 74 deletions

View File

@ -17,7 +17,7 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = InEnumValidator.class
validatedBy = {InEnumValidator.class, InEnumCollectionValidator.class}
)
public @interface InEnum {

View File

@ -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;
}
}

View File

@ -20,6 +20,13 @@ public interface CouponApi {
*/
void useCoupon(@Valid CouponUseReqDTO useReqDTO);
/**
* 退还已使用的优惠券
*
* @param id 优惠券编号
*/
void returnUsedCoupon(Long id);
/**
* 校验优惠劵
*

View File

@ -71,9 +71,9 @@ public class CouponRespDTO {
*/
private Integer productScope;
/**
* 商品 SPU 编号的数组
* 商品范围编号的数组
*/
private List<Long> productSpuIds;
private List<Long> productScopeValues;
// ========== 使用规则 END ==========
// ========== 使用效果 BEGIN ==========

View File

@ -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, "满减送活动不存在");

View File

@ -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();

View File

@ -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();

View File

@ -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());

View File

@ -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);
}
}

View File

@ -75,5 +75,4 @@ public class CouponTemplateController {
PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage(pageVO);
return success(CouponTemplateConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@ -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 ==========

View File

@ -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;
}

View File

@ -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 = "生效开始时间不能为空")

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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 ==========

View File

@ -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;
/**
* 生效日期类型
*

View File

@ -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)
);
}
}

View File

@ -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));
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);

View File

@ -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 实现类

View File

@ -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, "售后单不存在");

View File

@ -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));
}

View File

@ -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 后台订单统计
}

View File

@ -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 @puhui999swagger 注解
@Data
public static class OrderLog {
/**
* 内容
*/
private String content;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@Schema(description = "管理后台 - 交易订单的详情的订单项目")
@Data
public static class Item extends TradeOrderItemBaseVO {

View File

@ -61,5 +61,5 @@ public class TradeOrderPageReqVO extends PageParam {
@Schema(description = "订单来源", example = "10")
@InEnum(value = TerminalEnum.class, message = "订单来源 {value}")
private Integer terminal;
// TODO 添加配送方式筛选
}

View File

@ -24,6 +24,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Validated
public class AppDeliverPickUpStoreController {
// TODO 待实现[门店自提]如果 latitudelongitude 非空计算经纬度并排序计算的库可以使用 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 = "门店编号")

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -143,6 +143,8 @@ public class TradeOrderItemDO extends BaseDO {
* 对应 taobao trade.point_fee 字段
*/
private Integer pointPrice;
// TODO @芋艿如果商品 vip 折扣时到底是新增一个 vipPrice 记录优惠记录还是 vipDiscountPrice记录 vip 的优惠还是直接使用 vipPrice
// 目前 crmeb 的选择单独一个 vipPrice 记录优惠价格感觉不一定合理可以在看看有赞的
// ========== 售后基本信息 ==========

View File

@ -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) {

View File

@ -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 ===================
/**

View File

@ -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();

View File

@ -117,4 +117,11 @@ public interface TradeOrderUpdateService {
*/
Long createOrderItemComment(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO);
/**
* 会员取消订单
*
* @param userId 用户ID
* @param id 订单编号
*/
void cancelOrder(Long userId, Long id);
}

View File

@ -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 @puhui999TradeOrderItemDO 需要做 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 发送订单变化的消息
}
/**
* 判断指定订单的所有订单项是不是都售后成功
*

View File

@ -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 数组

View File

@ -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);
}

View File

@ -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;

View File

@ -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);