!530 完成拼团活动和秒杀活动 CRUD

Merge pull request !530 from puhui999/feature/mall_product
This commit is contained in:
芋道源码 2023-07-26 05:01:00 +00:00 committed by Gitee
commit e2ed0b7afb
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
90 changed files with 2974 additions and 604 deletions

View File

@ -4,6 +4,7 @@ import cn.hutool.core.date.LocalDateTimeUtil;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime;
/** /**
* 时间工具类用于 {@link java.time.LocalDateTime} * 时间工具类用于 {@link java.time.LocalDateTime}
@ -60,4 +61,23 @@ public class LocalDateTimeUtils {
return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime); return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime);
} }
/**
* 检查时间重叠 不包含日期
*
* @param startTime1 需要校验的开始时间
* @param endTime1 需要校验的结束时间
* @param startTime2 校验所需的开始时间
* @param endTime2 校验所需的结束时间
* @return 是否重叠
*/
public static boolean checkTimeOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {
// 判断时间是否重叠
// 开始时间在已配置时段的结束时间之前 结束时间在已配置时段的开始时间之后 []
return startTime1.isBefore(endTime2) && endTime1.isAfter(startTime2)
// 开始时间在已配置时段的开始时间之前 结束时间在已配置时段的开始时间之后 (] ()
|| startTime1.isBefore(startTime2) && endTime1.isAfter(startTime2)
// 开始时间在已配置时段的结束时间之前 结束时间在已配值时段的结束时间之后 [) ()
|| startTime1.isBefore(endTime2) && endTime1.isAfter(endTime2);
}
} }

View File

@ -30,14 +30,13 @@ public interface ProductSkuApi {
*/ */
List<ProductSkuRespDTO> getSkuList(Collection<Long> ids); List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
// TODO puhui999入参用 Collection<Long> 更通用
/** /**
* 批量查询 SKU 数组 * 批量查询 SKU 数组
* *
* @param spuIds SPU 编号列表 * @param spuIds SPU 编号列表
* @return SKU 数组 * @return SKU 数组
*/ */
List<ProductSkuRespDTO> getSkuListBySpuId(List<Long> spuIds); List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds);
/** /**
* 更新 SKU 库存 * 更新 SKU 库存

View File

@ -46,7 +46,7 @@ public interface ErrorCodeConstants {
// ========== 商品 评价 1008007000 ========== // ========== 商品 评价 1008007000 ==========
ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1008007000, "商品 评价 不存在"); ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1008007000, "商品 评价 不存在");
ErrorCode ORDER_SPU_COMMENT_EXISTS = new ErrorCode(1008007001, "订单 商品评价 已存在"); ErrorCode ORDER_SKU_COMMENT_EXISTS = new ErrorCode(1008007001, "订单 商品评价 已存在");
ErrorCode COMMENT_ERROR_OPT = new ErrorCode(1008007002, "商品评价非法操作"); ErrorCode COMMENT_ERROR_OPT = new ErrorCode(1008007002, "商品评价非法操作");
ErrorCode COMMENT_ADDITIONAL_EXISTS = new ErrorCode(1008007003, "商品追加评价已存在"); ErrorCode COMMENT_ADDITIONAL_EXISTS = new ErrorCode(1008007003, "商品追加评价已存在");

View File

@ -43,7 +43,7 @@ public class ProductSkuApiImpl implements ProductSkuApi {
} }
@Override @Override
public List<ProductSkuRespDTO> getSkuListBySpuId(List<Long> spuIds) { public List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds) {
if (CollUtil.isEmpty(spuIds)) { if (CollUtil.isEmpty(spuIds)) {
return Collections.emptyList(); return Collections.emptyList();
} }

View File

@ -26,23 +26,10 @@ public class ProductCommentBaseVO {
@NotNull(message = "评价人头像不能为空") @NotNull(message = "评价人头像不能为空")
private String userAvatar; private String userAvatar;
// TODO @puhuispuIdspuName 是不是只有 ProductCommentRespVO 有呀
@Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉丝滑透气小短袖")
@NotNull(message = "商品 SPU 编号不能为空")
private Long spuId;
@Schema(description = "商品 SPU 名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@NotNull(message = "商品 SPU 名称不能为空")
private String spuName;
@Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "商品 SKU 编号不能为空") @NotNull(message = "商品 SKU 编号不能为空")
private Long skuId; private Long skuId;
@Schema(description = "评分星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
@NotNull(message = "评分星级不能为空")
private Integer scores;
@Schema(description = "描述星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") @Schema(description = "描述星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
@NotNull(message = "描述星级不能为空") @NotNull(message = "描述星级不能为空")
private Integer descriptionScores; private Integer descriptionScores;

View File

@ -35,9 +35,8 @@ public class ProductCommentPageReqVO extends PageParam {
@InEnum(ProductCommentScoresEnum.class) @InEnum(ProductCommentScoresEnum.class)
private Integer scores; private Integer scores;
// TODO @puhui999replyStatus
@Schema(description = "商家是否回复", example = "true") @Schema(description = "商家是否回复", example = "true")
private Boolean replied; private Boolean replyStatus;
@Schema(description = "创建时间") @Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)

View File

@ -5,6 +5,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Schema(description = "管理后台 - 商品评价 Response VO") @Schema(description = "管理后台 - 商品评价 Response VO")
@ -40,4 +41,15 @@ public class ProductCommentRespVO extends ProductCommentBaseVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime; private LocalDateTime createTime;
@Schema(description = "评分星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Integer scores;
@Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉丝滑透气小短袖")
@NotNull(message = "商品 SPU 编号不能为空")
private Long spuId;
@Schema(description = "商品 SPU 名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@NotNull(message = "商品 SPU 名称不能为空")
private String spuName;
} }

View File

@ -21,6 +21,7 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid; import javax.validation.Valid;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -29,11 +30,6 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS; import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
/**
* 商品 SPU 相关接口
*
* @author HUIHUI
*/
@Tag(name = "管理后台 - 商品 SPU") @Tag(name = "管理后台 - 商品 SPU")
@RestController @RestController
@RequestMapping("/product/spu") @RequestMapping("/product/spu")
@ -100,6 +96,15 @@ public class ProductSpuController {
return success(ProductSpuConvert.INSTANCE.convertList02(list)); return success(ProductSpuConvert.INSTANCE.convertList02(list));
} }
@GetMapping("/list")
@Operation(summary = "获得商品 SPU 详情列表")
@Parameter(name = "spuIds", description = "spu 编号列表", required = true, example = "[1,2,3]")
@PreAuthorize("@ss.hasPermission('product:spu:query')")
public CommonResult<List<ProductSpuDetailRespVO>> getSpuList(@RequestParam("spuIds") Collection<Long> spuIds) {
return success(ProductSpuConvert.INSTANCE.convertForSpuDetailRespListVO(
productSpuService.getSpuList(spuIds), productSkuService.getSkuListBySpuId(spuIds)));
}
@GetMapping("/page") @GetMapping("/page")
@Operation(summary = "获得商品 SPU 分页") @Operation(summary = "获得商品 SPU 分页")
@PreAuthorize("@ss.hasPermission('product:spu:query')") @PreAuthorize("@ss.hasPermission('product:spu:query')")

View File

@ -9,11 +9,6 @@ import lombok.ToString;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.List; import java.util.List;
/**
* 商品 SPU 创建 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU 创建 Request VO") @Schema(description = "管理后台 - 商品 SPU 创建 Request VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -8,12 +8,6 @@ import lombok.ToString;
import java.util.List; import java.util.List;
/**
* 商品 SPU 详细 Response VO
* 包括关联的 SKU 等信息
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU 详细 Response VO") @Schema(description = "管理后台 - 商品 SPU 详细 Response VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -10,11 +10,6 @@ import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 商品Spu导出 Request VO,参数和 ProductSpuPageReqVO 是一致的
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品Spu导出 Request VO,参数和 ProductSpuPageReqVO 是一致的") @Schema(description = "管理后台 - 商品Spu导出 Request VO,参数和 ProductSpuPageReqVO 是一致的")
@Data @Data
@NoArgsConstructor @NoArgsConstructor

View File

@ -11,11 +11,6 @@ import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 商品 SPU 分页 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU 分页 Request VO") @Schema(description = "管理后台 - 商品 SPU 分页 Request VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -7,11 +7,6 @@ import lombok.ToString;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/**
* 商品 SPU Response VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU Response VO") @Schema(description = "管理后台 - 商品 SPU Response VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -4,11 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.ToString; import lombok.ToString;
/**
* 商品 SPU 精简 Response VO
* TODO 商品 SPU 精简 VO 暂时没有使用到用到的时候再按需添加\修改属性
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU 精简 Response VO") @Schema(description = "管理后台 - 商品 SPU 精简 Response VO")
@Data @Data
@ToString(callSuper = true) @ToString(callSuper = true)

View File

@ -12,11 +12,6 @@ import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.List; import java.util.List;
/**
* 商品 SPU 更新 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU 更新 Request VO") @Schema(description = "管理后台 - 商品 SPU 更新 Request VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -7,11 +7,6 @@ import lombok.Data;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
/**
* 商品 SPU Status 更新 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU Status 更新 Request VO") @Schema(description = "管理后台 - 商品 SPU Status 更新 Request VO")
@Data @Data
public class ProductSpuUpdateStatusReqVO{ public class ProductSpuUpdateStatusReqVO{

View File

@ -1,18 +1,18 @@
package cn.iocoder.yudao.module.product.controller.app.comment; package cn.iocoder.yudao.module.product.controller.app.comment;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO; import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO; import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppProductCommentRespVO; import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppProductCommentRespVO;
import cn.iocoder.yudao.module.product.controller.app.property.vo.value.AppProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert; import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.service.comment.ProductCommentService; import cn.iocoder.yudao.module.product.service.comment.ProductCommentService;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.Parameters;
@ -26,11 +26,9 @@ import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -61,26 +59,14 @@ public class AppProductCommentController {
@GetMapping("/page") @GetMapping("/page")
@Operation(summary = "获得商品评价分页") @Operation(summary = "获得商品评价分页")
public CommonResult<PageResult<AppProductCommentRespVO>> getCommentPage(@Valid AppCommentPageReqVO pageVO) { public CommonResult<PageResult<AppProductCommentRespVO>> getCommentPage(@Valid AppCommentPageReqVO pageVO) {
PageResult<AppProductCommentRespVO> page = productCommentService.getCommentPage(pageVO, Boolean.TRUE); PageResult<ProductCommentDO> commentDOPage = productCommentService.getCommentPage(pageVO, Boolean.TRUE);
// TODO @puhui CollUtils 有简化 convertmap list 的方法 Set<Long> skuIds = CollectionUtils.convertSet(commentDOPage.getList(), ProductCommentDO::getSkuId);
Set<Long> skuIds = page.getList().stream().map(AppProductCommentRespVO::getSkuId).collect(Collectors.toSet());
List<ProductSkuDO> skuList = productSkuService.getSkuList(skuIds); List<ProductSkuDO> skuList = productSkuService.getSkuList(skuIds);
Map<Long, ProductSkuDO> skuDOMap = new HashMap<>(skuIds.size()); Map<Long, ProductSkuDO> skuDOMap = Maps.newLinkedHashMapWithExpectedSize(skuIds.size());
if (CollUtil.isNotEmpty(skuList)) { if (CollUtil.isNotEmpty(skuList)) {
skuDOMap.putAll(skuList.stream().collect(Collectors.toMap(ProductSkuDO::getId, c -> c))); skuDOMap.putAll(CollectionUtils.convertMap(skuList, ProductSkuDO::getId, c -> c));
} }
// TODO @puihui999下面也可以放到 convert 里哈 PageResult<AppProductCommentRespVO> page = ProductCommentConvert.INSTANCE.convertPage02(commentDOPage, skuDOMap);
page.getList().forEach(item -> {
// 判断用户是否选择匿名
if (ObjectUtil.equal(item.getAnonymous(), true)) {
item.setUserNickname(ProductCommentDO.NICKNAME_ANONYMOUS);
}
ProductSkuDO productSkuDO = skuDOMap.get(item.getSkuId());
if (productSkuDO != null) {
List<AppProductPropertyValueDetailRespVO> skuProperties = ProductCommentConvert.INSTANCE.convertList01(productSkuDO.getProperties());
item.setSkuProperties(skuProperties);
}
});
return success(page); return success(page);
} }

View File

@ -10,11 +10,6 @@ import javax.validation.constraints.Size;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
/**
* 用户 App - 商品评价详情 Response VO
*
* @author HUIHUI
*/
@Schema(description = "用户 App - 商品评价详情 Response VO") @Schema(description = "用户 App - 商品评价详情 Response VO")
@Data @Data
@ToString(callSuper = true) @ToString(callSuper = true)

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.product.convert.comment; package cn.iocoder.yudao.module.product.convert.comment;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
@ -19,6 +20,7 @@ import org.mapstruct.factory.Mappers;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 商品评价 Convert * 商品评价 Convert
@ -32,10 +34,12 @@ public interface ProductCommentConvert {
ProductCommentRespVO convert(ProductCommentDO bean); ProductCommentRespVO convert(ProductCommentDO bean);
// TODO @puhui999这里貌似字段对上就不用 mapping 可以测试下看看哈 @Named("calculateOverallScore")
@Mapping(target = "goodCount", source = "goodCount") default double calculateOverallScore(long goodCount, long mediocreCount, long negativeCount) {
@Mapping(target = "mediocreCount", source = "mediocreCount") return (goodCount * 5 + mediocreCount * 3 + negativeCount) / (double) (goodCount + mediocreCount + negativeCount);
@Mapping(target = "negativeCount", source = "negativeCount") }
@Mapping(target = "scores", expression = "java(calculateOverallScore(goodCount, mediocreCount, negativeCount))")
AppCommentStatisticsRespVO convert(Long goodCount, Long mediocreCount, Long negativeCount); AppCommentStatisticsRespVO convert(Long goodCount, Long mediocreCount, Long negativeCount);
List<ProductCommentRespVO> convertList(List<ProductCommentDO> list); List<ProductCommentRespVO> convertList(List<ProductCommentDO> list);
@ -44,7 +48,23 @@ public interface ProductCommentConvert {
PageResult<ProductCommentRespVO> convertPage(PageResult<ProductCommentDO> page); PageResult<ProductCommentRespVO> convertPage(PageResult<ProductCommentDO> page);
PageResult<AppProductCommentRespVO> convertPage02(PageResult<ProductCommentDO> pageResult); PageResult<AppProductCommentRespVO> convertPage01(PageResult<ProductCommentDO> pageResult);
default PageResult<AppProductCommentRespVO> convertPage02(PageResult<ProductCommentDO> pageResult, Map<Long, ProductSkuDO> skuDOMap) {
PageResult<AppProductCommentRespVO> page = convertPage01(pageResult);
page.getList().forEach(item -> {
// 判断用户是否选择匿名
if (ObjectUtil.equal(item.getAnonymous(), true)) {
item.setUserNickname(ProductCommentDO.NICKNAME_ANONYMOUS);
}
ProductSkuDO productSkuDO = skuDOMap.get(item.getSkuId());
if (productSkuDO != null) {
List<AppProductPropertyValueDetailRespVO> skuProperties = ProductCommentConvert.INSTANCE.convertList01(productSkuDO.getProperties());
item.setSkuProperties(skuProperties);
}
});
return page;
}
/** /**
* 计算综合评分 * 计算综合评分

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.product.convert.spu; package cn.iocoder.yudao.module.product.convert.spu;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
@ -18,6 +19,7 @@ import org.mapstruct.factory.Mappers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import static cn.hutool.core.util.ObjectUtil.defaultIfNull; import static cn.hutool.core.util.ObjectUtil.defaultIfNull;
@ -107,4 +109,15 @@ public interface ProductSpuConvert {
return detailRespVO; return detailRespVO;
} }
default List<ProductSpuDetailRespVO> convertForSpuDetailRespListVO(List<ProductSpuDO> spus, List<ProductSkuDO> skus) {
ArrayList<ProductSpuDetailRespVO> vos = new ArrayList<>();
Map<Long, List<ProductSkuDO>> skuMultiMap = CollectionUtils.convertMultiMap(skus, ProductSkuDO::getSpuId);
spus.forEach(spu -> {
ProductSpuDetailRespVO detailRespVO = convert03(spu);
detailRespVO.setSkus(ProductSkuConvert.INSTANCE.convertList(skuMultiMap.get(spu.getId())));
vos.add(detailRespVO);
});
return vos;
}
} }

View File

@ -51,11 +51,11 @@ public interface ProductCommentMapper extends BaseMapperX<ProductCommentDO> {
return selectPage(reqVO, queryWrapper); return selectPage(reqVO, queryWrapper);
} }
default ProductCommentDO selectByUserIdAndOrderItemIdAndSpuId(Long userId, Long orderItemId, Long spuId) { default ProductCommentDO selectByUserIdAndOrderItemIdAndSpuId(Long userId, Long orderItemId, Long skuId) {
return selectOne(new LambdaQueryWrapperX<ProductCommentDO>() return selectOne(new LambdaQueryWrapperX<ProductCommentDO>()
.eq(ProductCommentDO::getUserId, userId) .eq(ProductCommentDO::getUserId, userId)
.eq(ProductCommentDO::getOrderItemId, orderItemId) .eq(ProductCommentDO::getOrderItemId, orderItemId)
.eq(ProductCommentDO::getSpuId, spuId)); .eq(ProductCommentDO::getSpuId, skuId));
} }
default Long selectCountBySpuId(Long spuId, Boolean visible, Integer type) { default Long selectCountBySpuId(Long spuId, Boolean visible, Integer type) {

View File

@ -54,7 +54,7 @@ public interface ProductCommentService {
* @param visible 是否可见 * @param visible 是否可见
* @return 商品评价分页 * @return 商品评价分页
*/ */
PageResult<AppProductCommentRespVO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible); PageResult<ProductCommentDO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible);
/** /**
* 创建商品评论 * 创建商品评论

View File

@ -81,7 +81,7 @@ public class ProductCommentServiceImpl implements ProductCommentService {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void createComment(ProductCommentCreateReqVO createReqVO) { public void createComment(ProductCommentCreateReqVO createReqVO) {
// 校验评论 // 校验评论
validateComment(createReqVO.getSpuId(), createReqVO.getUserId(), createReqVO.getOrderItemId()); validateComment(createReqVO.getSkuId(), createReqVO.getUserId(), createReqVO.getOrderItemId());
ProductCommentDO commentDO = ProductCommentConvert.INSTANCE.convert(createReqVO); ProductCommentDO commentDO = ProductCommentConvert.INSTANCE.convert(createReqVO);
productCommentMapper.insert(commentDO); productCommentMapper.insert(commentDO);
@ -108,11 +108,11 @@ public class ProductCommentServiceImpl implements ProductCommentService {
return commentDO.getId(); return commentDO.getId();
} }
private void validateComment(Long spuId, Long userId, Long orderItemId) { private void validateComment(Long skuId, Long userId, Long orderItemId) {
// 判断当前订单的当前商品用户是否评价过 // 判断当前订单的当前商品用户是否评价过
ProductCommentDO exist = productCommentMapper.selectByUserIdAndOrderItemIdAndSpuId(userId, orderItemId, spuId); ProductCommentDO exist = productCommentMapper.selectByUserIdAndOrderItemIdAndSpuId(userId, orderItemId, skuId);
if (null != exist) { if (null != exist) {
throw exception(ORDER_SPU_COMMENT_EXISTS); throw exception(ORDER_SKU_COMMENT_EXISTS);
} }
} }
@ -141,23 +141,17 @@ public class ProductCommentServiceImpl implements ProductCommentService {
productCommentMapper.selectCountBySpuId(spuId, visible, AppCommentPageReqVO.MEDIOCRE_COMMENT), productCommentMapper.selectCountBySpuId(spuId, visible, AppCommentPageReqVO.MEDIOCRE_COMMENT),
// 查询商品 id = spuId 的所有差评数量 // 查询商品 id = spuId 的所有差评数量
productCommentMapper.selectCountBySpuId(spuId, visible, AppCommentPageReqVO.NEGATIVE_COMMENT) productCommentMapper.selectCountBySpuId(spuId, visible, AppCommentPageReqVO.NEGATIVE_COMMENT)
).setScores(3.0); // TODO @puhui999这里要实现下 );
} }
@Override @Override
public List<AppProductCommentRespVO> getCommentList(Long spuId, Integer count) { public List<AppProductCommentRespVO> getCommentList(Long spuId, Integer count) {
// 校验商品 spu 是否存在 return ProductCommentConvert.INSTANCE.convertList02(productCommentMapper.selectCommentList(spuId, count).getList());
// TODO @puhui 这里校验可以去掉哈
ProductSpuDO spuDO = validateSpu(spuId);
return ProductCommentConvert.INSTANCE.convertList02(productCommentMapper.selectCommentList(spuDO.getId(), count).getList());
} }
// TODO @puhui 可以放到 controller convert
@Override @Override
public PageResult<AppProductCommentRespVO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible) { public PageResult<ProductCommentDO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible) {
// TODO @puhui 可以放到 controller convert return productCommentMapper.selectPage(pageVO, visible);
return ProductCommentConvert.INSTANCE.convertPage02(
productCommentMapper.selectPage(pageVO, visible));
} }
@Override @Override

View File

@ -90,7 +90,7 @@ public interface ProductSkuService {
* @param spuIds spu 编码集合 * @param spuIds spu 编码集合
* @return 商品 sku 集合 * @return 商品 sku 集合
*/ */
List<ProductSkuDO> getSkuListBySpuId(List<Long> spuIds); List<ProductSkuDO> getSkuListBySpuId(Collection<Long> spuIds);
/** /**
* 通过 spuId 删除 sku 信息 * 通过 spuId 删除 sku 信息

View File

@ -148,7 +148,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
} }
@Override @Override
public List<ProductSkuDO> getSkuListBySpuId(List<Long> spuIds) { public List<ProductSkuDO> getSkuListBySpuId(Collection<Long> spuIds) {
return productSkuMapper.selectListBySpuId(spuIds); return productSkuMapper.selectListBySpuId(spuIds);
} }

View File

@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommen
import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO; import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO; import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO; import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppProductCommentRespVO;
import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert; import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper; import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper;
@ -128,7 +127,7 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
productCommentPageReqVO.setSpuId(spuId); productCommentPageReqVO.setSpuId(spuId);
productCommentPageReqVO.setSpuName("感冒药"); productCommentPageReqVO.setSpuName("感冒药");
productCommentPageReqVO.setScores(ProductCommentScoresEnum.FOUR.getScores()); productCommentPageReqVO.setScores(ProductCommentScoresEnum.FOUR.getScores());
productCommentPageReqVO.setReplied(Boolean.TRUE); productCommentPageReqVO.setReplyStatus(Boolean.TRUE);
PageResult<ProductCommentDO> commentPage = productCommentService.getCommentPage(productCommentPageReqVO); PageResult<ProductCommentDO> commentPage = productCommentService.getCommentPage(productCommentPageReqVO);
PageResult<ProductCommentRespVO> result = ProductCommentConvert.INSTANCE.convertPage(productCommentMapper.selectPage(productCommentPageReqVO)); PageResult<ProductCommentRespVO> result = ProductCommentConvert.INSTANCE.convertPage(productCommentMapper.selectPage(productCommentPageReqVO));
@ -138,15 +137,15 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
assertEquals(8, all.getTotal()); assertEquals(8, all.getTotal());
// 测试获取所有商品分页评论数据 // 测试获取所有商品分页评论数据
PageResult<AppProductCommentRespVO> result1 = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE); PageResult<ProductCommentDO> result1 = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE);
assertEquals(7, result1.getTotal()); assertEquals(7, result1.getTotal());
// 测试获取所有商品分页中评数据 // 测试获取所有商品分页中评数据
PageResult<AppProductCommentRespVO> result2 = productCommentService.getCommentPage(new AppCommentPageReqVO().setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE); PageResult<ProductCommentDO> result2 = productCommentService.getCommentPage(new AppCommentPageReqVO().setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
assertEquals(2, result2.getTotal()); assertEquals(2, result2.getTotal());
// 测试获取指定 spuId 商品分页中评数据 // 测试获取指定 spuId 商品分页中评数据
PageResult<AppProductCommentRespVO> result3 = productCommentService.getCommentPage(new AppCommentPageReqVO().setSpuId(spuId).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE); PageResult<ProductCommentDO> result3 = productCommentService.getCommentPage(new AppCommentPageReqVO().setSpuId(spuId).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
assertEquals(2, result3.getTotal()); assertEquals(2, result3.getTotal());
// 测试分页 tab count // 测试分页 tab count

View File

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
import javax.validation.Valid;
/**
* 拼团活动 API 接口
*
* @author HUIHUI
*/
public interface CombinationApi {
/**
* 创建开团记录
*
* @param reqDTO 请求 DTO
*/
void createRecord(@Valid CombinationRecordReqDTO reqDTO);
/**
* 获取开团记录状态
*
* @param userId 用户编号
* @param orderId 订单编号
*/
boolean validateRecordStatusIsSuccess(Long userId, Long orderId);
/**
* 更新开团记录状态
*
* @param userId 用户编号
* @param orderId 订单编号
* @param status 状态值
*/
void updateRecordStatus(Long userId, Long orderId, Integer status);
/**
* 更新开团记录状态和开始时间
*
* @param userId 用户编号
* @param orderId 订单编号
* @param status 状态值
* @return
*/
void updateRecordStatusAndStartTime(Long userId, Long orderId, Integer status);
}

View File

@ -0,0 +1,77 @@
package cn.iocoder.yudao.module.promotion.api.combination.dto;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 拼团记录 Request DTO
*
* @author HUIHUI
*/
@Data
public class CombinationRecordReqDTO {
/**
* 拼团活动编号
*/
@NotNull(message = "拼团活动编号不能为空")
private Long activityId;
/**
* spu 编号
*/
@NotNull(message = "spu 编号不能为空")
private Long spuId;
/**
* sku 编号
*/
@NotNull(message = "sku 编号不能为空")
private Long skuId;
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
/**
* 订单编号
*/
@NotNull(message = "订单编号不能为空")
private Long orderId;
/**
* 团长编号
*/
@NotNull(message = "团长编号不能为空")
private Long headId;
/**
* 商品名字
*/
@NotEmpty(message = "商品名字不能为空")
private String spuName;
/**
* 商品图片
*/
@NotEmpty(message = "商品图片不能为空")
private String picUrl;
/**
* 拼团商品单价
*/
@NotNull(message = "拼团商品单价不能为空")
private Integer combinationPrice;
/**
* 用户昵称
*/
@NotEmpty(message = "用户昵称不能为空")
private String nickname;
/**
* 用户头像
*/
@NotEmpty(message = "用户头像不能为空")
private String avatar;
/**
* 开团状态正在开团 拼团成功 拼团失败 TODO 等待支付
*/
@NotNull(message = "开团状态不能为空")
private Integer status;
}

View File

@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/** /**
* Promotion 错误码枚举类 * Promotion 错误码枚举类
* * <p>
* promotion 系统使用 1-013-000-000 * promotion 系统使用 1-013-000-000
*/ */
public interface ErrorCodeConstants { public interface ErrorCodeConstants {
@ -58,5 +58,12 @@ public interface ErrorCodeConstants {
ErrorCode SECKILL_TIME_CONFLICTS = new ErrorCode(1013009001, "秒杀时段冲突"); ErrorCode SECKILL_TIME_CONFLICTS = new ErrorCode(1013009001, "秒杀时段冲突");
ErrorCode SECKILL_TIME_EQUAL = new ErrorCode(1013009002, "秒杀时段开始时间和结束时间不能相等"); ErrorCode SECKILL_TIME_EQUAL = new ErrorCode(1013009002, "秒杀时段开始时间和结束时间不能相等");
ErrorCode SECKILL_START_TIME_BEFORE_END_TIME = new ErrorCode(1013009003, "秒杀时段开始时间不能在结束时间之后"); ErrorCode SECKILL_START_TIME_BEFORE_END_TIME = new ErrorCode(1013009003, "秒杀时段开始时间不能在结束时间之后");
ErrorCode SECKILL_TIME_DISABLE = new ErrorCode(1013009004, "秒杀时段已关闭");
// ========== 拼团活动 1013010000 ==========
ErrorCode COMBINATION_ACTIVITY_NOT_EXISTS = new ErrorCode(1013010000, "拼团活动不存在");
ErrorCode COMBINATION_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013010001, "存在商品参加了其它拼团活动");
ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE = new ErrorCode(1013010002, "拼团活动已关闭不能修改");
ErrorCode COMBINATION_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013010003, "拼团活动未关闭或未结束,不能删除");
ErrorCode COMBINATION_RECORD_NOT_EXISTS = new ErrorCode(1013010004, "拼团不存在");
} }

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.promotion.enums.combination;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 拼团状态枚举
*
* @author HUIHUI
*/
@AllArgsConstructor
@Getter
public enum CombinationRecordStatusEnum implements IntArrayValuable {
NOT_PAY(0, "未付款"),
ONGOING(1, "进行中"),
SUCCESS(2, "拼团成功"),
FAILED(3, "拼团失败");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CombinationRecordStatusEnum::getStatus).toArray();
/**
* 状态值
*/
private final Integer status;
/**
* 状态名
*/
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 拼团活动 API 实现类
*
* @author HUIHUI
*/
@Service
public class CombinationApiImpl implements CombinationApi {
@Resource
private CombinationActivityService activityService;
@Override
public void createRecord(CombinationRecordReqDTO reqDTO) {
activityService.createRecord(reqDTO);
}
@Override
public boolean validateRecordStatusIsSuccess(Long userId, Long orderId) {
return activityService.validateRecordStatusIsSuccess(userId, orderId);
}
@Override
public void updateRecordStatus(Long userId, Long orderId, Integer status) {
activityService.updateRecordStatusByUserIdAndOrderId(userId, orderId, status);
}
@Override
public void updateRecordStatusAndStartTime(Long userId, Long orderId, Integer status) {
activityService.updateRecordStatusAndStartTimeByUserIdAndOrderId(userId, orderId, status, LocalDateTime.now());
}
}

View File

@ -0,0 +1,112 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination;
import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.*;
import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
@Tag(name = "管理后台 - 拼团活动")
@RestController
@RequestMapping("/promotion/combination-activity")
@Validated
public class CombinationActivityController {
@Resource
private CombinationActivityService combinationActivityService;
@Resource
private ProductSpuApi spuApi;
@PostMapping("/create")
@Operation(summary = "创建拼团活动")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:create')")
public CommonResult<Long> createCombinationActivity(@Valid @RequestBody CombinationActivityCreateReqVO createReqVO) {
return success(combinationActivityService.createCombinationActivity(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新拼团活动")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:update')")
public CommonResult<Boolean> updateCombinationActivity(@Valid @RequestBody CombinationActivityUpdateReqVO updateReqVO) {
combinationActivityService.updateCombinationActivity(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除拼团活动")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:delete')")
public CommonResult<Boolean> deleteCombinationActivity(@RequestParam("id") Long id) {
combinationActivityService.deleteCombinationActivity(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得拼团活动")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
public CommonResult<CombinationActivityRespVO> getCombinationActivity(@RequestParam("id") Long id) {
CombinationActivityDO combinationActivity = combinationActivityService.getCombinationActivity(id);
List<CombinationProductDO> productDOs = combinationActivityService.getProductsByActivityIds(CollectionUtil.newArrayList(id));
return success(CombinationActivityConvert.INSTANCE.convert(combinationActivity, productDOs));
}
@GetMapping("/list")
@Operation(summary = "获得拼团活动列表")
@Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
public CommonResult<List<CombinationActivityRespVO>> getCombinationActivityList(@RequestParam("ids") Collection<Long> ids) {
List<CombinationActivityDO> list = combinationActivityService.getCombinationActivityList(ids);
return success(CombinationActivityConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@Operation(summary = "获得拼团活动分页")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
public CommonResult<PageResult<CombinationActivityRespVO>> getCombinationActivityPage(@Valid CombinationActivityPageReqVO pageVO) {
PageResult<CombinationActivityDO> pageResult = combinationActivityService.getCombinationActivityPage(pageVO);
Set<Long> aIds = CollectionUtils.convertSet(pageResult.getList(), CombinationActivityDO::getId);
List<CombinationProductDO> productDOs = combinationActivityService.getProductsByActivityIds(aIds);
Set<Long> spuIds = CollectionUtils.convertSet(pageResult.getList(), CombinationActivityDO::getSpuId);
List<ProductSpuRespDTO> spuList = spuApi.getSpuList(spuIds);
return success(CombinationActivityConvert.INSTANCE.convertPage(pageResult, productDOs, spuList));
}
@GetMapping("/export-excel")
@Operation(summary = "导出拼团活动 Excel")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:export')")
@OperateLog(type = EXPORT)
public void exportCombinationActivityExcel(@Valid CombinationActivityExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<CombinationActivityDO> list = combinationActivityService.getCombinationActivityList(exportReqVO);
// 导出 Excel
List<CombinationActivityExcelVO> datas = CombinationActivityConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "拼团活动.xls", "数据", CombinationActivityExcelVO.class, datas);
}
}

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 拼团活动 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class CombinationActivityBaseVO {
@Schema(description = "拼团名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "越拼越省钱")
@NotNull(message = "拼团名称不能为空")
private String name;
@Schema(description = "商品 SPU 编号关联 ProductSpuDO 的 id", example = "[1,2,3]")
@NotNull(message = "拼团商品不能为空")
private Long spuId;
@Schema(description = "总限购数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "16218")
@NotNull(message = "总限购数量不能为空")
private Integer totalLimitCount;
@Schema(description = "单次限购数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "28265")
@NotNull(message = "单次限购数量不能为空")
private Integer singleLimitCount;
@Schema(description = "活动时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]")
@NotNull(message = "活动时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activityTime;
@Schema(description = "开团人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
@NotNull(message = "开团人数不能为空")
private Integer userSize;
@Schema(description = "限制时长(小时)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "限制时长(小时)不能为空")
private Integer limitDuration;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductCreateReqVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import java.util.List;
@Schema(description = "管理后台 - 拼团活动创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationActivityCreateReqVO extends CombinationActivityBaseVO {
@Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
private List<CombinationProductCreateReqVO> products;
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 拼团活动 Excel VO
*
* @author HUIHUI
*/
@Data
public class CombinationActivityExcelVO {
@ExcelProperty("活动编号")
private Long id;
@ExcelProperty("拼团名称")
private String name;
@ExcelProperty("商品 SPU 编号关联 ProductSpuDO 的 id")
private Long spuId;
@ExcelProperty("总限购数量")
private Integer totalLimitCount;
@ExcelProperty("单次限购数量")
private Integer singleLimitCount;
@ExcelProperty("开始时间")
private LocalDateTime startTime;
@ExcelProperty("结束时间")
private LocalDateTime endTime;
@ExcelProperty("开团人数")
private Integer userSize;
@ExcelProperty("开团组数")
private Integer totalNum;
@ExcelProperty("成团组数")
private Integer successNum;
@ExcelProperty("参与人数")
private Integer orderUserCount;
@ExcelProperty("虚拟成团")
private Integer virtualGroup;
@ExcelProperty(value = "活动状态0开启 1关闭", converter = DictConvert.class)
@DictFormat("common_status") // TODO 代码优化建议设置到对应的 XXXDictTypeConstants 枚举类中
private Integer status;
@ExcelProperty("限制时长(小时)")
private Integer limitDuration;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 拼团活动 Excel 导出 Request VO参数和 CombinationActivityPageReqVO 是一致的")
@Data
public class CombinationActivityExportReqVO {
@Schema(description = "拼团名称", example = "赵六")
private String name;
@Schema(description = "商品 SPU 编号关联 ProductSpuDO 的 id", example = "14016")
private Long spuId;
@Schema(description = "总限购数量", example = "16218")
private Integer totalLimitCount;
@Schema(description = "单次限购数量", example = "28265")
private Integer singleLimitCount;
@Schema(description = "开始时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] startTime;
@Schema(description = "结束时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] endTime;
@Schema(description = "开团人数")
private Integer userSize;
@Schema(description = "开团组数")
private Integer totalNum;
@Schema(description = "成团组数")
private Integer successNum;
@Schema(description = "参与人数", example = "25222")
private Integer orderUserCount;
@Schema(description = "虚拟成团")
private Integer virtualGroup;
@Schema(description = "活动状态0开启 1关闭", example = "0")
private Integer status;
@Schema(description = "限制时长(小时)")
private Integer limitDuration;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 拼团活动分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationActivityPageReqVO extends PageParam {
@Schema(description = "拼团名称", example = "赵六")
private String name;
@Schema(description = "商品 SPU 编号关联 ProductSpuDO 的 id", example = "14016")
private Long spuId;
@Schema(description = "总限购数量", example = "16218")
private Integer totalLimitCount;
@Schema(description = "单次限购数量", example = "28265")
private Integer singleLimitCount;
@Schema(description = "开始时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] startTime;
@Schema(description = "结束时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] endTime;
@Schema(description = "开团人数")
private Integer userSize;
@Schema(description = "开团组数")
private Integer totalNum;
@Schema(description = "成团组数")
private Integer successNum;
@Schema(description = "参与人数", example = "25222")
private Integer orderUserCount;
@Schema(description = "虚拟成团")
private Integer virtualGroup;
@Schema(description = "活动状态0开启 1关闭", example = "0")
private Integer status;
@Schema(description = "限制时长(小时)")
private Integer limitDuration;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 拼团活动 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationActivityRespVO extends CombinationActivityBaseVO {
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618大促")
private String spuName;
@Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
private String picUrl;
@Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "开团人数", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "开团人数不能为空")
private Integer userSize;
@Schema(description = "开团组数", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "开团组数不能为空")
private Integer totalNum;
@Schema(description = "成团组数", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "成团组数不能为空")
private Integer successNum;
@Schema(description = "虚拟成团", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "虚拟成团不能为空")
private Integer virtualGroup;
@Schema(description = "活动状态0开启 1关闭", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "活动状态不能为空")
private Integer status;
@Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
private List<CombinationProductRespVO> products;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductUpdateReqVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "管理后台 - 拼团活动更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationActivityUpdateReqVO extends CombinationActivityBaseVO {
@Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
@NotNull(message = "活动编号不能为空")
private Long id;
@Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
private List<CombinationProductUpdateReqVO> products;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 拼团商品 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class CombinationProductBaseVO {
@Schema(description = "商品 spuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "30563")
@NotNull(message = "商品 spuId 不能为空")
private Long spuId;
@Schema(description = "商品 skuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "30563")
@NotNull(message = "商品 skuId 不能为空")
private Long skuId;
@Schema(description = "拼团价格,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "27682")
@NotNull(message = "拼团价格,单位分不能为空")
private Integer activePrice;
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 拼团商品创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationProductCreateReqVO extends CombinationProductBaseVO {
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 拼团商品 Excel VO
*
* @author HUIHUI
*/
@Data
public class CombinationProductExcelVO {
@ExcelProperty("编号")
private Long id;
@ExcelProperty("拼团活动编号")
private Long activityId;
@ExcelProperty("商品 SPU 编号")
private Long spuId;
@ExcelProperty("商品 SKU 编号")
private Long skuId;
@ExcelProperty("拼团商品状态")
private Integer activityStatus;
@ExcelProperty("活动开始时间点")
private LocalDateTime activityStartTime;
@ExcelProperty("活动结束时间点")
private LocalDateTime activityEndTime;
@ExcelProperty("拼团价格,单位分")
private Integer activePrice;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 拼团商品 Excel 导出 Request VO参数和 CombinationProductPageReqVO 是一致的")
@Data
public class CombinationProductExportReqVO {
@Schema(description = "拼团活动编号", example = "6829")
private Long activityId;
@Schema(description = "商品 SPU 编号", example = "18731")
private Long spuId;
@Schema(description = "商品 SKU 编号", example = "31675")
private Long skuId;
@Schema(description = "拼团商品状态", example = "2")
private Integer activityStatus;
@Schema(description = "活动开始时间点")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activityStartTime;
@Schema(description = "活动结束时间点")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activityEndTime;
@Schema(description = "拼团价格,单位分", example = "27682")
private Integer activePrice;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 拼团商品分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationProductPageReqVO extends PageParam {
@Schema(description = "拼团活动编号", example = "6829")
private Long activityId;
@Schema(description = "商品 SPU 编号", example = "18731")
private Long spuId;
@Schema(description = "商品 SKU 编号", example = "31675")
private Long skuId;
@Schema(description = "拼团商品状态", example = "2")
private Integer activityStatus;
@Schema(description = "活动开始时间点")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activityStartTime;
@Schema(description = "活动结束时间点")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activityEndTime;
@Schema(description = "拼团价格,单位分", example = "27682")
private Integer activePrice;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 拼团商品 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationProductRespVO extends CombinationProductBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28322")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 拼团商品更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationProductUpdateReqVO extends CombinationProductBaseVO {
}

View File

@ -2,6 +2,9 @@ package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.*; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.*;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert; import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
@ -18,6 +21,7 @@ import javax.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -29,6 +33,8 @@ public class SeckillActivityController {
@Resource @Resource
private SeckillActivityService seckillActivityService; private SeckillActivityService seckillActivityService;
@Resource
private ProductSpuApi spuApi;
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建秒杀活动") @Operation(summary = "创建秒杀活动")
@ -69,11 +75,8 @@ public class SeckillActivityController {
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')") @PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<SeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) { public CommonResult<SeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
SeckillActivityDO seckillActivity = seckillActivityService.getSeckillActivity(id); SeckillActivityDO seckillActivity = seckillActivityService.getSeckillActivity(id);
if (seckillActivity == null) {
return success(null);
}
List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(id); List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(id);
return success(SeckillActivityConvert.INSTANCE.convert(seckillActivity,seckillProducts)); return success(SeckillActivityConvert.INSTANCE.convert(seckillActivity, seckillProducts));
} }
@GetMapping("/list") @GetMapping("/list")
@ -90,7 +93,11 @@ public class SeckillActivityController {
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')") @PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<PageResult<SeckillActivityRespVO>> getSeckillActivityPage(@Valid SeckillActivityPageReqVO pageVO) { public CommonResult<PageResult<SeckillActivityRespVO>> getSeckillActivityPage(@Valid SeckillActivityPageReqVO pageVO) {
PageResult<SeckillActivityDO> pageResult = seckillActivityService.getSeckillActivityPage(pageVO); PageResult<SeckillActivityDO> pageResult = seckillActivityService.getSeckillActivityPage(pageVO);
return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult)); Set<Long> aIds = CollectionUtils.convertSet(pageResult.getList(), SeckillActivityDO::getId);
List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(aIds);
Set<Long> spuIds = CollectionUtils.convertSet(pageResult.getList(), SeckillActivityDO::getSpuId);
List<ProductSpuRespDTO> spuList = spuApi.getSpuList(spuIds);
return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, seckillProducts, spuList));
} }
} }

View File

@ -19,11 +19,6 @@ import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
* 管理后台 - 秒杀时段相关接口
*
* @author HUIHUI
*/
@Tag(name = "管理后台 - 秒杀时段") @Tag(name = "管理后台 - 秒杀时段")
@RestController @RestController
@RequestMapping("/promotion/seckill-config") @RequestMapping("/promotion/seckill-config")

View File

@ -20,10 +20,9 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@Data @Data
public class SeckillActivityBaseVO { public class SeckillActivityBaseVO {
// TODO @puhui999对应单 spuId
@Schema(description = "秒杀活动商品id", requiredMode = Schema.RequiredMode.REQUIRED, example = "[121,1212]") @Schema(description = "秒杀活动商品id", requiredMode = Schema.RequiredMode.REQUIRED, example = "[121,1212]")
@NotNull(message = "秒杀活动商品不能为空") @NotNull(message = "秒杀活动商品不能为空")
private List<Long> spuIds; private Long spuId;
@Schema(description = "秒杀活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618大促") @Schema(description = "秒杀活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618大促")
@NotNull(message = "秒杀活动名称不能为空") @NotNull(message = "秒杀活动名称不能为空")
@ -56,8 +55,4 @@ public class SeckillActivityBaseVO {
@Schema(description = "单次限够数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "31683") @Schema(description = "单次限够数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "31683")
private Integer singleLimitCount; private Integer singleLimitCount;
// TODO @puhui999这个应该是计算出来的字段只返回create update 不用哈
@Schema(description = "秒杀总库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer totalStock;
} }

View File

@ -6,26 +6,46 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
/**
* 管理后台 - 秒杀活动 Response VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 秒杀活动 Response VO") @Schema(description = "管理后台 - 秒杀活动 Response VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)
public class SeckillActivityRespVO extends SeckillActivityBaseVO { public class SeckillActivityRespVO extends SeckillActivityBaseVO {
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618大促")
private String spuName;
@Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
private String picUrl;
@Schema(description = "秒杀活动id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "秒杀活动id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id; private Long id;
@Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
private List<SeckillProductRespVO> products; // TODO puhui: 考虑是否去除 private List<SeckillProductRespVO> products;
@Schema(description = "活动状态 开启0 禁用1", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") @Schema(description = "活动状态 开启0 禁用1", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status; private Integer status;
@Schema(description = "订单实付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22354")
private Integer totalPrice;
@Schema(description = "秒杀库存", example = "10")
private Integer stock;
@Schema(description = "秒杀总库存", example = "20")
private Integer totalStock;
@Schema(description = "新增订单数", example = "20")
private Integer orderCount;
@Schema(description = "付款人数", example = "20")
private Integer userCount;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
} }

View File

@ -1,9 +1,13 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config; package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.time.LocalTime;
import java.util.List;
/** /**
* 秒杀时段 Base VO提供给添加修改详细的子 VO 使用 * 秒杀时段 Base VO提供给添加修改详细的子 VO 使用
@ -26,12 +30,24 @@ public class SeckillConfigBaseVO {
@NotNull(message = "结束时间点不能为空") @NotNull(message = "结束时间点不能为空")
private String endTime; private String endTime;
@Schema(description = "秒杀", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn") @Schema(description = "秒杀轮播", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/xx.png]")
@NotNull(message = "秒杀图不能为空") @NotNull(message = "秒杀轮播图不能为空")
private String picUrl; private List<String> sliderPicUrls;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "状态不能为空") @NotNull(message = "状态不能为空")
private Integer status; private Integer status;
@AssertTrue(message = "秒杀时段开始时间和结束时间不能相等")
@JsonIgnore
public boolean isValidStartTimeValid() {
return !LocalTime.parse(startTime).equals(LocalTime.parse(endTime));
}
@AssertTrue(message = "秒杀时段开始时间不能在结束时间之后")
@JsonIgnore
public boolean isValidEndTimeValid() {
return !LocalTime.parse(startTime).isAfter(LocalTime.parse(endTime));
}
} }

View File

@ -6,12 +6,6 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
// TODO @puhuiVO 上不写注释已经有注解啦
/**
* 管理后台 - 秒杀时段分页 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 秒杀时段分页 Request VO") @Schema(description = "管理后台 - 秒杀时段分页 Request VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -14,11 +14,6 @@ import javax.validation.constraints.NotNull;
@Data @Data
public class SeckillProductBaseVO { public class SeckillProductBaseVO {
// TODO @puhuispuId 不用传递因为一个秒杀活动只对应一个 SPU ;
@Schema(description = "商品spu_id", requiredMode = Schema.RequiredMode.REQUIRED, example = "30563")
@NotNull(message = "商品spu_id不能为空")
private Long spuId;
@Schema(description = "商品sku_id", requiredMode = Schema.RequiredMode.REQUIRED, example = "30563") @Schema(description = "商品sku_id", requiredMode = Schema.RequiredMode.REQUIRED, example = "30563")
@NotNull(message = "商品sku_id不能为空") @NotNull(message = "商品sku_id不能为空")
private Long skuId; private Long skuId;
@ -31,5 +26,4 @@ public class SeckillProductBaseVO {
@NotNull(message = "秒杀库存不能为空") @NotNull(message = "秒杀库存不能为空")
private Integer stock; private Integer stock;
} }

View File

@ -1,16 +1,9 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product; package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.*; import lombok.Data;
import lombok.EqualsAndHashCode;
/** import lombok.ToString;
* 管理后台 - 秒杀参与商品创建 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 秒杀参与商品创建 Request VO") @Schema(description = "管理后台 - 秒杀参与商品创建 Request VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -1,14 +1,12 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product; package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/**
* 管理后台 - 秒杀参与商品 Response VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 秒杀参与商品 Response VO") @Schema(description = "管理后台 - 秒杀参与商品 Response VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -1,22 +1,14 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product; package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.Data;
import javax.validation.constraints.*; import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* 管理后台 - 秒杀参与商品更新 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 秒杀参与商品更新 Request VO") @Schema(description = "管理后台 - 秒杀参与商品更新 Request VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)
public class SeckillProductUpdateReqVO extends SeckillProductBaseVO { public class SeckillProductUpdateReqVO extends SeckillProductBaseVO {
@Schema(description = "秒杀参与商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "256")
@NotNull(message = "秒杀参与商品编号不能为空")
private Long id;
} }

View File

@ -0,0 +1,128 @@
package cn.iocoder.yudao.module.promotion.convert.combination;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityExcelVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductBaseVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationRecordDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 拼团活动 Convert
*
* @author HUIHUI
*/
@Mapper
public interface CombinationActivityConvert {
CombinationActivityConvert INSTANCE = Mappers.getMapper(CombinationActivityConvert.class);
@Mappings({
@Mapping(target = "startTime", expression = "java(bean.getActivityTime()[0])"),
@Mapping(target = "endTime", expression = "java(bean.getActivityTime()[1])")
})
CombinationActivityDO convert(CombinationActivityCreateReqVO bean);
@Mappings({
@Mapping(target = "startTime", expression = "java(bean.getActivityTime()[0])"),
@Mapping(target = "endTime", expression = "java(bean.getActivityTime()[1])")
})
CombinationActivityDO convert(CombinationActivityUpdateReqVO bean);
@Named("mergeTime")
default LocalDateTime[] mergeTime(LocalDateTime startTime, LocalDateTime endTime) {
// TODO 有点怪第一次这样写 hh
LocalDateTime[] localDateTime = new LocalDateTime[2];
localDateTime[0] = startTime;
localDateTime[1] = endTime;
return localDateTime;
}
@Mappings({
@Mapping(target = "activityTime", expression = "java(mergeTime(bean.getStartTime(),bean.getEndTime()))")
})
CombinationActivityRespVO convert(CombinationActivityDO bean);
CombinationProductRespVO convert(CombinationProductDO bean);
default CombinationActivityRespVO convert(CombinationActivityDO bean, List<CombinationProductDO> productDOs) {
CombinationActivityRespVO respVO = convert(bean);
respVO.setProducts(convertList2(productDOs));
return respVO;
}
List<CombinationActivityRespVO> convertList(List<CombinationActivityDO> list);
PageResult<CombinationActivityRespVO> convertPage(PageResult<CombinationActivityDO> page);
default PageResult<CombinationActivityRespVO> convertPage(PageResult<CombinationActivityDO> page, List<CombinationProductDO> productDOList, List<ProductSpuRespDTO> spuList) {
Map<Long, ProductSpuRespDTO> spuMap = CollectionUtils.convertMap(spuList, ProductSpuRespDTO::getId, c -> c);
PageResult<CombinationActivityRespVO> pageResult = convertPage(page);
pageResult.getList().forEach(item -> {
item.setSpuName(spuMap.get(item.getSpuId()).getName());
item.setPicUrl(spuMap.get(item.getSpuId()).getPicUrl());
item.setProducts(convertList2(productDOList));
});
return pageResult;
}
List<CombinationProductRespVO> convertList2(List<CombinationProductDO> productDOs);
List<CombinationActivityExcelVO> convertList02(List<CombinationActivityDO> list);
@Mappings({
@Mapping(target = "id", ignore = true),
@Mapping(target = "activityId", source = "activityDO.id"),
@Mapping(target = "spuId", source = "activityDO.spuId"),
@Mapping(target = "skuId", source = "vo.skuId"),
@Mapping(target = "activePrice", source = "vo.activePrice"),
@Mapping(target = "activityStartTime", source = "activityDO.startTime"),
@Mapping(target = "activityEndTime", source = "activityDO.endTime")
})
CombinationProductDO convert(CombinationActivityDO activityDO, CombinationProductBaseVO vo);
default List<CombinationProductDO> convertList(CombinationActivityDO activityDO, List<? extends CombinationProductBaseVO> products) {
List<CombinationProductDO> list = new ArrayList<>();
products.forEach(sku -> {
CombinationProductDO productDO = convert(activityDO, sku);
// TODO 状态设置
productDO.setActivityStatus(CommonStatusEnum.ENABLE.getStatus());
list.add(productDO);
});
return list;
}
default List<CombinationProductDO> convertList1(CombinationActivityDO activityDO, List<CombinationProductUpdateReqVO> vos, List<CombinationProductDO> productDOs) {
Map<Long, Long> longMap = CollectionUtils.convertMap(productDOs, CombinationProductDO::getSkuId, CombinationProductDO::getId);
List<CombinationProductDO> list = new ArrayList<>();
vos.forEach(sku -> {
CombinationProductDO productDO = convert(activityDO, sku);
productDO.setId(longMap.get(sku.getSkuId()));
productDO.setActivityStatus(CommonStatusEnum.ENABLE.getStatus());
list.add(productDO);
});
return list;
}
CombinationRecordDO convert(CombinationRecordReqDTO reqDTO);
}

View File

@ -1,20 +1,26 @@
package cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity; package cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityDetailRespVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityDetailRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityRespVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 秒杀活动 Convert * 秒杀活动 Convert
@ -26,8 +32,6 @@ public interface SeckillActivityConvert {
SeckillActivityConvert INSTANCE = Mappers.getMapper(SeckillActivityConvert.class); SeckillActivityConvert INSTANCE = Mappers.getMapper(SeckillActivityConvert.class);
SeckillProductDO convert(SeckillProductCreateReqVO product);
SeckillActivityDO convert(SeckillActivityCreateReqVO bean); SeckillActivityDO convert(SeckillActivityCreateReqVO bean);
SeckillActivityDO convert(SeckillActivityUpdateReqVO bean); SeckillActivityDO convert(SeckillActivityUpdateReqVO bean);
@ -38,53 +42,60 @@ public interface SeckillActivityConvert {
PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page); PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page);
SeckillActivityDetailRespVO convert(SeckillActivityDO seckillActivity, List<SeckillProductDO> seckillProducts); default PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page, List<SeckillProductDO> seckillProducts, List<ProductSpuRespDTO> spuList) {
Map<Long, ProductSpuRespDTO> spuMap = CollectionUtils.convertMap(spuList, ProductSpuRespDTO::getId, c -> c);
PageResult<SeckillActivityRespVO> pageResult = convertPage(page);
/** pageResult.getList().forEach(item -> {
* 比较两个秒杀商品对象是否相等 item.setSpuName(spuMap.get(item.getSpuId()).getName());
* item.setPicUrl(spuMap.get(item.getSpuId()).getPicUrl());
* @param productDO 数据库中的商品 item.setProducts(convertList2(seckillProducts));
* @param productVO 前端传入的商品 });
* @return 是否匹配 return pageResult;
*/
default boolean isEquals(SeckillProductDO productDO, SeckillProductCreateReqVO productVO) {
return ObjectUtil.equals(productDO.getSpuId(), 1) // TODO puhui再看看
&& ObjectUtil.equals(productDO.getSkuId(), productVO.getSkuId())
&& ObjectUtil.equals(productDO.getSeckillPrice(), productVO.getSeckillPrice());
//&& ObjectUtil.equals(productDO.getQuota(), productVO.getQuota())
//&& ObjectUtil.equals(productDO.getLimitCount(), productVO.getLimitCount());
} }
/** SeckillActivityDetailRespVO convert1(SeckillActivityDO seckillActivity);
* 比较两个秒杀商品对象是否相等
* default SeckillActivityDetailRespVO convert(SeckillActivityDO seckillActivity, List<SeckillProductDO> seckillProducts) {
* @param productDO 商品1 SeckillActivityDetailRespVO respVO = convert1(seckillActivity);
* @param productVO 商品2 respVO.setProducts(convertList2(seckillProducts));
* @return 是否匹配 return respVO;
*/
default boolean isEquals(SeckillProductDO productDO, SeckillProductDO productVO) {
return ObjectUtil.equals(productDO.getSpuId(), productVO.getSpuId())
&& ObjectUtil.equals(productDO.getSkuId(), productVO.getSkuId())
&& ObjectUtil.equals(productDO.getSeckillPrice(), productVO.getSeckillPrice());
//&& ObjectUtil.equals(productDO.getQuota(), productVO.getQuota())
//&& ObjectUtil.equals(productDO.getLimitCount(), productVO.getLimitCount());
} }
default List<SeckillProductDO> convertList(SeckillActivityDO seckillActivity, List<SeckillProductCreateReqVO> products) { @Mappings({
@Mapping(target = "id", ignore = true),
@Mapping(target = "activityId", source = "activityDO.id"),
@Mapping(target = "configIds", source = "activityDO.configIds"),
@Mapping(target = "spuId", source = "activityDO.spuId"),
@Mapping(target = "skuId", source = "vo.skuId"),
@Mapping(target = "seckillPrice", source = "vo.seckillPrice"),
@Mapping(target = "stock", source = "vo.stock"),
@Mapping(target = "activityStartTime", source = "activityDO.startTime"),
@Mapping(target = "activityEndTime", source = "activityDO.endTime")
})
SeckillProductDO convert(SeckillActivityDO activityDO, SeckillProductBaseVO vo);
default List<SeckillProductDO> convertList(SeckillActivityDO activityDO, List<? extends SeckillProductBaseVO> products) {
List<SeckillProductDO> list = new ArrayList<>(); List<SeckillProductDO> list = new ArrayList<>();
products.forEach(sku -> { products.forEach(sku -> {
SeckillProductDO productDO = new SeckillProductDO(); SeckillProductDO productDO = convert(activityDO, sku);
productDO.setActivityId(seckillActivity.getId());
productDO.setConfigIds(seckillActivity.getConfigIds());
productDO.setSpuId(sku.getSpuId());
productDO.setSkuId(sku.getSkuId());
productDO.setSeckillPrice(sku.getSeckillPrice());
productDO.setStock(sku.getStock());
productDO.setActivityStatus(CommonStatusEnum.ENABLE.getStatus()); productDO.setActivityStatus(CommonStatusEnum.ENABLE.getStatus());
productDO.setActivityStartTime(seckillActivity.getStartTime()); list.add(productDO);
productDO.setActivityEndTime(seckillActivity.getEndTime());
}); });
return list; return list;
} }
default List<SeckillProductDO> convertList1(SeckillActivityDO activityDO, List<SeckillProductUpdateReqVO> vos, List<SeckillProductDO> productDOs) {
Map<Long, Long> longMap = CollectionUtils.convertMap(productDOs, SeckillProductDO::getSkuId, SeckillProductDO::getId);
List<SeckillProductDO> list = new ArrayList<>();
vos.forEach(sku -> {
SeckillProductDO productDO = convert(activityDO, sku);
productDO.setId(longMap.get(sku.getSkuId()));
productDO.setActivityStatus(CommonStatusEnum.ENABLE.getStatus());
list.add(productDO);
});
return list;
}
List<SeckillProductRespVO> convertList2(List<SeckillProductDO> productDOs);
} }

View File

@ -0,0 +1,87 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
/**
* 拼团活动 DO
*
* @author HUIHUI
*/
@TableName("promotion_combination_activity")
@KeySequence("promotion_combination_activity_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CombinationActivityDO extends BaseDO {
/**
* 活动编号
*/
@TableId
private Long id;
/**
* 拼团名称
*/
private String name;
/**
* 商品 SPU 编号关联 ProductSpuDO id
*/
private Long spuId;
/**
* 总限购数量
*/
private Integer totalLimitCount;
/**
* 单次限购数量
*/
private Integer singleLimitCount;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* 几人团
*/
private Integer userSize;
/**
* 开团组数
*/
private Integer totalNum;
/**
* 成团组数
*/
private Integer successNum;
/**
* 参与人数
*/
private Integer orderUserCount;
/**
* 虚拟成团
*/
private Integer virtualGroup;
/**
* 活动状态0开启 1关闭
* <p>
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 限制时长小时
*/
private Integer limitDuration;
}

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
/**
* 拼团商品 DO
*
* @author HUIHUI
*/
@TableName("promotion_combination_product")
@KeySequence("promotion_combination_product_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CombinationProductDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 拼团活动编号
*/
private Long activityId;
/**
* 商品 SPU 编号
*/
private Long spuId;
/**
* 商品 SKU 编号
*/
private Long skuId;
/**
* 拼团商品状态
*/
private Integer activityStatus;
/**
* 活动开始时间点
*/
private LocalDateTime activityStartTime;
/**
* 活动结束时间点
*/
private LocalDateTime activityEndTime;
/**
* 拼团价格单位分
*/
private Integer activePrice;
}

View File

@ -0,0 +1,108 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
/**
* 拼团记录 DO
*
* @author HUIHUI
*/
@TableName("promotion_combination_record")
@KeySequence("promotion_combination_record_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CombinationRecordDO extends BaseDO {
@TableId
private Long id;
/**
* 拼团活动编号
*/
private Long activityId;
/**
* spu 编号
*/
private Long spuId;
/**
* sku 编号
*/
private Long skuId;
/**
* 用户编号
*/
private Long userId;
/**
* 订单编号
*/
private Long orderId;
/**
* 团长编号
* <p>
* 关联 {@link CombinationRecordDO#getUserId()}
*/
private Long headId;
/**
* 商品名字
*/
private String spuName;
/**
* 商品图片
*/
private String picUrl;
/**
* 拼团商品单价
*/
private Integer combinationPrice;
/**
* 用户昵称
*/
private String nickname;
/**
* 用户头像
*/
private String avatar;
/**
* 开团状态
*
* 关联 {@link CombinationRecordStatusEnum}
*/
private Integer status;
/**
* 是否虚拟成团
*/
private Boolean virtualGroup;
/**
* 过期时间单位小时
* 关联关联 {@link CombinationActivityDO#getLimitDuration()}
*/
private Integer expireTime;
/**
* 开始时间 (订单付款后开始的时间)
*/
private LocalDateTime startTime;
/**
* 结束时间成团时间/失败时间
*/
private LocalDateTime endTime;
/**
* 开团需要人数
* 关联 {@link CombinationActivityDO#getUserSize()}
*/
private Integer userSize;
/**
* 已加入拼团人数
*/
private Integer userCount;
}

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.combination;

View File

@ -34,8 +34,7 @@ public class SeckillActivityDO extends BaseDO {
/** /**
* 秒杀活动商品 * 秒杀活动商品
*/ */
@TableField(typeHandler = LongListTypeHandler.class) private Long spuId;
private List<Long> spuIds;
/** /**
* 秒杀活动名称 * 秒杀活动名称
*/ */

View File

@ -3,22 +3,27 @@ package cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.EqualsAndHashCode; import lombok.*;
import lombok.ToString;
import java.util.List;
/** /**
* 秒杀时段 DO * 秒杀时段 DO
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@TableName("promotion_seckill_config") @TableName(value = "promotion_seckill_config", autoResultMap = true)
@KeySequence("promotion_seckill_config_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写 @KeySequence("promotion_seckill_config_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SeckillConfigDO extends BaseDO { public class SeckillConfigDO extends BaseDO {
/** /**
@ -38,14 +43,14 @@ public class SeckillConfigDO extends BaseDO {
* 结束时间点 * 结束时间点
*/ */
private String endTime; private String endTime;
// TODO puhui999应该是轮播图 private List<String> sliderPicUrls;
/** /**
* 秒杀 * 秒杀轮播
*/ */
private String picUrl; @TableField(typeHandler = JacksonTypeHandler.class)
private List<String> sliderPicUrls;
/** /**
* 状态 * 状态
* * <p>
* 枚举 {@link CommonStatusEnum 对应的类} * 枚举 {@link CommonStatusEnum 对应的类}
*/ */
private Integer status; private Integer status;

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity;
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.combination.vo.activity.CombinationActivityExportReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 拼团活动 Mapper
*
* @author HUIHUI
*/
@Mapper
public interface CombinationActivityMapper extends BaseMapperX<CombinationActivityDO> {
default PageResult<CombinationActivityDO> selectPage(CombinationActivityPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<CombinationActivityDO>()
.likeIfPresent(CombinationActivityDO::getName, reqVO.getName())
.orderByDesc(CombinationActivityDO::getId));
}
default List<CombinationActivityDO> selectList(CombinationActivityExportReqVO reqVO) {
return selectList(new LambdaQueryWrapperX<CombinationActivityDO>()
.likeIfPresent(CombinationActivityDO::getName, reqVO.getName())
.eqIfPresent(CombinationActivityDO::getStatus, reqVO.getStatus())
.orderByDesc(CombinationActivityDO::getId));
}
default List<CombinationActivityDO> selectListByStatus(Integer status) {
return selectList(CombinationActivityDO::getStatus, status);
}
}

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity;
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.combination.vo.product.CombinationProductExportReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* 拼团商品 Mapper
*
* @author HUIHUI
*/
@Mapper
public interface CombinationProductMapper extends BaseMapperX<CombinationProductDO> {
default PageResult<CombinationProductDO> selectPage(CombinationProductPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<CombinationProductDO>()
.eqIfPresent(CombinationProductDO::getActivityId, reqVO.getActivityId())
.eqIfPresent(CombinationProductDO::getSpuId, reqVO.getSpuId())
.eqIfPresent(CombinationProductDO::getSkuId, reqVO.getSkuId())
.eqIfPresent(CombinationProductDO::getActivityStatus, reqVO.getActivityStatus())
.betweenIfPresent(CombinationProductDO::getActivityStartTime, reqVO.getActivityStartTime())
.betweenIfPresent(CombinationProductDO::getActivityEndTime, reqVO.getActivityEndTime())
.eqIfPresent(CombinationProductDO::getActivePrice, reqVO.getActivePrice())
.betweenIfPresent(CombinationProductDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(CombinationProductDO::getId));
}
default List<CombinationProductDO> selectList(CombinationProductExportReqVO reqVO) {
return selectList(new LambdaQueryWrapperX<CombinationProductDO>()
.eqIfPresent(CombinationProductDO::getActivityId, reqVO.getActivityId())
.eqIfPresent(CombinationProductDO::getSpuId, reqVO.getSpuId())
.eqIfPresent(CombinationProductDO::getSkuId, reqVO.getSkuId())
.eqIfPresent(CombinationProductDO::getActivityStatus, reqVO.getActivityStatus())
.betweenIfPresent(CombinationProductDO::getActivityStartTime, reqVO.getActivityStartTime())
.betweenIfPresent(CombinationProductDO::getActivityEndTime, reqVO.getActivityEndTime())
.eqIfPresent(CombinationProductDO::getActivePrice, reqVO.getActivePrice())
.betweenIfPresent(CombinationProductDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(CombinationProductDO::getId));
}
default List<CombinationProductDO> selectListByActivityIds(Collection<Long> ids) {
return selectList(CombinationProductDO::getActivityId, ids);
}
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationRecordDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 拼团记录 Mapper
*
* @author HUIHUI
*/
@Mapper
public interface CombinationRecordMapper extends BaseMapperX<CombinationRecordDO> {
default CombinationRecordDO selectRecord(Long userId, Long orderId) {
return selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getOrderId, orderId);
}
default List<CombinationRecordDO> selectListByHeadIdAndStatus(Long headId, Integer status) {
return selectList(new LambdaQueryWrapperX<CombinationRecordDO>().eq(CombinationRecordDO::getHeadId, headId)
.eq(CombinationRecordDO::getStatus, status));
}
default List<CombinationRecordDO> selectListByStatus(Integer status) {
return selectList(new LambdaQueryWrapperX<CombinationRecordDO>().eq(CombinationRecordDO::getStatus, status));
}
}

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.combination;

View File

@ -21,6 +21,10 @@ public interface SeckillProductMapper extends BaseMapperX<SeckillProductDO> {
return selectList(SeckillProductDO::getActivityId, id); return selectList(SeckillProductDO::getActivityId, id);
} }
default List<SeckillProductDO> selectListByActivityId(Collection<Long> ids) {
return selectList(SeckillProductDO::getActivityId, ids);
}
default List<SeckillProductDO> selectListBySkuIds(Collection<Long> skuIds) { default List<SeckillProductDO> selectListBySkuIds(Collection<Long> skuIds) {
return selectList(SeckillProductDO::getSkuId, skuIds); return selectList(SeckillProductDO::getSkuId, skuIds);
} }

View File

@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.Seck
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig.SeckillConfigDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig.SeckillConfigDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper @Mapper
public interface SeckillConfigMapper extends BaseMapperX<SeckillConfigDO> { public interface SeckillConfigMapper extends BaseMapperX<SeckillConfigDO> {
@ -17,4 +19,9 @@ public interface SeckillConfigMapper extends BaseMapperX<SeckillConfigDO> {
.orderByDesc(SeckillConfigDO::getId)); .orderByDesc(SeckillConfigDO::getId));
} }
default List<SeckillConfigDO> selectListByStatus(Integer status) {
return selectList(SeckillConfigDO::getStatus, status);
}
} }

View File

@ -0,0 +1,121 @@
package cn.iocoder.yudao.module.promotion.service.combination;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityExportReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
/**
* 拼团活动 Service 接口
*
* @author HUIHUI
*/
public interface CombinationActivityService {
/**
* 创建拼团活动
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createCombinationActivity(@Valid CombinationActivityCreateReqVO createReqVO);
/**
* 更新拼团活动
*
* @param updateReqVO 更新信息
*/
void updateCombinationActivity(@Valid CombinationActivityUpdateReqVO updateReqVO);
/**
* 删除拼团活动
*
* @param id 编号
*/
void deleteCombinationActivity(Long id);
/**
* 获得拼团活动
*
* @param id 编号
* @return 拼团活动
*/
CombinationActivityDO getCombinationActivity(Long id);
/**
* 获得拼团活动列表
*
* @param ids 编号
* @return 拼团活动列表
*/
List<CombinationActivityDO> getCombinationActivityList(Collection<Long> ids);
/**
* 获得拼团活动分页
*
* @param pageReqVO 分页查询
* @return 拼团活动分页
*/
PageResult<CombinationActivityDO> getCombinationActivityPage(CombinationActivityPageReqVO pageReqVO);
/**
* 获得拼团活动列表, 用于 Excel 导出
*
* @param exportReqVO 查询条件
* @return 拼团活动列表
*/
List<CombinationActivityDO> getCombinationActivityList(CombinationActivityExportReqVO exportReqVO);
/**
* 获得拼团活动商品列表
*
* @param ids 拼团活动 ids
* @return 拼团活动的商品列表
*/
List<CombinationProductDO> getProductsByActivityIds(Collection<Long> ids);
/**
* 更新拼团状态
*
* @param userId 用户编号
* @param orderId 订单编号
* @param status 状态
*/
void updateRecordStatusByUserIdAndOrderId(Long userId, Long orderId, Integer status);
/**
* 更新拼团状态和开始时间
*
* @param userId 用户编号
* @param orderId 订单编号
* @param status 状态
* @param startTime 开始时间
* @return
*/
void updateRecordStatusAndStartTimeByUserIdAndOrderId(Long userId, Long orderId, Integer status, LocalDateTime startTime);
/**
* 创建拼团记录
*
* @param reqDTO 创建信息
*/
void createRecord(CombinationRecordReqDTO reqDTO);
/**
* 获得拼团状态
*
* @param userId 用户编号
* @param orderId 订单编号
* @return 拼团状态
*/
boolean validateRecordStatusIsSuccess(Long userId, Long orderId);
}

View File

@ -0,0 +1,281 @@
package cn.iocoder.yudao.module.promotion.service.combination;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityExportReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationRecordDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity.CombinationActivityMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity.CombinationProductMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity.CombinationRecordMapper;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.promotion.util.PromotionUtils.validateProductSkuExistence;
/**
* 拼团活动 Service 实现类
*
* @author HUIHUI
*/
@Service
@Validated
public class CombinationActivityServiceImpl implements CombinationActivityService {
@Resource
private CombinationActivityMapper combinationActivityMapper;
@Resource
private CombinationRecordMapper recordMapper;
@Resource
private CombinationProductMapper combinationProductMapper;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private ProductSkuApi productSkuApi;
@Override
public Long createCombinationActivity(CombinationActivityCreateReqVO createReqVO) {
// 校验商品 SPU 是否存在是否参加的别的活动
validateProductCombinationConflict(createReqVO.getSpuId(), null);
// 获取所选 spu下的所有 sku
List<ProductSkuRespDTO> skus = productSkuApi.getSkuListBySpuId(CollectionUtil.newArrayList(createReqVO.getSpuId()));
// 校验商品 sku 是否存在
validateProductSkuExistence(skus, createReqVO.getProducts(), CombinationProductCreateReqVO::getSkuId);
// TODO 艿艿 有个小问题现在有活动时间和限制时长活动时间的结束时间早于设置的限制时间怎么算状态比如
// 活动时间 2023-08-05 15:00:00 - 2023-08-05 15:20:00 限制时长 2小时那么活动时间结束就结束还是加时到满两小时
// 插入拼团活动
CombinationActivityDO activityDO = CombinationActivityConvert.INSTANCE.convert(createReqVO);
// TODO 营销相关属性初始化
activityDO.setTotalNum(0);
activityDO.setSuccessNum(0);
activityDO.setOrderUserCount(0);
activityDO.setVirtualGroup(0);
activityDO.setStatus(CommonStatusEnum.ENABLE.getStatus());
combinationActivityMapper.insert(activityDO);
// 插入商品
List<CombinationProductDO> productDOs = CombinationActivityConvert.INSTANCE.convertList(activityDO, createReqVO.getProducts());
combinationProductMapper.insertBatch(productDOs);
// 返回
return activityDO.getId();
}
private void validateProductCombinationConflict(Long spuId, Long activityId) {
// 校验商品 spu 是否存在
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(CollUtil.newArrayList(spuId));
if (CollUtil.isEmpty(spuList)) {
throw exception(SPU_NOT_EXISTS);
}
// 查询所有开启的拼团活动
List<CombinationActivityDO> activityDOs = combinationActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
// 更新时排除自己
if (activityId != null) {
activityDOs.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
}
// 过滤出所有 spuIds 有交集的活动
List<CombinationActivityDO> doList = CollectionUtils.convertList(activityDOs, c -> c, s -> ObjectUtil.equal(s.getId(), spuId));
if (CollUtil.isNotEmpty(doList)) {
throw exception(COMBINATION_ACTIVITY_SPU_CONFLICTS);
}
}
@Override
public void updateCombinationActivity(CombinationActivityUpdateReqVO updateReqVO) {
// 校验存在
CombinationActivityDO activityDO = validateCombinationActivityExists(updateReqVO.getId());
// 校验状态
if (ObjectUtil.equal(activityDO.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
throw exception(COMBINATION_ACTIVITY_STATUS_DISABLE);
}
// 校验商品冲突
validateProductCombinationConflict(updateReqVO.getSpuId(), updateReqVO.getId());
// 获取所选 spu下的所有 sku
List<ProductSkuRespDTO> skus = productSkuApi.getSkuListBySpuId(CollectionUtil.newArrayList(updateReqVO.getSpuId()));
// 校验商品 sku 是否存在
validateProductSkuExistence(skus, updateReqVO.getProducts(), CombinationProductUpdateReqVO::getSkuId);
// 更新
CombinationActivityDO updateObj = CombinationActivityConvert.INSTANCE.convert(updateReqVO);
combinationActivityMapper.updateById(updateObj);
// 更新商品
updateCombinationProduct(updateObj, updateReqVO.getProducts());
}
/**
* 更新秒杀商品
* TODO 更新商品要不要封装成通用方法
*
* @param updateObj DO
* @param products 商品配置
*/
private void updateCombinationProduct(CombinationActivityDO updateObj, List<CombinationProductUpdateReqVO> products) {
List<CombinationProductDO> combinationProductDOs = combinationProductMapper.selectListByActivityIds(CollUtil.newArrayList(updateObj.getId()));
// 数据库中的活动商品
Set<Long> convertSet = CollectionUtils.convertSet(combinationProductDOs, CombinationProductDO::getSkuId);
// 前端传过来的活动商品
Set<Long> convertSet1 = CollectionUtils.convertSet(products, CombinationProductUpdateReqVO::getSkuId);
// 删除后台存在的前端不存在的商品
List<Long> d = CollectionUtils.filterList(convertSet, item -> !convertSet1.contains(item));
if (CollUtil.isNotEmpty(d)) {
combinationProductMapper.deleteBatchIds(d);
}
// 前端存在的后端不存在的商品
List<Long> c = CollectionUtils.filterList(convertSet1, item -> !convertSet.contains(item));
if (CollUtil.isNotEmpty(c)) {
List<CombinationProductUpdateReqVO> vos = CollectionUtils.filterList(products, item -> c.contains(item.getSkuId()));
List<CombinationProductDO> productDOs = CombinationActivityConvert.INSTANCE.convertList(updateObj, vos);
combinationProductMapper.insertBatch(productDOs);
}
// 更新已存在的商品
List<Long> u = CollectionUtils.filterList(convertSet1, convertSet::contains);
if (CollUtil.isNotEmpty(u)) {
List<CombinationProductUpdateReqVO> vos = CollectionUtils.filterList(products, item -> u.contains(item.getSkuId()));
List<CombinationProductDO> productDOs = CombinationActivityConvert.INSTANCE.convertList1(updateObj, vos, combinationProductDOs);
combinationProductMapper.updateBatch(productDOs);
}
}
@Override
public void deleteCombinationActivity(Long id) {
// 校验存在
CombinationActivityDO activityDO = validateCombinationActivityExists(id);
// 校验状态
if (ObjectUtil.equal(activityDO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
throw exception(COMBINATION_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END);
}
// 删除
combinationActivityMapper.deleteById(id);
}
private CombinationActivityDO validateCombinationActivityExists(Long id) {
CombinationActivityDO activityDO = combinationActivityMapper.selectById(id);
if (activityDO == null) {
throw exception(COMBINATION_ACTIVITY_NOT_EXISTS);
}
return activityDO;
}
@Override
public CombinationActivityDO getCombinationActivity(Long id) {
return validateCombinationActivityExists(id);
}
@Override
public List<CombinationActivityDO> getCombinationActivityList(Collection<Long> ids) {
return combinationActivityMapper.selectBatchIds(ids);
}
@Override
public PageResult<CombinationActivityDO> getCombinationActivityPage(CombinationActivityPageReqVO pageReqVO) {
return combinationActivityMapper.selectPage(pageReqVO);
}
@Override
public List<CombinationActivityDO> getCombinationActivityList(CombinationActivityExportReqVO exportReqVO) {
return combinationActivityMapper.selectList(exportReqVO);
}
@Override
public List<CombinationProductDO> getProductsByActivityIds(Collection<Long> ids) {
return combinationProductMapper.selectListByActivityIds(ids);
}
@Override
public void updateRecordStatusByUserIdAndOrderId(Long userId, Long orderId, Integer status) {
// 校验拼团是否存在
CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
// 更新状态
recordDO.setStatus(status);
recordMapper.updateById(recordDO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateRecordStatusAndStartTimeByUserIdAndOrderId(Long userId, Long orderId, Integer status, LocalDateTime startTime) {
CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
// 更新状态
recordDO.setStatus(status);
// 更新开始时间
recordDO.setStartTime(startTime);
recordMapper.updateById(recordDO);
// 更新拼团参入人数
List<CombinationRecordDO> recordDOs = recordMapper.selectListByHeadIdAndStatus(recordDO.getHeadId(), status);
if (CollUtil.isNotEmpty(recordDOs)) {
recordDOs.forEach(item -> {
item.setUserCount(recordDOs.size());
// 校验拼团是否满足要求
if (recordDOs.size() >= recordDO.getUserSize()) {
item.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus());
}
});
}
recordMapper.updateBatch(recordDOs);
}
private CombinationRecordDO validateCombinationRecord(Long userId, Long orderId) {
// 校验拼团是否存在
CombinationRecordDO recordDO = recordMapper.selectRecord(userId, orderId);
if (recordDO == null) {
throw exception(COMBINATION_RECORD_NOT_EXISTS);
}
return recordDO;
}
@Override
public void createRecord(CombinationRecordReqDTO reqDTO) {
// 校验拼团活动
CombinationActivityDO activityDO = validateCombinationActivityExists(reqDTO.getActivityId());
CombinationRecordDO recordDO = CombinationActivityConvert.INSTANCE.convert(reqDTO);
recordDO.setVirtualGroup(false);
recordDO.setExpireTime(activityDO.getLimitDuration());
recordDO.setUserSize(activityDO.getUserSize());
recordMapper.insert(recordDO);
}
@Override
public boolean validateRecordStatusIsSuccess(Long userId, Long orderId) {
CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
return ObjectUtil.equal(recordDO.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus());
}
/**
* APP 端获取开团记录
*
* @return 开团记录
*/
public List<CombinationRecordDO> getRecordList() {
return recordMapper.selectListByStatus(CombinationRecordStatusEnum.ONGOING.getStatus());
}
}

View File

@ -79,4 +79,12 @@ public interface SeckillActivityService {
*/ */
List<SeckillProductDO> getSeckillProductListByActivityId(Long id); List<SeckillProductDO> getSeckillProductListByActivityId(Long id);
/**
* 通过活动编号获取活动商品
*
* @param ids 活动编号
* @return 活动商品列表
*/
List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> ids);
} }

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.promotion.service.seckill.seckillactivity; package cn.iocoder.yudao.module.promotion.service.seckill.seckillactivity;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
@ -13,29 +12,28 @@ import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert; import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper; import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper; import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
import cn.iocoder.yudao.module.promotion.service.seckill.seckillconfig.SeckillConfigService; import cn.iocoder.yudao.module.promotion.service.seckill.seckillconfig.SeckillConfigService;
import cn.iocoder.yudao.module.promotion.util.PromotionUtils; import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; 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.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS; import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
import static java.util.Arrays.asList; import static cn.iocoder.yudao.module.promotion.util.PromotionUtils.validateProductSkuExistence;
/** /**
* 秒杀活动 Service 实现类 * 秒杀活动 Service 实现类
@ -58,15 +56,19 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
private ProductSkuApi productSkuApi; private ProductSkuApi productSkuApi;
@Override @Override
@Transactional(rollbackFor = Exception.class)
public Long createSeckillActivity(SeckillActivityCreateReqVO createReqVO) { public Long createSeckillActivity(SeckillActivityCreateReqVO createReqVO) {
// 校验商品秒秒杀时段是否冲突 // 校验商品秒秒杀时段是否冲突
validateProductSpuSeckillConflict(createReqVO.getConfigIds(), createReqVO.getSpuIds()); validateProductSpuSeckillConflict(createReqVO.getConfigIds(), createReqVO.getSpuId(), null);
// 获取所选 spu下的所有 sku
List<ProductSkuRespDTO> skus = productSkuApi.getSkuListBySpuId(CollUtil.newArrayList(createReqVO.getSpuId()));
// 校验商品 sku 是否存在 // 校验商品 sku 是否存在
validateProductSkuExistence(createReqVO.getSpuIds(), createReqVO.getProducts()); validateProductSkuExistence(skus, createReqVO.getProducts(), SeckillProductCreateReqVO::getSkuId);
// 插入秒杀活动 // 插入秒杀活动
SeckillActivityDO activity = SeckillActivityConvert.INSTANCE.convert(createReqVO) SeckillActivityDO activity = SeckillActivityConvert.INSTANCE.convert(createReqVO)
.setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getEndTime())); .setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getEndTime()))
.setTotalStock(CollectionUtils.getSumValue(createReqVO.getProducts(), SeckillProductCreateReqVO::getStock, Integer::sum));
seckillActivityMapper.insert(activity); seckillActivityMapper.insert(activity);
// 插入商品 // 插入商品
List<SeckillProductDO> product = SeckillActivityConvert.INSTANCE.convertList(activity, createReqVO.getProducts()); List<SeckillProductDO> product = SeckillActivityConvert.INSTANCE.convertList(activity, createReqVO.getProducts());
@ -74,139 +76,124 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
return activity.getId(); return activity.getId();
} }
private <T extends SeckillProductBaseVO> void validateProductSkuExistence(List<Long> spuIds, List<T> products) { private void validateProductSpuSeckillConflict(List<Long> configIds, Long spuId, Long activityId) {
// 校验 spu 个数是否相等
// TODO @puhui999不用校验 SPU 只校验 sku 对应的 spuId 是否一致
Set<Long> convertedSpuIds = CollectionUtils.convertSet(products, T::getSpuId);
if (ObjectUtil.notEqual(spuIds.size(), convertedSpuIds.size())) {
throw exception(SKU_NOT_EXISTS);
}
// 获取所选 spu下的所有 sku
// TODO @puhui999变量可以简单一点skus
List<ProductSkuRespDTO> skuRespDTOs = productSkuApi.getSkuListBySpuId(spuIds);
// 校验 sku 个数是否一致
Set<Long> skuIdsSet = CollectionUtils.convertSet(products, T::getSkuId);
Set<Long> skuIdsSet1 = CollectionUtils.convertSet(skuRespDTOs, ProductSkuRespDTO::getId);
if (ObjectUtil.notEqual(skuIdsSet.size(), skuIdsSet1.size())) {
throw exception(SKU_NOT_EXISTS);
}
// 校验 skuId 是否存在
if (!skuIdsSet1.containsAll(skuIdsSet) || !skuIdsSet.containsAll(skuIdsSet1)) {
throw exception(SKU_NOT_EXISTS);
}
}
private void validateProductSpuSeckillConflict(List<Long> configIds, List<Long> spuIds) {
// 校验秒杀时段是否存在 // 校验秒杀时段是否存在
seckillConfigService.validateSeckillConfigExists(configIds); seckillConfigService.validateSeckillConfigExists(configIds);
// 校验商品 spu 是否存在 // 校验商品 spu 是否存在
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(spuIds); List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(CollUtil.newArrayList(spuId));
if (ObjectUtil.notEqual(spuIds.size(), spuList.size())) { if (CollUtil.isEmpty(spuList)) {
throw exception(SPU_NOT_EXISTS); throw exception(SPU_NOT_EXISTS);
} }
// 查询所有开启的秒杀活动 // 查询所有开启的秒杀活动
List<SeckillActivityDO> activityDOs = seckillActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); List<SeckillActivityDO> activityDOs = seckillActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
// 过滤出所有 spuIds 有交集的活动 if (activityId != null) {
List<SeckillActivityDO> doList = activityDOs.stream().filter(s -> { // 更新时移除本活动
// 判断 spu 是否有交集 activityDOs.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
List<Long> spuIdsClone = ArrayUtil.clone(s.getSpuIds());
spuIdsClone.retainAll(spuIds);
if (CollUtil.isEmpty(spuIdsClone)) {
return false;
} }
// 过滤出所有 spuId 有交集的活动
List<SeckillActivityDO> activityDOs1 = CollectionUtils.convertList(activityDOs, c -> c, s -> ObjectUtil.equal(s.getSpuId(), spuId));
if (CollUtil.isNotEmpty(activityDOs1)) {
throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);
}
List<SeckillActivityDO> activityDOs2 = CollectionUtils.convertList(activityDOs, c -> c, s -> {
// 判断秒杀时段是否有交集 // 判断秒杀时段是否有交集
List<Long> configIdsClone = ArrayUtil.clone(s.getConfigIds()); List<Long> configIdsClone = CollUtil.newArrayList(s.getConfigIds());
configIdsClone.retainAll(configIds); configIdsClone.retainAll(configIds);
return CollUtil.isNotEmpty(configIdsClone); return CollUtil.isNotEmpty(configIdsClone);
}).collect(Collectors.toList()); });
if (CollUtil.isNotEmpty(doList)) { if (CollUtil.isNotEmpty(activityDOs2)) {
throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS); throw exception(SECKILL_TIME_CONFLICTS);
} }
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void updateSeckillActivity(SeckillActivityUpdateReqVO updateReqVO) { public void updateSeckillActivity(SeckillActivityUpdateReqVO updateReqVO) {
// 校验存在 // 校验存在
SeckillActivityDO seckillActivity = validateSeckillActivityExists(updateReqVO.getId()); SeckillActivityDO seckillActivity = validateSeckillActivityExists(updateReqVO.getId());
if (CommonStatusEnum.ENABLE.getStatus().equals(seckillActivity.getStatus())) { if (CommonStatusEnum.DISABLE.getStatus().equals(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED); throw exception(SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
} }
// 校验商品是否冲突 // 校验商品是否冲突
validateProductSpuSeckillConflict(updateReqVO.getConfigIds(), updateReqVO.getSpuIds()); validateProductSpuSeckillConflict(updateReqVO.getConfigIds(), updateReqVO.getSpuId(), updateReqVO.getId());
// 获取所选 spu下的所有 sku
List<ProductSkuRespDTO> skus = productSkuApi.getSkuListBySpuId(CollUtil.newArrayList(updateReqVO.getSpuId()));
// 校验商品 sku 是否存在 // 校验商品 sku 是否存在
validateProductSkuExistence(updateReqVO.getSpuIds(), updateReqVO.getProducts()); validateProductSkuExistence(skus, updateReqVO.getProducts(), SeckillProductUpdateReqVO::getSkuId);
// 更新活动 // 更新活动
SeckillActivityDO updateObj = SeckillActivityConvert.INSTANCE.convert(updateReqVO) SeckillActivityDO updateObj = SeckillActivityConvert.INSTANCE.convert(updateReqVO)
.setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getEndTime())); .setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getEndTime()))
.setTotalStock(CollectionUtils.getSumValue(updateReqVO.getProducts(), SeckillProductUpdateReqVO::getStock, Integer::sum));
seckillActivityMapper.updateById(updateObj); seckillActivityMapper.updateById(updateObj);
// 更新商品 // 更新商品
//updateSeckillProduct(updateReqVO); updateSeckillProduct(updateObj, updateReqVO.getProducts());
} }
/** /**
* 更新秒杀商品 * 更新秒杀商品
* 后台查出的数据和前台查出的数据进行遍历
* 1. 对前台数据进行遍历如果不存在于后台的 sku 中需要新增
* 2. 对后台数据进行遍历如果不存在于前台的 sku 中需要删除
* 3. 最后对当前活动商品全部更新更新秒杀时段id列表
* *
* @param updateReqVO 更新的请求VO * @param updateObj DO
* @param products 商品配置
*/ */
private void updateSeckillProduct(SeckillActivityUpdateReqVO updateReqVO) { private void updateSeckillProduct(SeckillActivityDO updateObj, List<SeckillProductUpdateReqVO> products) {
// TODO puhui999要不这里简单一点删除原本的插入新增的不做的这么细致 List<SeckillProductDO> seckillProductDOs = seckillProductMapper.selectListByActivityId(updateObj.getId());
// TODO puhui999后续完善 // 数据库中的活动商品
//List<SeckillProductDO> seckillProductDOs = seckillProductMapper.selectListByActivityId(updateReqVO.getId()); Set<Long> convertSet = CollectionUtils.convertSet(seckillProductDOs, SeckillProductDO::getSkuId);
//List<SeckillProductUpdateReqVO> products = updateReqVO.getProducts(); // 前端传过来的活动商品
Set<Long> convertSet1 = CollectionUtils.convertSet(products, SeckillProductUpdateReqVO::getSkuId);
////计算需要删除的数据 // 删除后台存在的前端不存在的商品
//List<Long> deleteIds = CollectionUtils.convertList(seckillProductDOs, SeckillProductDO::getId, List<Long> d = CollectionUtils.filterList(convertSet, item -> !convertSet1.contains(item));
// seckillProductDO -> products.stream() if (CollUtil.isNotEmpty(d)) {
// .noneMatch(product -> SeckillActivityConvert.INSTANCE.isEquals(seckillProductDO, product))); seckillProductMapper.deleteBatchIds(d);
//if (CollUtil.isNotEmpty(deleteIds)) { }
// seckillProductMapper.deleteBatchIds(deleteIds); // 前端存在的后端不存在的商品
//} List<Long> c = CollectionUtils.filterList(convertSet1, item -> !convertSet.contains(item));
// if (CollUtil.isNotEmpty(c)) {
//// 计算需要新增的数据 List<SeckillProductUpdateReqVO> vos = CollectionUtils.filterList(products, item -> c.contains(item.getSkuId()));
//List<SeckillProductDO> newSeckillProductDOs = CollectionUtils.convertList(products, List<SeckillProductDO> productDOs = SeckillActivityConvert.INSTANCE.convertList(updateObj, vos);
// product -> SeckillActivityConvert.INSTANCE.convert(product).setActivityId(updateReqVO.getId())); seckillProductMapper.insertBatch(productDOs);
//newSeckillProductDOs.removeIf(product -> seckillProductDOs.stream() }
// .anyMatch(seckillProduct -> SeckillActivityConvert.INSTANCE.isEquals(seckillProduct, product))); // 更新已存在的商品
//if (CollUtil.isNotEmpty(newSeckillProductDOs)) { List<Long> u = CollectionUtils.filterList(convertSet1, convertSet::contains);
// seckillProductMapper.insertBatch(newSeckillProductDOs); if (CollUtil.isNotEmpty(u)) {
//} List<SeckillProductUpdateReqVO> vos = CollectionUtils.filterList(products, item -> u.contains(item.getSkuId()));
List<SeckillProductDO> productDOs = SeckillActivityConvert.INSTANCE.convertList1(updateObj, vos, seckillProductDOs);
//全量更新当前活动商品的秒杀时段id列表timeIds seckillProductMapper.updateBatch(productDOs);
seckillProductMapper.updateTimeIdsByActivityId(updateReqVO.getId(), updateReqVO.getConfigIds()); }
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void closeSeckillActivity(Long id) { public void closeSeckillActivity(Long id) {
// TODO 待验证没使用过
// 校验存在 // 校验存在
SeckillActivityDO seckillActivity = this.validateSeckillActivityExists(id); SeckillActivityDO seckillActivity = validateSeckillActivityExists(id);
if (PromotionActivityStatusEnum.CLOSE.getStatus().equals(seckillActivity.getStatus())) { if (CommonStatusEnum.DISABLE.getStatus().equals(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED); throw exception(SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED);
} }
if (PromotionActivityStatusEnum.END.getStatus().equals(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_END);
}
// 更新 // 更新
SeckillActivityDO updateObj = new SeckillActivityDO().setId(id).setStatus(PromotionActivityStatusEnum.CLOSE.getStatus()); SeckillActivityDO updateObj = new SeckillActivityDO().setId(id).setStatus(CommonStatusEnum.DISABLE.getStatus());
seckillActivityMapper.updateById(updateObj); seckillActivityMapper.updateById(updateObj);
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void deleteSeckillActivity(Long id) { public void deleteSeckillActivity(Long id) {
// 校验存在 // 校验存在
SeckillActivityDO seckillActivity = this.validateSeckillActivityExists(id); SeckillActivityDO seckillActivity = this.validateSeckillActivityExists(id);
List<Integer> statuses = asList(PromotionActivityStatusEnum.CLOSE.getStatus(), PromotionActivityStatusEnum.END.getStatus()); if (CommonStatusEnum.ENABLE.getStatus().equals(seckillActivity.getStatus())) {
if (!statuses.contains(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END); throw exception(SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END);
} }
// 删除 // 删除活动
seckillActivityMapper.deleteById(id); seckillActivityMapper.deleteById(id);
// 删除活动商品
List<SeckillProductDO> productDOs = seckillProductMapper.selectListByActivityId(id);
Set<Long> convertSet = CollectionUtils.convertSet(productDOs, SeckillProductDO::getSkuId);
seckillProductMapper.deleteBatchIds(convertSet);
} }
private SeckillActivityDO validateSeckillActivityExists(Long id) { private SeckillActivityDO validateSeckillActivityExists(Long id) {
@ -219,7 +206,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
@Override @Override
public SeckillActivityDO getSeckillActivity(Long id) { public SeckillActivityDO getSeckillActivity(Long id) {
return seckillActivityMapper.selectById(id); return validateSeckillActivityExists(id);
} }
@Override @Override
@ -237,4 +224,9 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
return seckillProductMapper.selectListByActivityId(id); return seckillProductMapper.selectListByActivityId(id);
} }
@Override
public List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> ids) {
return seckillProductMapper.selectListByActivityId(ids);
}
} }

View File

@ -1,8 +1,11 @@
package cn.iocoder.yudao.module.promotion.service.seckill.seckillconfig; package cn.iocoder.yudao.module.promotion.service.seckill.seckillconfig;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigPageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigUpdateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigUpdateReqVO;
@ -10,14 +13,13 @@ import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillCo
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig.SeckillConfigDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig.SeckillConfigDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillconfig.SeckillConfigMapper; import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillconfig.SeckillConfigMapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.LocalTime; import java.time.LocalTime;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
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.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
@ -35,9 +37,10 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
private SeckillConfigMapper seckillConfigMapper; private SeckillConfigMapper seckillConfigMapper;
@Override @Override
@Transactional(rollbackFor = Exception.class)
public Long createSeckillConfig(SeckillConfigCreateReqVO createReqVO) { public Long createSeckillConfig(SeckillConfigCreateReqVO createReqVO) {
// 校验时间段是否冲突 // 校验时间段是否冲突
validateSeckillConfigConflict(createReqVO.getStartTime(), createReqVO.getEndTime()); validateSeckillConfigConflict(createReqVO.getStartTime(), createReqVO.getEndTime(), null);
// 插入 // 插入
SeckillConfigDO seckillConfig = SeckillConfigConvert.INSTANCE.convert(createReqVO); SeckillConfigDO seckillConfig = SeckillConfigConvert.INSTANCE.convert(createReqVO);
@ -47,18 +50,30 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void updateSeckillConfig(SeckillConfigUpdateReqVO updateReqVO) { public void updateSeckillConfig(SeckillConfigUpdateReqVO updateReqVO) {
// 校验存在 // 校验存在
validateSeckillConfigExists(updateReqVO.getId()); validateSeckillConfigExists(updateReqVO.getId());
// 校验时间段是否冲突 // 校验时间段是否冲突
validateSeckillConfigConflict(updateReqVO.getStartTime(), updateReqVO.getEndTime()); validateSeckillConfigConflict(updateReqVO.getStartTime(), updateReqVO.getEndTime(), updateReqVO.getId());
// 更新 // 更新
SeckillConfigDO updateObj = SeckillConfigConvert.INSTANCE.convert(updateReqVO); SeckillConfigDO updateObj = SeckillConfigConvert.INSTANCE.convert(updateReqVO);
seckillConfigMapper.updateById(updateObj); seckillConfigMapper.updateById(updateObj);
} }
// TODO @puhui999: 这个要不合并到更新操作里? 不单独有个操作咧; 更新状态不用那么多必须的参数更新的时候需要校验时间段
@Override @Override
public void updateSeckillConfigStatus(Long id, Integer status) {
// 校验秒杀时段是否存在
validateSeckillConfigExists(id);
// 更新状态
seckillConfigMapper.updateById(new SeckillConfigDO().setId(id).setStatus(status));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteSeckillConfig(Long id) { public void deleteSeckillConfig(Long id) {
// 校验存在 // 校验存在
validateSeckillConfigExists(id); validateSeckillConfigExists(id);
@ -79,39 +94,29 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
* @param startTime 开始时间 * @param startTime 开始时间
* @param endTime 结束时间 * @param endTime 结束时间
*/ */
private void validateSeckillConfigConflict(String startTime, String endTime) { private void validateSeckillConfigConflict(String startTime, String endTime, Long seckillConfigId) {
LocalTime startTime1 = LocalTime.parse(startTime); LocalTime startTime1 = LocalTime.parse(startTime);
LocalTime endTime1 = LocalTime.parse(endTime); LocalTime endTime1 = LocalTime.parse(endTime);
// TODO @puhui999 这个可以用 validator 里的 assertTrue 去做哈
// 检查选择的时间是否相等
if (startTime1.equals(endTime1)) {
throw exception(SECKILL_TIME_EQUAL);
}
// 检查开始时间是否在结束时间之前
if (startTime1.isAfter(endTime1)) {
throw exception(SECKILL_START_TIME_BEFORE_END_TIME);
}
// 查询出所有的时段配置 // 查询出所有的时段配置
List<SeckillConfigDO> configDOs = seckillConfigMapper.selectList(); List<SeckillConfigDO> configDOs = seckillConfigMapper.selectList();
// 更新时排除自己
if (seckillConfigId != null) {
configDOs.removeIf(item -> ObjectUtil.equal(item.getId(), seckillConfigId));
}
// 过滤出重叠的时段 ids // 过滤出重叠的时段 ids
// TODO @puhui999感觉 findOne 就可以了 boolean hasConflict = configDOs.stream().anyMatch(config -> {
Set<Long> ids = configDOs.stream().filter((config) -> {
LocalTime startTime2 = LocalTime.parse(config.getStartTime()); LocalTime startTime2 = LocalTime.parse(config.getStartTime());
LocalTime endTime2 = LocalTime.parse(config.getEndTime()); LocalTime endTime2 = LocalTime.parse(config.getEndTime());
// 判断时间是否重叠 // 判断时间是否重叠
// 开始时间在已配置时段的结束时间之前 结束时间在已配置时段的开始时间之后 [] return LocalDateTimeUtils.checkTimeOverlap(startTime1, endTime1, startTime2, endTime2);
// todo @puhui999LocalDateUtils 可以写个工具类是否是有重叠的时间感觉别的场景可能也会有需要 });
return startTime1.isBefore(endTime2) && endTime1.isAfter(startTime2)
// 开始时间在已配置时段的开始时间之前 结束时间在已配置时段的开始时间之后 (] () if (hasConflict) {
|| startTime1.isBefore(startTime2) && endTime1.isAfter(startTime2)
// 开始时间在已配置时段的结束时间之前 结束时间在已配值时段的结束时间之后 [) ()
|| startTime1.isBefore(endTime2) && endTime1.isAfter(endTime2);
}).map(SeckillConfigDO::getId).collect(Collectors.toSet());
if (CollUtil.isNotEmpty(ids)) {
throw exception(SECKILL_TIME_CONFLICTS); throw exception(SECKILL_TIME_CONFLICTS);
} }
} }
@Override @Override
public SeckillConfigDO getSeckillConfig(Long id) { public SeckillConfigDO getSeckillConfig(Long id) {
return seckillConfigMapper.selectById(id); return seckillConfigMapper.selectById(id);
@ -127,10 +132,18 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
if (CollUtil.isEmpty(configIds)) { if (CollUtil.isEmpty(configIds)) {
throw exception(SECKILL_TIME_NOT_EXISTS); throw exception(SECKILL_TIME_NOT_EXISTS);
} }
if (seckillConfigMapper.selectBatchIds(configIds).size() != configIds.size()) { List<SeckillConfigDO> configDOs = seckillConfigMapper.selectBatchIds(configIds);
if (CollUtil.isEmpty(configDOs)) {
throw exception(SECKILL_TIME_NOT_EXISTS);
}
// 过滤出关闭的时段
List<SeckillConfigDO> filterList = CollectionUtils.filterList(configDOs, item -> ObjectUtil.equal(item.getStatus(), CommonStatusEnum.DISABLE.getStatus()));
if (CollUtil.isNotEmpty(filterList)) {
throw exception(SECKILL_TIME_DISABLE);
}
if (configDOs.size() != configIds.size()) {
throw exception(SECKILL_TIME_NOT_EXISTS); throw exception(SECKILL_TIME_NOT_EXISTS);
} }
// TODO @puhui999应该要校验个 status 如果有禁用的也不行
} }
@Override @Override
@ -138,23 +151,9 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
return seckillConfigMapper.selectPage(pageVO); return seckillConfigMapper.selectPage(pageVO);
} }
// TODO @puhui999:写个查询状态的; 尽可能通用哈
@Override @Override
public List<SeckillConfigDO> getListAllSimple() { public List<SeckillConfigDO> getListAllSimple() {
return seckillConfigMapper.selectList(SeckillConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus()); return seckillConfigMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
}
// TODO @puhui999: 这个要不合并到更新操作里? 不单独有个操作咧;
@Override
public void updateSeckillConfigStatus(Long id, Integer status) {
// 校验秒杀时段是否存在
validateSeckillConfigExists(id);
SeckillConfigDO seckillConfigDO = new SeckillConfigDO();
seckillConfigDO.setId(id);
seckillConfigDO.setStatus(status);
// 更新状态
seckillConfigMapper.updateById(seckillConfigDO);
} }
} }

View File

@ -1,9 +1,18 @@
package cn.iocoder.yudao.module.promotion.util; package cn.iocoder.yudao.module.promotion.util;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
/** /**
* 活动工具类 * 活动工具类
@ -22,4 +31,15 @@ public class PromotionUtils {
return LocalDateTimeUtils.beforeNow(endTime) ? CommonStatusEnum.DISABLE.getStatus() : CommonStatusEnum.ENABLE.getStatus(); return LocalDateTimeUtils.beforeNow(endTime) ? CommonStatusEnum.DISABLE.getStatus() : CommonStatusEnum.ENABLE.getStatus();
} }
public static <T> void validateProductSkuExistence(List<ProductSkuRespDTO> skus, List<T> products, Function<T, Long> func) {
// 校验 sku 个数是否一致
Set<Long> skuIdsSet = CollectionUtils.convertSet(products, func);
Set<Long> skuIdsSet1 = CollectionUtils.convertSet(skus, ProductSkuRespDTO::getId);
// 校验 skuId 是否存在
List<Long> f = CollectionUtils.filterList(skuIdsSet, s -> !skuIdsSet1.contains(s));
if (CollUtil.isNotEmpty(f)) {
throw exception(SKU_NOT_EXISTS);
}
}
} }

View File

@ -0,0 +1,251 @@
package cn.iocoder.yudao.module.promotion.service.combination;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityExportReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity.CombinationActivityMapper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COMBINATION_ACTIVITY_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link CombinationActivityServiceImpl} 的单元测试类
*
* @author HUIHUI
*/
@Import(CombinationActivityServiceImpl.class)
public class CombinationActivityServiceImplTest extends BaseDbUnitTest {
@Resource
private CombinationActivityServiceImpl combinationActivityService;
@Resource
private CombinationActivityMapper combinationActivityMapper;
@Test
public void testCreateCombinationActivity_success() {
// 准备参数
CombinationActivityCreateReqVO reqVO = randomPojo(CombinationActivityCreateReqVO.class);
// 调用
Long combinationActivityId = combinationActivityService.createCombinationActivity(reqVO);
// 断言
assertNotNull(combinationActivityId);
// 校验记录的属性是否正确
CombinationActivityDO combinationActivity = combinationActivityMapper.selectById(combinationActivityId);
assertPojoEquals(reqVO, combinationActivity);
}
@Test
public void testUpdateCombinationActivity_success() {
// mock 数据
CombinationActivityDO dbCombinationActivity = randomPojo(CombinationActivityDO.class);
combinationActivityMapper.insert(dbCombinationActivity);// @Sql: 先插入出一条存在的数据
// 准备参数
CombinationActivityUpdateReqVO reqVO = randomPojo(CombinationActivityUpdateReqVO.class, o -> {
o.setId(dbCombinationActivity.getId()); // 设置更新的 ID
});
// 调用
combinationActivityService.updateCombinationActivity(reqVO);
// 校验是否更新正确
CombinationActivityDO combinationActivity = combinationActivityMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, combinationActivity);
}
@Test
public void testUpdateCombinationActivity_notExists() {
// 准备参数
CombinationActivityUpdateReqVO reqVO = randomPojo(CombinationActivityUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> combinationActivityService.updateCombinationActivity(reqVO), COMBINATION_ACTIVITY_NOT_EXISTS);
}
@Test
public void testDeleteCombinationActivity_success() {
// mock 数据
CombinationActivityDO dbCombinationActivity = randomPojo(CombinationActivityDO.class);
combinationActivityMapper.insert(dbCombinationActivity);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbCombinationActivity.getId();
// 调用
combinationActivityService.deleteCombinationActivity(id);
// 校验数据不存在了
assertNull(combinationActivityMapper.selectById(id));
}
@Test
public void testDeleteCombinationActivity_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> combinationActivityService.deleteCombinationActivity(id), COMBINATION_ACTIVITY_NOT_EXISTS);
}
@Test
@Disabled // TODO 请修改 null 为需要的值然后删除 @Disabled 注解
public void testGetCombinationActivityPage() {
// mock 数据
CombinationActivityDO dbCombinationActivity = randomPojo(CombinationActivityDO.class, o -> { // 等会查询到
o.setName(null);
//o.setSpuId(null);
o.setTotalLimitCount(null);
o.setSingleLimitCount(null);
o.setStartTime(null);
o.setEndTime(null);
o.setUserSize(null);
o.setTotalNum(null);
o.setSuccessNum(null);
o.setOrderUserCount(null);
o.setVirtualGroup(null);
o.setStatus(null);
o.setLimitDuration(null);
o.setCreateTime(null);
});
combinationActivityMapper.insert(dbCombinationActivity);
// 测试 name 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setName(null)));
// 测试 spuId 不匹配
//combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSpuId(null)));
// 测试 totalLimitCount 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setTotalLimitCount(null)));
// 测试 singleLimitCount 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSingleLimitCount(null)));
// 测试 startTime 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setStartTime(null)));
// 测试 endTime 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setEndTime(null)));
// 测试 userSize 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setUserSize(null)));
// 测试 totalNum 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setTotalNum(null)));
// 测试 successNum 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSuccessNum(null)));
// 测试 orderUserCount 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setOrderUserCount(null)));
// 测试 virtualGroup 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setVirtualGroup(null)));
// 测试 status 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setStatus(null)));
// 测试 limitDuration 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setLimitDuration(null)));
// 测试 createTime 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setCreateTime(null)));
// 准备参数
CombinationActivityPageReqVO reqVO = new CombinationActivityPageReqVO();
reqVO.setName(null);
reqVO.setSpuId(null);
reqVO.setTotalLimitCount(null);
reqVO.setSingleLimitCount(null);
reqVO.setStartTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
reqVO.setEndTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
reqVO.setUserSize(null);
reqVO.setTotalNum(null);
reqVO.setSuccessNum(null);
reqVO.setOrderUserCount(null);
reqVO.setVirtualGroup(null);
reqVO.setStatus(null);
reqVO.setLimitDuration(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<CombinationActivityDO> pageResult = combinationActivityService.getCombinationActivityPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbCombinationActivity, pageResult.getList().get(0));
}
@Test
@Disabled // TODO 请修改 null 为需要的值然后删除 @Disabled 注解
public void testGetCombinationActivityList() {
// mock 数据
CombinationActivityDO dbCombinationActivity = randomPojo(CombinationActivityDO.class, o -> { // 等会查询到
o.setName(null);
//o.setSpuId(null);
o.setTotalLimitCount(null);
o.setSingleLimitCount(null);
o.setStartTime(null);
o.setEndTime(null);
o.setUserSize(null);
o.setTotalNum(null);
o.setSuccessNum(null);
o.setOrderUserCount(null);
o.setVirtualGroup(null);
o.setStatus(null);
o.setLimitDuration(null);
o.setCreateTime(null);
});
combinationActivityMapper.insert(dbCombinationActivity);
// 测试 name 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setName(null)));
// 测试 spuId 不匹配
//combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSpuId(null)));
// 测试 totalLimitCount 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setTotalLimitCount(null)));
// 测试 singleLimitCount 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSingleLimitCount(null)));
// 测试 startTime 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setStartTime(null)));
// 测试 endTime 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setEndTime(null)));
// 测试 userSize 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setUserSize(null)));
// 测试 totalNum 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setTotalNum(null)));
// 测试 successNum 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSuccessNum(null)));
// 测试 orderUserCount 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setOrderUserCount(null)));
// 测试 virtualGroup 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setVirtualGroup(null)));
// 测试 status 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setStatus(null)));
// 测试 limitDuration 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setLimitDuration(null)));
// 测试 createTime 不匹配
combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setCreateTime(null)));
// 准备参数
CombinationActivityExportReqVO reqVO = new CombinationActivityExportReqVO();
reqVO.setName(null);
reqVO.setSpuId(null);
reqVO.setTotalLimitCount(null);
reqVO.setSingleLimitCount(null);
reqVO.setStartTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
reqVO.setEndTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
reqVO.setUserSize(null);
reqVO.setTotalNum(null);
reqVO.setSuccessNum(null);
reqVO.setOrderUserCount(null);
reqVO.setVirtualGroup(null);
reqVO.setStatus(null);
reqVO.setLimitDuration(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
List<CombinationActivityDO> list = combinationActivityService.getCombinationActivityList(reqVO);
// 断言
assertEquals(1, list.size());
assertPojoEquals(dbCombinationActivity, list.get(0));
}
}

View File

@ -1,7 +1,16 @@
DELETE FROM "market_activity"; DELETE
DELETE FROM "promotion_coupon_template"; FROM "market_activity";
DELETE FROM "promotion_coupon"; DELETE
DELETE FROM "promotion_reward_activity"; FROM "promotion_coupon_template";
DELETE FROM "promotion_discount_activity"; DELETE
DELETE FROM "promotion_discount_product"; FROM "promotion_coupon";
DELETE FROM "promotion_seckill_config"; DELETE
FROM "promotion_reward_activity";
DELETE
FROM "promotion_discount_activity";
DELETE
FROM "promotion_discount_product";
DELETE
FROM "promotion_seckill_config";
DELETE
FROM "promotion_combination_activity";

View File

@ -1,4 +1,5 @@
CREATE TABLE IF NOT EXISTS "market_activity" ( CREATE TABLE IF NOT EXISTS "market_activity"
(
"id" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"title" varchar(50) NOT NULL, "title" varchar(50) NOT NULL,
"activity_type" tinyint(4) NOT NULL, "activity_type" tinyint(4) NOT NULL,
@ -16,9 +17,10 @@ CREATE TABLE IF NOT EXISTS "market_activity" (
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint(20) NOT NULL, "tenant_id" bigint(20) NOT NULL,
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '促销活动'; ) COMMENT '促销活动';
CREATE TABLE IF NOT EXISTS "promotion_coupon_template" ( CREATE TABLE IF NOT EXISTS "promotion_coupon_template"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL, "name" varchar NOT NULL,
"status" int NOT NULL, "status" int NOT NULL,
@ -47,7 +49,8 @@ CREATE TABLE IF NOT EXISTS "promotion_coupon_template" (
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '优惠劵模板'; ) COMMENT '优惠劵模板';
CREATE TABLE IF NOT EXISTS "promotion_coupon" ( CREATE TABLE IF NOT EXISTS "promotion_coupon"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"template_id" bigint NOT NULL, "template_id" bigint NOT NULL,
"name" varchar NOT NULL, "name" varchar NOT NULL,
@ -73,7 +76,8 @@ CREATE TABLE IF NOT EXISTS "promotion_coupon" (
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '优惠劵'; ) COMMENT '优惠劵';
CREATE TABLE IF NOT EXISTS "promotion_reward_activity" ( CREATE TABLE IF NOT EXISTS "promotion_reward_activity"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL, "name" varchar NOT NULL,
"status" int NOT NULL, "status" int NOT NULL,
@ -92,7 +96,8 @@ CREATE TABLE IF NOT EXISTS "promotion_reward_activity" (
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '满减送活动'; ) COMMENT '满减送活动';
CREATE TABLE IF NOT EXISTS "promotion_discount_activity" ( CREATE TABLE IF NOT EXISTS "promotion_discount_activity"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL, "name" varchar NOT NULL,
"status" int NOT NULL, "status" int NOT NULL,
@ -108,7 +113,8 @@ CREATE TABLE IF NOT EXISTS "promotion_discount_activity" (
) COMMENT '限时折扣活动'; ) COMMENT '限时折扣活动';
-- 将该建表 SQL 语句添加到 yudao-module-promotion-biz 模块的 test/resources/sql/create_tables.sql 文件里 -- 将该建表 SQL 语句添加到 yudao-module-promotion-biz 模块的 test/resources/sql/create_tables.sql 文件里
CREATE TABLE IF NOT EXISTS "promotion_seckill_activity" ( CREATE TABLE IF NOT EXISTS "promotion_seckill_activity"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"spu_id" bigint NOT NULL, "spu_id" bigint NOT NULL,
"name" varchar NOT NULL, "name" varchar NOT NULL,
@ -134,7 +140,8 @@ CREATE TABLE IF NOT EXISTS "promotion_seckill_activity" (
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '秒杀活动'; ) COMMENT '秒杀活动';
CREATE TABLE IF NOT EXISTS "promotion_seckill_config" ( CREATE TABLE IF NOT EXISTS "promotion_seckill_config"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL, "name" varchar NOT NULL,
"start_time" varchar NOT NULL, "start_time" varchar NOT NULL,
@ -149,3 +156,28 @@ CREATE TABLE IF NOT EXISTS "promotion_seckill_config" (
"tenant_id" bigint NOT NULL, "tenant_id" bigint NOT NULL,
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '秒杀时段配置'; ) COMMENT '秒杀时段配置';
CREATE TABLE IF NOT EXISTS "promotion_combination_activity"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"spu_id" bigint,
"total_limit_count" int NOT NULL,
"single_limit_count" int NOT NULL,
"start_time" varchar NOT NULL,
"end_time" varchar NOT NULL,
"user_size" int NOT NULL,
"total_num" int NOT NULL,
"success_num" int NOT NULL,
"order_user_count" int NOT NULL,
"virtual_group" int NOT NULL,
"status" int NOT NULL,
"limit_duration" int NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL,
PRIMARY KEY ("id")
) COMMENT '拼团活动';

View File

@ -27,6 +27,10 @@ public interface ErrorCodeConstants {
ErrorCode ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1011000016, "交易订单更新支付状态失败,支付单金额不匹配"); ErrorCode ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1011000016, "交易订单更新支付状态失败,支付单金额不匹配");
ErrorCode ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED = new ErrorCode(1011000017, "交易订单发货失败,订单不是【待发货】状态"); ErrorCode ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED = new ErrorCode(1011000017, "交易订单发货失败,订单不是【待发货】状态");
ErrorCode ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1011000018, "交易订单收货失败,订单不是【待收货】状态"); ErrorCode ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1011000018, "交易订单收货失败,订单不是【待收货】状态");
ErrorCode ORDER_COMMENT_FAIL_STATUS_NOT_COMPLETED = new ErrorCode(1011000019, "创建交易订单项的评价失败,订单不是【已完成】状态");
ErrorCode ORDER_COMMENT_STATUS_NOT_FALSE = new ErrorCode(1011000020, "创建交易订单项的评价失败,订单已评价");
ErrorCode ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE = new ErrorCode(1011000021, "交易订单发货失败,订单已退款或部分退款");
ErrorCode ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1011000022, "交易订单发货失败,拼团未成功");
// ========== After Sale 模块 1011000100 ========== // ========== After Sale 模块 1011000100 ==========
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1011000100, "售后单不存在"); ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1011000100, "售后单不存在");
@ -63,8 +67,10 @@ public interface ErrorCodeConstants {
ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1011005001, "运费模板不存在"); ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1011005001, "运费模板不存在");
// ========== 物流 PICK_UP 模块 1011006000 ========== // ========== 物流 PICK_UP 模块 1011006000 ==========
ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011006000, "自提门店不存在"); ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011006000, "自提门店不存在");
// ========== 物流 PICK_UP 模块 1011007000 ==========
ErrorCode ORDER_DELIVERY_FAILED_ITEMS_NOT_EMPTY = new ErrorCode(1011007000, "订单发货失败,请选择发货商品");
ErrorCode ORDER_DELIVERY_FAILED_ITEM_NOT_EXISTS = new ErrorCode(1011007001, "订单发货失败,所选发货商品不存在");
ErrorCode ORDER_DELIVERY_FAILED_ITEM_ALREADY_DELIVERY = new ErrorCode(1011007001, "订单发货失败,所选商品已发货");
} }

View File

@ -1,10 +1,12 @@
package cn.iocoder.yudao.module.trade.controller.admin.order.vo; package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "管理后台 - 订单发货 Request VO") @Schema(description = "管理后台 - 订单发货 Request VO")
@Data @Data
@ -14,12 +16,24 @@ public class TradeOrderDeliveryReqVO {
@NotNull(message = "订单编号不能为空") @NotNull(message = "订单编号不能为空")
private Long id; private Long id;
@Schema(description = "发货物流公司编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "发货类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "发货物流公司编号不能为空") @InEnum(DeliveryTypeEnum.class)
@NotNull(message = "发货类型不能为空")
private Integer type;
@Schema(description = "发货物流公司编号", example = "1")
private Long logisticsId; private Long logisticsId;
@Schema(description = "发货物流单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "SF123456789") @Schema(description = "发货物流单号", example = "SF123456789")
@NotEmpty(message = "发货物流单号不能为空")
private String logisticsNo; private String logisticsNo;
// TODO 订单项商品单独发货
@Schema(description = "发货订单项", example = "[1,2,3]")
@NotNull(message = "发货订单项不能为空")
private List<Long> orderItemIds;
// =============== 同城配送 ================
// TODO
} }

View File

@ -4,8 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi; import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*; import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
@ -30,13 +28,10 @@ import javax.validation.Valid;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_ITEM_NOT_FOUND;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_NOT_FOUND;
@Tag(name = "用户 App - 交易订单") @Tag(name = "用户 App - 交易订单")
@RestController @RestController
@ -47,12 +42,8 @@ public class AppTradeOrderController {
@Resource @Resource
private TradeOrderService tradeOrderService; private TradeOrderService tradeOrderService;
@Resource @Resource
private ProductPropertyValueApi productPropertyValueApi; private ProductPropertyValueApi productPropertyValueApi;
@Resource
private ProductCommentApi productCommentApi;
@Resource @Resource
private TradeOrderProperties tradeOrderProperties; private TradeOrderProperties tradeOrderProperties;
@ -164,22 +155,7 @@ public class AppTradeOrderController {
@PostMapping("/item/create-comment") @PostMapping("/item/create-comment")
@Operation(summary = "创建交易订单项的评价") @Operation(summary = "创建交易订单项的评价")
public CommonResult<Long> createOrderItemComment(@RequestBody AppTradeOrderItemCommentCreateReqVO createReqVO) { public CommonResult<Long> createOrderItemComment(@RequestBody AppTradeOrderItemCommentCreateReqVO createReqVO) {
// TODO @puhui999这个逻辑最好写到 service return success(tradeOrderService.createOrderItemComment(createReqVO));
Long loginUserId = getLoginUserId();
// 先通过订单项 ID 查询订单项是否存在
TradeOrderItemDO orderItemDO = tradeOrderService.getOrderItemByIdAndUserId(createReqVO.getOrderItemId(), loginUserId);
if (orderItemDO == null) {
throw exception(ORDER_ITEM_NOT_FOUND);
}
// 校验订单
TradeOrderDO orderDO = tradeOrderService.getOrderByIdAndUserId(orderItemDO.getOrderId(), loginUserId);
if (orderDO == null) {
throw exception(ORDER_NOT_FOUND);
}
// TODO @puhui999要校验订单已完成但是未评价
ProductCommentCreateReqDTO productCommentCreateReqDTO = TradeOrderConvert.INSTANCE.convert04(createReqVO, orderItemDO);
return success(productCommentApi.createComment(productCommentCreateReqDTO));
} }
} }

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile; import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@ -19,11 +18,6 @@ import java.util.List;
@Data @Data
public class AppTradeOrderSettlementReqVO { public class AppTradeOrderSettlementReqVO {
@NotNull(message = "交易类型不能为空")
@InEnum(value = TradeOrderTypeEnum.class, message = "交易类型必须是 {value}")
@Deprecated // TODO 芋艿后续干掉这个字段对于前端不需要关注这个
private Integer type = 1;
@Schema(description = "商品项数组", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "商品项数组", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "商品不能为空") @NotEmpty(message = "商品不能为空")
private List<Item> items; private List<Item> items;

View File

@ -19,11 +19,6 @@ public class AppTradeOrderItemCommentCreateReqVO {
@NotNull(message = "交易订单项编号不能为空") @NotNull(message = "交易订单项编号不能为空")
private Long orderItemId; private Long orderItemId;
// TODO @puhui999貌似不用这个字段哈
@Schema(description = "评分星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
@NotNull(message = "评分星级 1-5 分不能为空")
private Integer scores;
@Schema(description = "描述星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") @Schema(description = "描述星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
@NotNull(message = "描述星级 1-5 分不能为空") @NotNull(message = "描述星级 1-5 分不能为空")
private Integer descriptionScores; private Integer descriptionScores;

View File

@ -13,10 +13,12 @@ import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO; import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO; import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO; import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO; import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDetailRespVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageItemRespVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageItemRespVO;
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
@ -25,6 +27,7 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderI
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO; import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDeliveryDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
@ -52,7 +55,6 @@ public interface TradeOrderConvert {
@Mapping(source = "createReqVO.couponId", target = "couponId"), @Mapping(source = "createReqVO.couponId", target = "couponId"),
@Mapping(target = "remark", ignore = true), @Mapping(target = "remark", ignore = true),
@Mapping(source = "createReqVO.remark", target = "userRemark"), @Mapping(source = "createReqVO.remark", target = "userRemark"),
@Mapping(source = "createReqVO.type", target = "type"),
@Mapping(source = "calculateRespBO.price.totalPrice", target = "totalPrice"), @Mapping(source = "calculateRespBO.price.totalPrice", target = "totalPrice"),
@Mapping(source = "calculateRespBO.price.discountPrice", target = "discountPrice"), @Mapping(source = "calculateRespBO.price.discountPrice", target = "discountPrice"),
@Mapping(source = "calculateRespBO.price.deliveryPrice", target = "deliveryPrice"), @Mapping(source = "calculateRespBO.price.deliveryPrice", target = "deliveryPrice"),
@ -123,7 +125,7 @@ public interface TradeOrderConvert {
// TODO 芋艿可简化 // TODO 芋艿可简化
default PageResult<TradeOrderPageItemRespVO> convertPage(PageResult<TradeOrderDO> pageResult, List<TradeOrderItemDO> orderItems, default PageResult<TradeOrderPageItemRespVO> convertPage(PageResult<TradeOrderDO> pageResult, List<TradeOrderItemDO> orderItems,
List<ProductPropertyValueDetailRespDTO> propertyValueDetails, List<ProductPropertyValueDetailRespDTO> propertyValueDetails,
Map<Long,MemberUserRespDTO> memberUserRespDTOMap) { Map<Long, MemberUserRespDTO> memberUserRespDTOMap) {
Map<Long, List<TradeOrderItemDO>> orderItemMap = convertMultiMap(orderItems, TradeOrderItemDO::getOrderId); Map<Long, List<TradeOrderItemDO>> orderItemMap = convertMultiMap(orderItems, TradeOrderItemDO::getOrderId);
Map<Long, ProductPropertyValueDetailRespDTO> propertyValueDetailMap = convertMap(propertyValueDetails, ProductPropertyValueDetailRespDTO::getValueId); Map<Long, ProductPropertyValueDetailRespDTO> propertyValueDetailMap = convertMap(propertyValueDetails, ProductPropertyValueDetailRespDTO::getValueId);
// 转化 List // 转化 List
@ -267,23 +269,23 @@ public interface TradeOrderConvert {
AppTradeOrderItemRespVO convert03(TradeOrderItemDO bean); AppTradeOrderItemRespVO convert03(TradeOrderItemDO bean);
@Mapping(target = "skuId", source = "tradeOrderItemDO.skuId") @Mappings({
@Mapping(target = "orderId", source = "tradeOrderItemDO.orderId") @Mapping(target = "skuId", source = "tradeOrderItemDO.skuId"),
@Mapping(target = "orderItemId", source = "tradeOrderItemDO.id") @Mapping(target = "orderId", source = "tradeOrderItemDO.orderId"),
@Mapping(target = "scores", source = "createReqVO.scores") @Mapping(target = "orderItemId", source = "tradeOrderItemDO.id"),
@Mapping(target = "descriptionScores", source = "createReqVO.descriptionScores") @Mapping(target = "descriptionScores", source = "createReqVO.descriptionScores"),
@Mapping(target = "benefitScores", source = "createReqVO.benefitScores") @Mapping(target = "benefitScores", source = "createReqVO.benefitScores"),
@Mapping(target = "content", source = "createReqVO.content") @Mapping(target = "content", source = "createReqVO.content"),
@Mapping(target = "picUrls", source = "createReqVO.picUrls") @Mapping(target = "picUrls", source = "createReqVO.picUrls"),
@Mapping(target = "anonymous", source = "createReqVO.anonymous") @Mapping(target = "anonymous", source = "createReqVO.anonymous"),
@Mapping(target = "userId", source = "tradeOrderItemDO.userId") @Mapping(target = "userId", source = "tradeOrderItemDO.userId")
})
ProductCommentCreateReqDTO convert04(AppTradeOrderItemCommentCreateReqVO createReqVO, TradeOrderItemDO tradeOrderItemDO); ProductCommentCreateReqDTO convert04(AppTradeOrderItemCommentCreateReqVO createReqVO, TradeOrderItemDO tradeOrderItemDO);
default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO, default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO,
List<TradeCartDO> cartList) { List<TradeCartDO> cartList) {
TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO(); TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO();
reqBO.setUserId(userId).setType(settlementReqVO.getType()) reqBO.setUserId(userId).setCouponId(settlementReqVO.getCouponId()).setAddressId(settlementReqVO.getAddressId())
.setCouponId(settlementReqVO.getCouponId()).setAddressId(settlementReqVO.getAddressId())
.setItems(new ArrayList<>(settlementReqVO.getItems().size())); .setItems(new ArrayList<>(settlementReqVO.getItems().size()));
// 商品项的构建 // 商品项的构建
Map<Long, TradeCartDO> cartMap = convertMap(cartList, TradeCartDO::getId); Map<Long, TradeCartDO> cartMap = convertMap(cartList, TradeCartDO::getId);
@ -318,4 +320,31 @@ public interface TradeOrderConvert {
AppTradeOrderSettlementRespVO convert0(TradePriceCalculateRespBO calculate, AddressRespDTO address); AppTradeOrderSettlementRespVO convert0(TradePriceCalculateRespBO calculate, AddressRespDTO address);
@Mappings({
@Mapping(target = "activityId", source = "createReqVO.combinationActivityId"),
@Mapping(target = "spuId", source = "orderItem.spuId"),
@Mapping(target = "skuId", source = "orderItem.skuId"),
@Mapping(target = "userId", source = "order.userId"),
@Mapping(target = "orderId", source = "order.id"),
@Mapping(target = "headId", source = "createReqVO.combinationHeadId"),
@Mapping(target = "spuName", source = "orderItem.spuName"),
@Mapping(target = "picUrl", source = "orderItem.picUrl"),
@Mapping(target = "combinationPrice", source = "orderItem.payPrice"),
@Mapping(target = "nickname", source = "user.nickname"),
@Mapping(target = "avatar", source = "user.avatar"),
@Mapping(target = "status", ignore = true)
})
CombinationRecordReqDTO convert(TradeOrderDO order, TradeOrderItemDO orderItem, AppTradeOrderCreateReqVO createReqVO, MemberUserRespDTO user);
TradeOrderDeliveryDO covert(Long orderId, Long orderItemId, Long userId, Integer deliveryType, Long logisticsId, String logisticsNo);
default List<TradeOrderDeliveryDO> covert(TradeOrderDO order, TradeOrderDeliveryReqVO deliveryReqVO) {
ArrayList<TradeOrderDeliveryDO> arrayList = new ArrayList<>();
deliveryReqVO.getOrderItemIds().forEach(item -> {
arrayList.add(covert(order.getId(), item, order.getUserId(), deliveryReqVO.getType(),
deliveryReqVO.getLogisticsId(), deliveryReqVO.getLogisticsNo()));
});
return arrayList;
}
} }

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.order;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 交易订单发货记录 DO
*
* @author HUIHUI
*/
@TableName("trade_order_delivery")
@KeySequence("trade_order_delivery_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TradeOrderDeliveryDO extends BaseDO {
/**
* 订单发货记录 id
*/
private Long id;
/**
* 订单 id
*/
private Long orderId;
/**
* 订单项 id TODO 要不要一个发货记录可对应多个订单项
*/
private Long orderItemId;
/**
* 用户编号
*
* 关联 MemberUserDO id 编号
*/
private Long userId;
/**
* 配送方式
*
* 枚举 {@link DeliveryTypeEnum}
*/
private Integer deliveryType;
/**
* 发货物流公司编号
*/
private Long logisticsId;
/**
* 发货物流单号
*/
private String logisticsNo;
// TODO 同城配送
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.trade.dal.mysql.order;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDeliveryDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 交易订单发货记录 Mapper
*
* @author HUIHUI
*/
@Mapper
public interface TradeOrderDeliveryMapper extends BaseMapperX<TradeOrderDeliveryDO> {
default List<TradeOrderDeliveryDO> selsectListByOrderIdAndItemIds(Long orderId, List<Long> orderItemIds) {
return selectList(new LambdaQueryWrapperX<TradeOrderDeliveryDO>()
.eq(TradeOrderDeliveryDO::getOrderId, orderId).in(TradeOrderDeliveryDO::getOrderItemId, orderItemIds));
}
}

View File

@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreate
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
@ -177,4 +178,14 @@ public interface TradeOrderService {
* @return 得到订单 * @return 得到订单
*/ */
TradeOrderDO getOrderByIdAndUserId(Long orderId, Long loginUserId); TradeOrderDO getOrderByIdAndUserId(Long orderId, Long loginUserId);
/**
* 创建订单项评论
* 创建交易订单项的评价
*
* @param createReqVO 创建请求
* @return 得到评价 id
*/
Long createOrderItemComment(AppTradeOrderItemCommentCreateReqVO createReqVO);
} }

View File

@ -17,24 +17,30 @@ import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.CombinationApi;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO; import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO; import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDeliveryDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderDeliveryMapper;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
@ -57,6 +63,7 @@ import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_NOT_FOUND; import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_NOT_FOUND;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
@ -93,13 +100,17 @@ public class TradeOrderServiceImpl implements TradeOrderService {
@Resource @Resource
private MemberUserApi memberUserApi; private MemberUserApi memberUserApi;
@Resource @Resource
private AdminUserApi adminUserApi; private ProductCommentApi productCommentApi;
@Resource @Resource
private NotifyMessageSendApi notifyMessageSendApi; private NotifyMessageSendApi notifyMessageSendApi;
@Resource @Resource
private TradeOrderProperties tradeOrderProperties; private TradeOrderProperties tradeOrderProperties;
@Resource
private CombinationApi combinationApi;
@Resource
private TradeOrderDeliveryMapper orderDeliveryMapper;
// =================== Order =================== // =================== Order ===================
@Override @Override
@ -153,19 +164,27 @@ public class TradeOrderServiceImpl implements TradeOrderService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) { public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) {
// 1. 用户收件地址的校验
AddressRespDTO address = validateAddress(userId, createReqVO.getAddressId());
// 2. 价格计算 // 2. 价格计算
TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO); TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
// 3.1 插入 TradeOrderDO 订单 // 3.1 插入 TradeOrderDO 订单
TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO, address); TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO);
// 3.2 插入 TradeOrderItemDO 订单项 // 3.2 插入 TradeOrderItemDO 订单项
List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO); List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO);
// 订单创建完后的逻辑 // 订单创建完后的逻辑
afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO); afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO);
// 3.3 校验订单类型
// 拼团
if (ObjectUtil.equal(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
MemberUserRespDTO user = memberUserApi.getUser(userId);
// TODO 拼团一次应该只能选择一种规格的商品
combinationApi.createRecord(TradeOrderConvert.INSTANCE.convert(order, orderItems.get(0), createReqVO, user)
.setStatus(CombinationRecordStatusEnum.NOT_PAY.getStatus()));
}
// TODO 秒杀扣减库存是下单就扣除还是等待订单支付成功再扣除
if (ObjectUtil.equal(TradeOrderTypeEnum.SECKILL.getType(), order.getType())) {
}
// TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来! // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
return order; return order;
} }
@ -185,19 +204,42 @@ public class TradeOrderServiceImpl implements TradeOrderService {
return address; return address;
} }
/**
* 校验活动返回订单类型
*
* @param createReqVO 请求参数
* @return 订单类型
*/
private Integer validateActivity(AppTradeOrderCreateReqVO createReqVO) {
if (createReqVO.getSeckillActivityId() != null) {
return TradeOrderTypeEnum.SECKILL.getType();
}
if (createReqVO.getCombinationActivityId() != null) {
return TradeOrderTypeEnum.COMBINATION.getType();
}
// TODO 砍价敬请期待
return TradeOrderTypeEnum.NORMAL.getType();
}
private TradeOrderDO createTradeOrder(Long userId, String clientIp, AppTradeOrderCreateReqVO createReqVO, private TradeOrderDO createTradeOrder(Long userId, String clientIp, AppTradeOrderCreateReqVO createReqVO,
TradePriceCalculateRespBO calculateRespBO, AddressRespDTO address) { TradePriceCalculateRespBO calculateRespBO) {
// 用户选择物流配送的时候才需要填写收货地址
AddressRespDTO address = new AddressRespDTO();
if (ObjectUtil.equal(createReqVO.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getMode())) {
// 用户收件地址的校验
address = validateAddress(userId, createReqVO.getAddressId());
}
TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address); 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 @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的;
order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
order.setType(TradeOrderTypeEnum.NORMAL.getType());
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
order.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源? order.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源?
// 支付信息 // 支付信息
order.setAdjustPrice(0).setPayStatus(false); order.setAdjustPrice(0).setPayStatus(false);
// 物流信息 TODO 芋艿暂时写死物流方式应该是前端选择的 // 物流信息 TODO 芋艿暂时写死物流方式应该是前端选择的
order.setDeliveryType(DeliveryTypeEnum.EXPRESS.getMode()).setDeliveryStatus(TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus()); order.setDeliveryType(createReqVO.getDeliveryType()).setDeliveryStatus(TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus());
// 退款信息 // 退款信息
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
tradeOrderMapper.insert(order); tradeOrderMapper.insert(order);
@ -212,7 +254,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
/** /**
* 执行创建完创建完订单后的逻辑 * 执行创建完创建完订单后的逻辑
* <p> *
* 例如说优惠劵的扣减积分的扣减支付单的创建等等 * 例如说优惠劵的扣减积分的扣减支付单的创建等等
* *
* @param userId 用户编号 * @param userId 用户编号
@ -254,6 +296,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void updateOrderPaid(Long id, Long payOrderId) { public void updateOrderPaid(Long id, Long payOrderId) {
// 校验并获得交易订单可支付 // 校验并获得交易订单可支付
KeyValue<TradeOrderDO, PayOrderRespDTO> orderResult = validateOrderPayable(id, payOrderId); KeyValue<TradeOrderDO, PayOrderRespDTO> orderResult = validateOrderPayable(id, payOrderId);
@ -267,7 +310,12 @@ public class TradeOrderServiceImpl implements TradeOrderService {
if (updateCount == 0) { if (updateCount == 0) {
throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID); throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
} }
// 校验活动
// 1拼团活动
if (ObjectUtil.equal(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
// 更新拼团状态 TODO puhui999订单支付失败或订单支付过期删除这条拼团记录
combinationApi.updateRecordStatusAndStartTime(order.getUserId(), order.getId(), CombinationRecordStatusEnum.ONGOING.getStatus());
}
// TODO 芋艿发送订单变化的消息 // TODO 芋艿发送订单变化的消息
// TODO 芋艿发送站内信 // TODO 芋艿发送站内信
@ -277,7 +325,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
/** /**
* 校验交易订单满足被支付的条件 * 校验交易订单满足被支付的条件
* <p> *
* 1. 交易订单未支付 * 1. 交易订单未支付
* 2. 支付单已支付 * 2. 支付单已支付
* *
@ -308,7 +356,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId); PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
if (payOrder == null) { if (payOrder == null) {
log.error("[validateOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId); log.error("[validateOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
throw exception(cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_NOT_FOUND); throw exception(ORDER_NOT_FOUND);
} }
// 校验支付单已支付 // 校验支付单已支付
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
@ -331,29 +379,50 @@ public class TradeOrderServiceImpl implements TradeOrderService {
return new KeyValue<>(order, payOrder); return new KeyValue<>(order, payOrder);
} }
// TODO 芋艿如果无需发货需要怎么存储
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void deliveryOrder(Long userId, TradeOrderDeliveryReqVO deliveryReqVO) { public void deliveryOrder(Long userId, TradeOrderDeliveryReqVO deliveryReqVO) {
// 校验并获得交易订单可发货 // 校验并获得交易订单可发货
TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId()); TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId());
// TODO 芋艿logisticsId 校验存在 发货物流公司 fix
DeliveryExpressDO deliveryExpress = deliveryExpressService.getDeliveryExpress(deliveryReqVO.getLogisticsId()); TradeOrderDO tradeOrderDO = new TradeOrderDO();
if (deliveryExpress == null) { List<TradeOrderDeliveryDO> deliveryDOs = new ArrayList<>();
throw exception(EXPRESS_NOT_EXISTS); /* TODO
* fix: 首先需要店铺设置配送方式如 自提 配送物流-配送物流-配送-自提商家配送
* 1.如果店铺有设置配送方式用户只填写收货地址的情况下店家后台自己选择配送方式
* 2.如果店铺只支持到店自提那么下单后默认发货不需要物流
* 3.如果店铺支持 物流-配送-自提 的情况下后台不需要选择配送方式按前端用户选择的配送方式发货即可
*/
// 判断发货类型
// 快递发货
if (ObjectUtil.equal(deliveryReqVO.getType(), DeliveryTypeEnum.EXPRESS.getMode())) {
deliveryDOs = express(order, deliveryReqVO);
tradeOrderDO.setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo());
}
// 用户自提
if (ObjectUtil.equal(deliveryReqVO.getType(), DeliveryTypeEnum.PICK_UP.getMode())) {
deliveryDOs = pickUp(order, deliveryReqVO);
// 重置一下确保快递公司和快递单号为空
tradeOrderDO.setLogisticsId(null).setLogisticsNo("");
}
// TODO 芋艿如果无需发货需要怎么存储
if (ObjectUtil.equal(deliveryReqVO.getType(), DeliveryTypeEnum.NULL.getMode())) {
// TODO 情况一正常走发货逻辑和用户自提有点像 不同点不需要自提门店只需要用户确认收货
// TODO 情况二用户下单付款后直接确认收货或等待用户确认收货
} }
// 更新 TradeOrderDO 状态为已发货等待收货 // 更新 TradeOrderDO 状态为已发货等待收货
int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), tradeOrderDO.setStatus(TradeOrderStatusEnum.DELIVERED.getStatus())
new TradeOrderDO().setStatus(TradeOrderStatusEnum.DELIVERED.getStatus()) .setDeliveryStatus(TradeOrderDeliveryStatusEnum.DELIVERED.getStatus()).setDeliveryTime(LocalDateTime.now());
.setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo()) int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), tradeOrderDO);
.setDeliveryStatus(TradeOrderDeliveryStatusEnum.DELIVERED.getStatus()).setDeliveryTime(LocalDateTime.now()));
if (updateCount == 0) { if (updateCount == 0) {
throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED); throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED);
} }
// 发货成功记录发货表
orderDeliveryMapper.insertBatch(deliveryDOs);
// TODO 芋艿发送订单变化的消息 // TODO 芋艿发送订单变化的消息
// TODO 芋艿发送站内信 fix // 发送站内信
// 1构造消息 // 1构造消息
Map<String, Object> msgMap = new HashMap<>(); Map<String, Object> msgMap = new HashMap<>();
msgMap.put("orderId", deliveryReqVO.getId()); msgMap.put("orderId", deliveryReqVO.getId());
@ -364,16 +433,59 @@ public class TradeOrderServiceImpl implements TradeOrderService {
.setUserId(userId) .setUserId(userId)
.setTemplateCode("order_delivery") .setTemplateCode("order_delivery")
.setTemplateParams(msgMap)); .setTemplateParams(msgMap));
// TODO 芋艿OrderLog
// TODO 设计like是否要单独一个 delivery 发货单表 // TODO 芋艿OrderLog
// TODO 设计niu要不要支持一个订单下多个 order item 单独发货类似有赞
// TODO 设计lili是不是发货后才支持售后 // TODO 设计lili是不是发货后才支持售后
} }
private List<TradeOrderDeliveryDO> express(TradeOrderDO order, TradeOrderDeliveryReqVO deliveryReqVO) {
// 校验快递公司
DeliveryExpressDO deliveryExpress = deliveryExpressService.getDeliveryExpress(deliveryReqVO.getLogisticsId());
if (deliveryExpress == null) {
throw exception(EXPRESS_NOT_EXISTS);
}
// 校验发货商品
validateDeliveryOrderItem(order, deliveryReqVO);
// 创建发货记录
return TradeOrderConvert.INSTANCE.covert(order, deliveryReqVO);
}
private List<TradeOrderDeliveryDO> pickUp(TradeOrderDO order, TradeOrderDeliveryReqVO deliveryReqVO) {
// TODO 校验自提门店是否存在
// 重置一下确保快递公司和快递单号为空
deliveryReqVO.setLogisticsId(null);
deliveryReqVO.setLogisticsNo("");
// 校验发货商品
validateDeliveryOrderItem(order, deliveryReqVO);
// 创建发货记录
return TradeOrderConvert.INSTANCE.covert(order, deliveryReqVO);
}
private void validateDeliveryOrderItem(TradeOrderDO order, TradeOrderDeliveryReqVO deliveryReqVO) {
// TODO 设计like是否要单独一个 delivery 发货单表 fix: 多商品可分开单独发货添加 trade_order_delivery 交易订单发货日志表关联发货所选订单项设置物流单号
// TODO 设计niu要不要支持一个订单下多个 order item 单独发货类似有赞 fix
// 校验发货商品
if (CollUtil.isEmpty(deliveryReqVO.getOrderItemIds())) {
throw exception(ORDER_DELIVERY_FAILED_ITEMS_NOT_EMPTY);
}
// 校验发货商品是否存在
List<TradeOrderItemDO> orderItemDOs = tradeOrderItemMapper.selectListByOrderId(order.getId());
Set<Long> itemIds = convertSet(orderItemDOs, TradeOrderItemDO::getId);
if (!itemIds.containsAll(deliveryReqVO.getOrderItemIds())) {
throw exception(ORDER_DELIVERY_FAILED_ITEM_NOT_EXISTS);
}
// 校验所选订单项是否存在有已发货的
List<TradeOrderDeliveryDO> deliveryDOList = orderDeliveryMapper.selsectListByOrderIdAndItemIds(order.getId(), deliveryReqVO.getOrderItemIds());
if (CollUtil.isNotEmpty(deliveryDOList)) {
HashSet<Long> hashSet = CollUtil.newHashSet(deliveryReqVO.getOrderItemIds());
hashSet.retainAll(convertSet(deliveryDOList, TradeOrderDeliveryDO::getOrderItemId));
throw exception(ORDER_DELIVERY_FAILED_ITEM_ALREADY_DELIVERY);
}
}
/** /**
* 校验交易订单满足被发货的条件 * 校验交易订单满足被发货的条件
* <p> *
* 1. 交易订单未发货 * 1. 交易订单未发货
* *
* @param id 交易订单编号 * @param id 交易订单编号
@ -390,6 +502,19 @@ public class TradeOrderServiceImpl implements TradeOrderService {
|| ObjectUtil.notEqual(order.getDeliveryStatus(), TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus())) { || ObjectUtil.notEqual(order.getDeliveryStatus(), TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus())) {
throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED); throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED);
} }
// 校验订单是否退款
if (ObjectUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
throw exception(ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE);
}
// 订单类型拼团
if (ObjectUtil.equal(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
// 校验订单拼团是否成功
// TODO 用户 ID 使用当前登录用户的还是订单保存的
if (combinationApi.validateRecordStatusIsSuccess(order.getUserId(), order.getId())) {
throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS);
}
}
// TODO puhui999: 校验订单砍价是否成功
return order; return order;
} }
@ -421,7 +546,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
/** /**
* 校验交易订单满足可售货的条件 * 校验交易订单满足可售货的条件
* <p> *
* 1. 交易订单待收货 * 1. 交易订单待收货
* *
* @param userId 用户编号 * @param userId 用户编号
@ -556,6 +681,30 @@ public class TradeOrderServiceImpl implements TradeOrderService {
return tradeOrderMapper.selectOrderByIdAndUserId(orderId, loginUserId); return tradeOrderMapper.selectOrderByIdAndUserId(orderId, loginUserId);
} }
@Override
public Long createOrderItemComment(AppTradeOrderItemCommentCreateReqVO createReqVO) {
Long loginUserId = getLoginUserId();
// 先通过订单项 ID 查询订单项是否存在
TradeOrderItemDO orderItemDO = getOrderItemByIdAndUserId(createReqVO.getOrderItemId(), loginUserId);
if (orderItemDO == null) {
throw exception(ORDER_ITEM_NOT_FOUND);
}
// 校验订单
TradeOrderDO orderDO = getOrderByIdAndUserId(orderItemDO.getOrderId(), loginUserId);
if (orderDO == null) {
throw exception(ORDER_NOT_FOUND);
}
if (ObjectUtil.notEqual(orderDO.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus())) {
throw exception(ORDER_COMMENT_FAIL_STATUS_NOT_COMPLETED);
}
if (ObjectUtil.notEqual(orderDO.getCommentStatus(), Boolean.FALSE)) {
throw exception(ORDER_COMMENT_STATUS_NOT_FALSE);
}
ProductCommentCreateReqDTO productCommentCreateReqDTO = TradeOrderConvert.INSTANCE.convert04(createReqVO, orderItemDO);
return productCommentApi.createComment(productCommentCreateReqDTO);
}
/** /**
* 判断指定订单的所有订单项是不是都售后成功 * 判断指定订单的所有订单项是不是都售后成功
* *

View File

@ -19,10 +19,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
/** /**
* {@link AddressServiceImpl} 的单元测试类 * {@link AddressServiceImpl} 的单元测试类
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@Import(AddressServiceImpl.class) @Import(AddressServiceImpl.class)
public class AddressServiceImplTest extends BaseDbUnitTest { public class AddressServiceImplTest extends BaseDbUnitTest {