feat(mall): tenant-scope product, promotion and trade modules

This commit is contained in:
2026-08-01 12:19:02 +08:00
parent abf82f86ab
commit c409cf47e5
91 changed files with 847 additions and 117 deletions

View File

@@ -25,6 +25,10 @@
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.product.dal.dataobject.brand;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -20,7 +20,7 @@ import lombok.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductBrandDO extends BaseDO {
public class ProductBrandDO extends TenantBaseDO {
/**
* 品牌编号

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.product.dal.dataobject.category;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -20,7 +20,7 @@ import lombok.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductCategoryDO extends BaseDO {
public class ProductCategoryDO extends TenantBaseDO {
/**
* 父分类编号 - 根分类

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.product.dal.dataobject.comment;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -26,7 +26,7 @@ import java.util.List;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductCommentDO extends BaseDO {
public class ProductCommentDO extends TenantBaseDO {
/**
* 默认匿名昵称

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.product.dal.dataobject.favorite;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -20,7 +20,7 @@ import lombok.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductFavoriteDO extends BaseDO {
public class ProductFavoriteDO extends TenantBaseDO {
/**
* 编号,主键自增

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.product.dal.dataobject.history;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -19,7 +19,7 @@ import lombok.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductBrowseHistoryDO extends BaseDO {
public class ProductBrowseHistoryDO extends TenantBaseDO {
/**
* 记录编号
@@ -39,4 +39,4 @@ public class ProductBrowseHistoryDO extends BaseDO {
*/
private Boolean userDeleted;
}
}

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.product.dal.dataobject.property;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -19,7 +19,7 @@ import lombok.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductPropertyDO extends BaseDO {
public class ProductPropertyDO extends TenantBaseDO {
/**
* SPU 单规格时,默认属性 id

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.product.dal.dataobject.property;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -20,7 +20,7 @@ import lombok.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductPropertyValueDO extends BaseDO {
public class ProductPropertyValueDO extends TenantBaseDO {
/**
* SPU 单规格时,默认属性值 id

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.product.dal.dataobject.sku;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
@@ -26,7 +26,7 @@ import java.util.List;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductSkuDO extends BaseDO {
public class ProductSkuDO extends TenantBaseDO {
/**
* 商品 SKU 编号,自增
@@ -131,4 +131,3 @@ public class ProductSkuDO extends BaseDO {
}
}

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.product.dal.dataobject.spu;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO;
import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
@@ -28,7 +28,7 @@ import java.util.List;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductSpuDO extends BaseDO {
public class ProductSpuDO extends TenantBaseDO {
/**
* 商品 SPU 编号,自增

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.product.dal.dataobject;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO;
import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO;
import cn.iocoder.yudao.module.product.dal.dataobject.history.ProductBrowseHistoryDO;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class ProductTenantContractTest {
@Test
void allNativeProductRecordsParticipateInFrameworkTenantIsolation() {
assertThat(TenantBaseDO.class).isAssignableFrom(ProductBrandDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(ProductCategoryDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(ProductPropertyDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(ProductPropertyValueDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(ProductSpuDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(ProductSkuDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(ProductCommentDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(ProductFavoriteDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(ProductBrowseHistoryDO.class);
}
}

View File

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.promotion.api.combination.dto;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
/**
* 拼团记录的创建 Request DTO
@@ -16,40 +18,48 @@ public class CombinationRecordCreateReqDTO {
* 拼团活动编号
*/
@NotNull(message = "拼团活动编号不能为空")
@Positive(message = "拼团活动编号必须大于 0")
private Long activityId;
/**
* spu 编号
*/
@NotNull(message = "spu 编号不能为空")
@Positive(message = "spu 编号必须大于 0")
private Long spuId;
/**
* sku 编号
*/
@NotNull(message = "sku 编号不能为空")
@Positive(message = "sku 编号必须大于 0")
private Long skuId;
/**
* 购买的商品数量
*/
@NotNull(message = "购买数量不能为空")
@Positive(message = "购买数量必须大于 0")
private Integer count;
/**
* 订单编号
*/
@NotNull(message = "订单编号不能为空")
@Positive(message = "订单编号必须大于 0")
private Long orderId;
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
@Positive(message = "用户编号必须大于 0")
private Long userId;
/**
* 团长编号
*/
@PositiveOrZero(message = "团长编号不能小于 0")
private Long headId;
/**
* 拼团商品单价
*/
@NotNull(message = "拼团商品单价不能为空")
@Positive(message = "拼团商品单价必须大于 0")
private Integer combinationPrice;
}

View File

@@ -1,10 +1,15 @@
package cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.activity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -19,31 +24,38 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
public class BargainActivityBaseVO {
@Schema(description = "砍价活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "砍得越多省得越多,是兄弟就来砍我")
@NotNull(message = "砍价名称不能为空")
@NotBlank(message = "砍价名称不能为空")
@Size(max = 255, message = "砍价名称不能超过 255 个字符")
private String name;
@Schema(description = "商品 SPU 编号", example = "1")
@NotNull(message = "砍价商品不能为空")
@Positive(message = "砍价商品必须大于 0")
private Long spuId;
@Schema(description = "商品 skuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "23")
@NotNull(message = "商品 skuId 不能为空")
@Positive(message = "商品 skuId 必须大于 0")
private Long skuId;
@Schema(description = "砍价起始价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "23")
@NotNull(message = "砍价起始价格不能为空")
@Positive(message = "砍价起始价格必须大于 0")
private Integer bargainFirstPrice;
@Schema(description = "砍价底价", requiredMode = Schema.RequiredMode.REQUIRED, example = "23")
@NotNull(message = "砍价底价不能为空")
@Positive(message = "砍价底价必须大于 0")
private Integer bargainMinPrice;
@Schema(description = "活动库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "23")
@NotNull(message = "活动库存不能为空")
@Positive(message = "活动库存必须大于 0")
private Integer stock;
@Schema(description = "总限购数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "16218")
@NotNull(message = "总限购数量不能为空")
@Positive(message = "总限购数量必须大于 0")
private Integer totalLimitCount;
@Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2022-07-01 23:59:59]")
@@ -58,18 +70,40 @@ public class BargainActivityBaseVO {
@Schema(description = "最大助力次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
@NotNull(message = "最大助力次数不能为空")
@Positive(message = "最大助力次数必须大于 0")
private Integer helpMaxCount;
@Schema(description = "最大帮砍次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
@NotNull(message = "最大帮砍次数不能为空")
@Positive(message = "最大帮砍次数必须大于 0")
private Integer bargainCount;
@Schema(description = "用户每次砍价的最小金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
@NotNull(message = "用户每次砍价的最小金额不能为空")
@Positive(message = "用户每次砍价的最小金额必须大于 0")
private Integer randomMinPrice;
@Schema(description = "用户每次砍价的最大金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
@NotNull(message = "用户每次砍价的最大金额不能为空")
@Positive(message = "用户每次砍价的最大金额必须大于 0")
private Integer randomMaxPrice;
@AssertTrue(message = "活动结束时间必须晚于开始时间")
@JsonIgnore
public boolean isActivityTimeValid() {
return startTime == null || endTime == null || endTime.isAfter(startTime);
}
@AssertTrue(message = "砍价底价不能高于砍价起始价格")
@JsonIgnore
public boolean isBargainPriceValid() {
return bargainFirstPrice == null || bargainMinPrice == null || bargainMinPrice <= bargainFirstPrice;
}
@AssertTrue(message = "单次砍价最大金额不能小于最小金额")
@JsonIgnore
public boolean isRandomPriceValid() {
return randomMinPrice == null || randomMaxPrice == null || randomMinPrice <= randomMaxPrice;
}
}

View File

@@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
@Schema(description = "管理后台 - 砍价活动更新 Request VO")
@Data
@@ -15,6 +16,7 @@ public class BargainActivityUpdateReqVO extends BargainActivityBaseVO {
@Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
@NotNull(message = "活动编号不能为空")
@Positive(message = "活动编号必须大于 0")
private Long id;
}

View File

@@ -93,6 +93,8 @@ public class CombinationActivityController {
@GetMapping("/list-by-ids")
@Operation(summary = "获得拼团活动列表,基于活动编号数组")
@Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
@PreAuthorize("@ss.hasAnyPermissions('promotion:combination-activity:query', " +
"'promotion:diy-page:query', 'promotion:diy-template:query')")
public CommonResult<List<CombinationActivityRespVO>> getCombinationActivityListByIds(@RequestParam("ids") List<Long> ids) {
// 1. 获得开启的活动列表
List<CombinationActivityDO> activityList = combinationActivityService.getCombinationActivityListByIds(ids);

View File

@@ -1,10 +1,16 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -19,19 +25,23 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
public class CombinationActivityBaseVO {
@Schema(description = "拼团名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "越拼越省钱")
@NotNull(message = "拼团名称不能为空")
@NotBlank(message = "拼团名称不能为空")
@Size(max = 255, message = "拼团名称不能超过 255 个字符")
private String name;
@Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "拼团商品不能为空")
@Positive(message = "拼团商品必须大于 0")
private Long spuId;
@Schema(description = "总限购数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "16218")
@NotNull(message = "总限购数量不能为空")
@Positive(message = "总限购数量必须大于 0")
private Integer totalLimitCount;
@Schema(description = "单次限购数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "28265")
@NotNull(message = "单次限购数量不能为空")
@Positive(message = "单次限购数量必须大于 0")
private Integer singleLimitCount;
@Schema(description = "活动时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2022-07-01 23:59:59]")
@@ -46,6 +56,7 @@ public class CombinationActivityBaseVO {
@Schema(description = "开团人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
@NotNull(message = "开团人数不能为空")
@Min(value = 2, message = "开团人数不能小于 2")
private Integer userSize;
@Schema(description = "虚拟成团", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
@@ -54,6 +65,20 @@ public class CombinationActivityBaseVO {
@Schema(description = "限制时长(小时)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "限制时长不能为空")
@Positive(message = "限制时长必须大于 0")
private Integer limitDuration;
@AssertTrue(message = "活动结束时间必须晚于开始时间")
@JsonIgnore
public boolean isActivityTimeValid() {
return startTime == null || endTime == null || endTime.isAfter(startTime);
}
@AssertTrue(message = "单次限购数量不能超过总限购数量")
@JsonIgnore
public boolean isLimitCountValid() {
return totalLimitCount == null || singleLimitCount == null
|| singleLimitCount <= totalLimitCount;
}
}

View File

@@ -7,6 +7,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
@Schema(description = "管理后台 - 拼团活动创建 Request VO")
@@ -16,7 +17,7 @@ import java.util.List;
public class CombinationActivityCreateReqVO extends CombinationActivityBaseVO {
@Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
private List<CombinationProductBaseVO> products;
@NotEmpty(message = "拼团商品不能为空")
private List<@Valid CombinationProductBaseVO> products;
}

View File

@@ -7,7 +7,9 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.util.List;
@Schema(description = "管理后台 - 拼团活动更新 Request VO")
@@ -18,10 +20,11 @@ public class CombinationActivityUpdateReqVO extends CombinationActivityBaseVO {
@Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
@NotNull(message = "活动编号不能为空")
@Positive(message = "活动编号必须大于 0")
private Long id;
@Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
private List<CombinationProductBaseVO> products;
@NotEmpty(message = "拼团商品不能为空")
private List<@Valid CombinationProductBaseVO> products;
}

View File

@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
/**
* 拼团商品 Base VO提供给添加、修改、详细的子 VO 使用
@@ -14,14 +15,17 @@ public class CombinationProductBaseVO {
@Schema(description = "商品 spuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "30563")
@NotNull(message = "商品 spuId 不能为空")
@Positive(message = "商品 spuId 必须大于 0")
private Long spuId;
@Schema(description = "商品 skuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "30563")
@NotNull(message = "商品 skuId 不能为空")
@Positive(message = "商品 skuId 必须大于 0")
private Long skuId;
@Schema(description = "拼团价格,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "27682")
@NotNull(message = "拼团价格不能为空")
@Positive(message = "拼团价格必须大于 0")
private Integer combinationPrice;
}

View File

@@ -102,6 +102,8 @@ public class SeckillActivityController {
@GetMapping("/list-by-ids")
@Operation(summary = "获得秒杀活动列表,基于活动编号数组")
@Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
@PreAuthorize("@ss.hasAnyPermissions('promotion:seckill-activity:query', " +
"'promotion:diy-page:query', 'promotion:diy-template:query')")
public CommonResult<List<SeckillActivityRespVO>> getSeckillActivityListByIds(@RequestParam("ids") List<Long> ids) {
// 1. 获得开启的活动列表
List<SeckillActivityDO> activityList = seckillActivityService.getSeckillActivityListByIds(ids);

View File

@@ -80,6 +80,7 @@ public class SeckillConfigController {
@GetMapping("/simple-list")
@Operation(summary = "获得所有开启状态的秒杀时段精简列表", description = "主要用于前端的下拉选项")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<List<SeckillConfigSimpleRespVO>> getSeckillConfigSimpleList() {
List<SeckillConfigDO> list = seckillConfigService.getSeckillConfigListByStatus(
CommonStatusEnum.ENABLE.getStatus());

View File

@@ -1,10 +1,17 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;
@@ -22,10 +29,12 @@ public class SeckillActivityBaseVO {
@Schema(description = "秒杀活动商品 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "[121,1212]")
@NotNull(message = "秒杀活动商品不能为空")
@Positive(message = "秒杀活动商品必须大于 0")
private Long spuId;
@Schema(description = "秒杀活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618大促")
@NotNull(message = "秒杀活动名称不能为空")
@NotBlank(message = "秒杀活动名称不能为空")
@Size(max = 255, message = "秒杀活动名称不能超过 255 个字符")
private String name;
@Schema(description = "备注", example = "清仓大甩卖割韭菜")
@@ -43,16 +52,34 @@ public class SeckillActivityBaseVO {
@Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "排序不能为空")
@PositiveOrZero(message = "排序不能小于 0")
private Integer sort;
@Schema(description = "秒杀时段 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3]")
@NotNull(message = "秒杀时段不能为空")
private List<Long> configIds;
@NotEmpty(message = "秒杀时段不能为空")
private List<@Positive(message = "秒杀时段编号必须大于 0") Long> configIds;
@Schema(description = "总限购数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "12877")
@NotNull(message = "总限购数量不能为空")
@Positive(message = "总限购数量必须大于 0")
private Integer totalLimitCount;
@Schema(description = "单次限够数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "31683")
@NotNull(message = "单次限购数量不能为空")
@Positive(message = "单次限购数量必须大于 0")
private Integer singleLimitCount;
@AssertTrue(message = "活动结束时间必须晚于开始时间")
@JsonIgnore
public boolean isActivityTimeValid() {
return startTime == null || endTime == null || endTime.isAfter(startTime);
}
@AssertTrue(message = "单次限购数量不能超过总限购数量")
@JsonIgnore
public boolean isLimitCountValid() {
return totalLimitCount == null || singleLimitCount == null
|| singleLimitCount <= totalLimitCount;
}
}

View File

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@@ -16,6 +18,7 @@ import java.util.List;
public class SeckillActivityCreateReqVO extends SeckillActivityBaseVO {
@Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
private List<SeckillProductBaseVO> products;
@NotEmpty(message = "秒杀商品不能为空")
private List<@Valid SeckillProductBaseVO> products;
}

View File

@@ -2,6 +2,10 @@ package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@@ -15,9 +19,12 @@ import java.util.List;
public class SeckillActivityUpdateReqVO extends SeckillActivityBaseVO {
@Schema(description = "秒杀活动id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "秒杀活动编号不能为空")
@Positive(message = "秒杀活动编号必须大于 0")
private Long id;
@Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
private List<SeckillProductBaseVO> products;
@NotEmpty(message = "秒杀商品不能为空")
private List<@Valid SeckillProductBaseVO> products;
}

View File

@@ -1,11 +1,17 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.time.LocalTime;
import java.util.List;
@@ -19,35 +25,52 @@ import java.util.List;
public class SeckillConfigBaseVO {
@Schema(description = "秒杀时段名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "早上场")
@NotNull(message = "秒杀时段名称不能为空")
@NotBlank(message = "秒杀时段名称不能为空")
@Size(max = 128, message = "秒杀时段名称不能超过 128 个字符")
private String name;
@Schema(description = "开始时间点", requiredMode = Schema.RequiredMode.REQUIRED, example = "09:00:00")
@NotNull(message = "开始时间点不能为空")
@Pattern(regexp = "^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d)?$", message = "开始时间点格式必须为 HH:mm 或 HH:mm:ss")
private String startTime;
@Schema(description = "结束时间点", requiredMode = Schema.RequiredMode.REQUIRED, example = "16:00:00")
@NotNull(message = "结束时间点不能为空")
@Pattern(regexp = "^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d)?$", message = "结束时间点格式必须为 HH:mm 或 HH:mm:ss")
private String endTime;
@Schema(description = "秒杀轮播图", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/xx.png]")
@NotNull(message = "秒杀轮播图不能为空")
private List<String> sliderPicUrls;
@NotEmpty(message = "秒杀轮播图不能为空")
@Size(max = 5, message = "秒杀轮播图不能超过 5 张")
private List<@NotBlank(message = "秒杀轮播图地址不能为空") String> sliderPicUrls;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "状态不能为空")
@InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}")
private Integer status;
@AssertTrue(message = "秒杀时段开始时间和结束时间不能相等")
@JsonIgnore
public boolean isValidStartTimeValid() {
return !LocalTime.parse(startTime).equals(LocalTime.parse(endTime));
LocalTime start = parseTime(startTime);
LocalTime end = parseTime(endTime);
return start == null || end == null || !start.equals(end);
}
@AssertTrue(message = "秒杀时段开始时间不能在结束时间之后")
@JsonIgnore
public boolean isValidEndTimeValid() {
return !LocalTime.parse(startTime).isAfter(LocalTime.parse(endTime));
LocalTime start = parseTime(startTime);
LocalTime end = parseTime(endTime);
return start == null || end == null || start.isBefore(end);
}
private static LocalTime parseTime(String value) {
try {
return value == null ? null : LocalTime.parse(value);
} catch (RuntimeException ignored) {
return null;
}
}
}

View File

@@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
@Schema(description = "管理后台 - 秒杀时段更新 Request VO")
@Data
@@ -15,6 +16,7 @@ public class SeckillConfigUpdateReqVO extends SeckillConfigBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "编号不能为空")
@Positive(message = "编号必须大于 0")
private Long id;
}

View File

@@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
@Schema(description = "管理后台 - 修改时段配置状态 Request VO")
@Data
@@ -13,6 +14,7 @@ public class SeckillConfigUpdateStatusReqVo {
@Schema(description = "时段配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "时段配置编号不能为空")
@Positive(message = "时段配置编号必须大于 0")
private Long id;
@Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")

View File

@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
/**
* 秒杀参与商品 Base VO提供给添加、修改、详细的子 VO 使用
@@ -16,14 +17,17 @@ public class SeckillProductBaseVO {
@Schema(description = "商品sku_id", requiredMode = Schema.RequiredMode.REQUIRED, example = "30563")
@NotNull(message = "商品sku_id不能为空")
@Positive(message = "商品sku_id必须大于 0")
private Long skuId;
@Schema(description = "秒杀金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "6689")
@NotNull(message = "秒杀金额,单位:分不能为空")
@Positive(message = "秒杀金额必须大于 0")
private Integer seckillPrice;
@Schema(description = "秒杀库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "秒杀库存不能为空")
@Positive(message = "秒杀库存必须大于 0")
private Integer stock;
}

View File

@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
@Schema(description = "用户 App - 砍价助力的创建 Request VO")
@Data
@@ -11,6 +12,7 @@ public class AppBargainHelpCreateReqVO {
@Schema(description = "砍价记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "砍价记录编号不能为空")
@Positive(message = "砍价记录编号必须大于 0")
private Long recordId;
}

View File

@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
@Schema(description = "用户 App - 砍价记录的创建 Request VO")
@Data
@@ -11,6 +12,7 @@ public class AppBargainRecordCreateReqVO {
@Schema(description = "砍价活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "砍价活动编号不能为空")
@Positive(message = "砍价活动编号必须大于 0")
private Long activityId;
}

View File

@@ -112,8 +112,10 @@ public interface CombinationActivityConvert {
CombinationRecordDO convert(CombinationRecordCreateReqDTO reqDTO);
default CombinationRecordCreateRespDTO convert4(CombinationRecordDO combinationRecord) {
Long headId = ObjectUtil.equal(combinationRecord.getHeadId(), CombinationRecordDO.HEAD_ID_GROUP)
? combinationRecord.getId() : combinationRecord.getHeadId();
return new CombinationRecordCreateRespDTO().setCombinationActivityId(combinationRecord.getActivityId())
.setCombinationRecordId(combinationRecord.getId()).setCombinationHeadId(combinationRecord.getHeadId());
.setCombinationRecordId(combinationRecord.getId()).setCombinationHeadId(headId);
}
default CombinationRecordDO convert(CombinationRecordCreateReqDTO reqDTO,

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.bargain;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -22,7 +22,7 @@ import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BargainActivityDO extends BaseDO {
public class BargainActivityDO extends TenantBaseDO {
/**
* 砍价活动编号

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.bargain;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -19,7 +19,7 @@ import lombok.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BargainHelpDO extends BaseDO {
public class BargainHelpDO extends TenantBaseDO {
/**
* 编号

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.bargain;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.promotion.enums.bargain.BargainRecordStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -22,7 +22,7 @@ import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BargainRecordDO extends BaseDO {
public class BargainRecordDO extends TenantBaseDO {
/**
* 编号

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.combination;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -22,7 +22,7 @@ import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CombinationActivityDO extends BaseDO {
public class CombinationActivityDO extends TenantBaseDO {
/**
* 活动编号

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.combination;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -21,7 +21,7 @@ import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CombinationProductDO extends BaseDO {
public class CombinationProductDO extends TenantBaseDO {
/**
* 编号

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.combination;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -26,7 +26,7 @@ import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CombinationRecordDO extends BaseDO {
public class CombinationRecordDO extends TenantBaseDO {
/**
* 团长编号 - 团长

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.coupon;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
@@ -25,7 +25,7 @@ import java.util.List;
@KeySequence("promotion_coupon_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class CouponDO extends BaseDO {
public class CouponDO extends TenantBaseDO {
// ========== 基本信息 BEGIN ==========
/**

View File

@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.coupon;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
@@ -28,7 +28,7 @@ import java.util.List;
@KeySequence("promotion_coupon_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class CouponTemplateDO extends BaseDO {
public class CouponTemplateDO extends TenantBaseDO {
/**
* 领取数量 - 不限制

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.discount;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -22,7 +22,7 @@ import java.time.LocalDateTime;
@KeySequence("promotion_discount_activity_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class DiscountActivityDO extends BaseDO {
public class DiscountActivityDO extends TenantBaseDO {
/**
* 活动编号,主键自增

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.discount;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,7 +19,7 @@ import java.time.LocalDateTime;
@KeySequence("promotion_discount_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class DiscountProductDO extends BaseDO {
public class DiscountProductDO extends TenantBaseDO {
/**
* 编号,主键自增

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.reward;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
@@ -27,7 +27,7 @@ import java.util.Map;
@KeySequence("promotion_reward_activity_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class RewardActivityDO extends BaseDO {
public class RewardActivityDO extends TenantBaseDO {
/**
* 活动编号,主键自增

View File

@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.seckill;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -24,7 +24,7 @@ import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityDO extends BaseDO {
public class SeckillActivityDO extends TenantBaseDO {
/**
* 秒杀活动编号

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.seckill;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -24,7 +24,7 @@ import java.util.List;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SeckillConfigDO extends BaseDO {
public class SeckillConfigDO extends TenantBaseDO {
/**
* 编号

View File

@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.seckill;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -17,7 +17,7 @@ import java.util.List;
*
* @author HUIHUI
*/
@TableName("promotion_seckill_product")
@TableName(value = "promotion_seckill_product", autoResultMap = true)
@KeySequence("promotion_seckill_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@@ -25,7 +25,7 @@ import java.util.List;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SeckillProductDO extends BaseDO {
public class SeckillProductDO extends TenantBaseDO {
/**
* 秒杀参与商品编号

View File

@@ -43,6 +43,7 @@ public interface BargainActivityMapper extends BaseMapperX<BargainActivityDO> {
if (count > 0) {
return update(null, new LambdaUpdateWrapper<BargainActivityDO>()
.eq(BargainActivityDO::getId, id)
.apply("stock + {0} <= total_stock", count)
.setSql("stock = stock + " + count));
}
// 情况二:扣减库存

View File

@@ -5,6 +5,7 @@ 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.CombinationProductPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
@@ -35,4 +36,14 @@ public interface CombinationProductMapper extends BaseMapperX<CombinationProduct
return selectList(CombinationProductDO::getActivityId, ids);
}
default int updateActivityStatusByActivityId(Long activityId, Integer status) {
return update(null, new LambdaUpdateWrapper<CombinationProductDO>()
.eq(CombinationProductDO::getActivityId, activityId)
.set(CombinationProductDO::getActivityStatus, status));
}
default void deleteByActivityId(Long activityId) {
delete(CombinationProductDO::getActivityId, activityId);
}
}

View File

@@ -43,6 +43,13 @@ public interface CombinationRecordMapper extends BaseMapperX<CombinationRecordDO
.eq(CombinationRecordDO::getStatus, status));
}
default CombinationRecordDO selectByHeadIdForUpdate(Long headId, Integer status) {
return selectOne(new LambdaQueryWrapperX<CombinationRecordDO>()
.eq(CombinationRecordDO::getId, headId)
.eq(CombinationRecordDO::getStatus, status)
.last("FOR UPDATE"));
}
/**
* 查询拼团记录
*

View File

@@ -37,6 +37,11 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
SeckillActivityDO::getStatus, status);
}
default Long selectCountByConfigId(Long configId) {
return selectCount(new LambdaQueryWrapperX<SeckillActivityDO>()
.apply(MyBatisUtils.findInSet("config_ids"), configId));
}
/**
* 更新活动库存(减少)
*
@@ -63,6 +68,7 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
Assert.isTrue(count > 0);
return update(null, new LambdaUpdateWrapper<SeckillActivityDO>()
.eq(SeckillActivityDO::getId, id)
.apply("stock + {0} <= total_stock", count)
.setSql("stock = stock + " + count));
}

View File

@@ -59,4 +59,10 @@ public interface SeckillProductMapper extends BaseMapperX<SeckillProductDO> {
.setSql("stock = stock + " + count));
}
default int updateActivityStatusByActivityId(Long activityId, Integer status) {
return update(null, new LambdaUpdateWrapper<SeckillProductDO>()
.eq(SeckillProductDO::getActivityId, activityId)
.set(SeckillProductDO::getActivityStatus, status));
}
}

View File

@@ -57,6 +57,7 @@ public interface ErrorCodeConstants {
// ========== 秒杀活动 1-013-008-000 ==========
ErrorCode SECKILL_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_008_000, "秒杀活动不存在");
ErrorCode SECKILL_ACTIVITY_PRODUCT_DUPLICATE = new ErrorCode(1_013_008_001, "秒杀商品 SKU 不能重复");
ErrorCode SECKILL_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1_013_008_002, "存在商品参加了其它秒杀活动,秒杀时段冲突");
ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_003, "秒杀活动已关闭,不能修改");
ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除");
@@ -66,10 +67,13 @@ public interface ErrorCodeConstants {
ErrorCode SECKILL_JOIN_ACTIVITY_STATUS_CLOSED = new ErrorCode(1_013_008_008, "秒杀失败,原因:秒杀活动已关闭");
ErrorCode SECKILL_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_008_009, "秒杀失败,原因:单次限购超出");
ErrorCode SECKILL_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_008_010, "秒杀失败,原因:商品不存在");
ErrorCode SECKILL_ACTIVITY_PRODUCT_PRICE_INVALID = new ErrorCode(1_013_008_011, "秒杀价格不能高于商品价格");
ErrorCode SECKILL_ACTIVITY_PRODUCT_STOCK_EXCEED = new ErrorCode(1_013_008_012, "秒杀库存不能超过商品库存");
// ========== 秒杀时段 1-013-009-000 ==========
ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在");
ErrorCode SECKILL_CONFIG_TIME_CONFLICTS = new ErrorCode(1_013_009_001, "秒杀时段冲突");
ErrorCode SECKILL_CONFIG_DELETE_FAIL_USED = new ErrorCode(1_013_009_002, "秒杀时段已被活动使用,不能删除");
ErrorCode SECKILL_CONFIG_DISABLE = new ErrorCode(1_013_009_004, "秒杀时段已关闭");
// ========== 拼团活动 1-013-010-000 ==========
@@ -80,6 +84,9 @@ public interface ErrorCodeConstants {
ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE = new ErrorCode(1_013_010_004, "拼团失败,原因:拼团活动已关闭");
ErrorCode COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_010_005, "拼团失败,原因:拼团活动商品不存在");
ErrorCode COMBINATION_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_010_006, "拼团失败,原因:拼团活动商品库存不足");
ErrorCode COMBINATION_ACTIVITY_PRODUCT_DUPLICATE = new ErrorCode(1_013_010_007, "拼团商品 SKU 不能重复");
ErrorCode COMBINATION_ACTIVITY_PRODUCT_PRICE_INVALID = new ErrorCode(1_013_010_008, "拼团价格不能高于商品价格");
ErrorCode COMBINATION_ACTIVITY_PRODUCT_SPU_MISMATCH = new ErrorCode(1_013_010_009, "拼团商品必须属于活动 SPU");
// ========== 拼团记录 1-013-011-000 ==========
ErrorCode COMBINATION_RECORD_NOT_EXISTS = new ErrorCode(1_013_011_000, "拼团不存在");
@@ -92,6 +99,7 @@ public interface ErrorCodeConstants {
ErrorCode COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_011_007, "拼团失败,原因:单次限购超出");
ErrorCode COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_011_008, "拼团失败,原因:超出总购买次数");
ErrorCode COMBINATION_RECORD_FAILED_ORDER_STATUS_UNPAID = new ErrorCode(1_013_011_009, "拼团失败,原因:存在未支付订单,请先支付");
ErrorCode COMBINATION_RECORD_HEAD_ACTIVITY_MISMATCH = new ErrorCode(1_013_011_010, "拼团失败,父拼团不属于当前活动");
// ========== 砍价活动 1-013-012-000 ==========
ErrorCode BARGAIN_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_012_000, "砍价活动不存在");
@@ -101,6 +109,9 @@ public interface ErrorCodeConstants {
ErrorCode BARGAIN_ACTIVITY_STOCK_NOT_ENOUGH = new ErrorCode(1_013_012_004, "砍价活动库存不足");
ErrorCode BARGAIN_ACTIVITY_STATUS_CLOSED = new ErrorCode(1_013_012_005, "砍价活动已关闭");
ErrorCode BARGAIN_ACTIVITY_TIME_END = new ErrorCode(1_013_012_006, "砍价活动已经结束");
ErrorCode BARGAIN_ACTIVITY_SKU_SPU_MISMATCH = new ErrorCode(1_013_012_007, "砍价商品 SKU 不属于活动 SPU");
ErrorCode BARGAIN_ACTIVITY_PRICE_INVALID = new ErrorCode(1_013_012_008, "砍价起始价格不能高于商品价格");
ErrorCode BARGAIN_ACTIVITY_STOCK_RESTORE_EXCEED = new ErrorCode(1_013_012_009, "砍价活动库存回补不能超过总库存");
// ========== 砍价记录 1-013-013-000 ==========
ErrorCode BARGAIN_RECORD_NOT_EXISTS = new ErrorCode(1_013_013_000, "砍价记录不存在");

View File

@@ -48,7 +48,7 @@ public class BargainActivityServiceImpl implements BargainActivityService {
// 校验商品 SPU 是否存在是否参加的别的活动
validateBargainConflict(createReqVO.getSpuId(), null);
// 校验商品 sku 是否存在
validateSku(createReqVO.getSkuId());
validateSku(createReqVO.getSpuId(), createReqVO.getSkuId(), createReqVO.getBargainFirstPrice());
// 插入砍价活动
BargainActivityDO activityDO = BargainActivityConvert.INSTANCE.convert(createReqVO)
@@ -70,7 +70,7 @@ public class BargainActivityServiceImpl implements BargainActivityService {
// 校验商品冲突
validateBargainConflict(updateReqVO.getSpuId(), updateReqVO.getId());
// 校验商品 sku 是否存在
validateSku(updateReqVO.getSkuId());
validateSku(updateReqVO.getSpuId(), updateReqVO.getSkuId(), updateReqVO.getBargainFirstPrice());
// 更新
BargainActivityDO updateObj = BargainActivityConvert.INSTANCE.convert(updateReqVO);
@@ -89,7 +89,9 @@ public class BargainActivityServiceImpl implements BargainActivityService {
throw exception(BARGAIN_ACTIVITY_STOCK_NOT_ENOUGH);
}
} else if (count > 0) {
bargainActivityMapper.updateStock(id, count);
if (bargainActivityMapper.updateStock(id, count) == 0) {
throw exception(BARGAIN_ACTIVITY_STOCK_RESTORE_EXCEED);
}
}
}
@@ -118,11 +120,17 @@ public class BargainActivityServiceImpl implements BargainActivityService {
}
}
private void validateSku(Long skuId) {
private void validateSku(Long spuId, Long skuId, Integer bargainFirstPrice) {
ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
if (sku == null) {
throw exception(SKU_NOT_EXISTS);
}
if (!ObjectUtil.equal(sku.getSpuId(), spuId)) {
throw exception(BARGAIN_ACTIVITY_SKU_SPU_MISMATCH);
}
if (bargainFirstPrice > sku.getPrice()) {
throw exception(BARGAIN_ACTIVITY_PRICE_INVALID);
}
}
@Override

View File

@@ -96,7 +96,7 @@ public class BargainHelpServiceImpl implements BargainHelpService {
// TODO 管理员:优化点:实现一个更随机的逻辑,可以按照你自己的业务;
private Integer calculateReducePrice(BargainActivityDO activity, BargainRecordDO record) {
// 1. 随机金额
Integer reducePrice = MathUtil.randomInt(activity.getBargainMinPrice(),
Integer reducePrice = MathUtil.randomInt(activity.getRandomMinPrice(),
activity.getRandomMaxPrice() + 1); // + 1 的原因是randomInt 默认不包含第二个参数
// 2. 校验是否超过砍价上限
if (record.getBargainPrice() - reducePrice < activity.getBargainMinPrice()) {

View File

@@ -18,6 +18,7 @@ import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Nullable;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -71,7 +72,7 @@ public class BargainRecordServiceImpl implements BargainRecordService {
Integer reducePrice, Boolean success) {
BargainRecordDO updateObj = new BargainRecordDO().setBargainPrice(whereBargainPrice - reducePrice);
if (success) {
updateObj.setStatus(BargainRecordStatusEnum.SUCCESS.getStatus());
updateObj.setStatus(BargainRecordStatusEnum.SUCCESS.getStatus()).setEndTime(LocalDateTime.now());
}
return bargainRecordMapper.updateByIdAndBargainPrice(id, whereBargainPrice, updateObj) > 0;
}

View File

@@ -25,8 +25,10 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -108,10 +110,21 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
// 2. 校验商品 sku 都存在
List<ProductSkuRespDTO> skus = productSkuApi.getSkuListBySpuId(singletonList(spuId));
Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
Set<Long> skuIds = new HashSet<>();
products.forEach(product -> {
if (!skuMap.containsKey(product.getSkuId())) {
if (!skuIds.add(product.getSkuId())) {
throw exception(COMBINATION_ACTIVITY_PRODUCT_DUPLICATE);
}
if (!ObjectUtil.equal(product.getSpuId(), spuId)) {
throw exception(COMBINATION_ACTIVITY_PRODUCT_SPU_MISMATCH);
}
ProductSkuRespDTO sku = skuMap.get(product.getSkuId());
if (sku == null) {
throw exception(SKU_NOT_EXISTS);
}
if (product.getCombinationPrice() > sku.getPrice()) {
throw exception(COMBINATION_ACTIVITY_PRODUCT_PRICE_INVALID);
}
});
}
@@ -130,7 +143,8 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
validateProductExists(updateReqVO.getSpuId(), updateReqVO.getProducts());
// 更新活动
CombinationActivityDO updateObj = CombinationActivityConvert.INSTANCE.convert(updateReqVO);
CombinationActivityDO updateObj = CombinationActivityConvert.INSTANCE.convert(updateReqVO)
.setStatus(activityDO.getStatus());
combinationActivityMapper.updateById(updateObj);
// 更新商品
updateCombinationProduct(updateObj, updateReqVO.getProducts());
@@ -148,6 +162,7 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
// 关闭活动
combinationActivityMapper.updateById(new CombinationActivityDO().setId(id)
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
combinationProductMapper.updateActivityStatusByActivityId(id, CommonStatusEnum.DISABLE.getStatus());
}
/**
@@ -191,6 +206,7 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
}
// 删除
combinationProductMapper.deleteByActivityId(id);
combinationActivityMapper.deleteById(id);
}

View File

@@ -80,6 +80,9 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
@Override
public KeyValue<CombinationActivityDO, CombinationProductDO> validateCombinationRecord(
Long userId, Long activityId, Long headId, Long skuId, Integer count) {
if (count == null || count <= 0) {
throw exception(COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED);
}
// 1. 校验拼团活动是否存在
CombinationActivityDO activity = combinationActivityService.validateCombinationActivityExists(activityId);
// 1.1 校验活动是否开启
@@ -99,9 +102,12 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
if (isJoinCombination(headId)) {
// 2.1. 查询进行中的父拼团
CombinationRecordDO record = combinationRecordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
if (record == null) {
if (record == null || ObjUtil.notEqual(record.getHeadId(), CombinationRecordDO.HEAD_ID_GROUP)) {
throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS);
}
if (ObjUtil.notEqual(record.getActivityId(), activityId)) {
throw exception(COMBINATION_RECORD_HEAD_ACTIVITY_MISMATCH);
}
// 2.2. 校验拼团是否已满
if (ObjUtil.equal(record.getUserCount(), record.getUserSize())) {
throw exception(COMBINATION_RECORD_USER_FULL);
@@ -122,6 +128,9 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
if (product == null) {
throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
}
if (ObjUtil.notEqual(product.getSpuId(), activity.getSpuId())) {
throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
}
// 4.2 校验 sku 是否存在
ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
if (sku == null) {
@@ -165,14 +174,29 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
@Override
@Transactional(rollbackFor = Exception.class)
public CombinationRecordDO createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
if (isJoinCombination(reqDTO.getHeadId())) {
CombinationRecordDO headRecord = combinationRecordMapper.selectByHeadIdForUpdate(reqDTO.getHeadId(),
CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
if (headRecord == null
|| ObjUtil.notEqual(headRecord.getHeadId(), CombinationRecordDO.HEAD_ID_GROUP)) {
throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS);
}
}
// 1. 校验拼团活动
KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(reqDTO.getUserId(),
reqDTO.getActivityId(), reqDTO.getHeadId(), reqDTO.getSkuId(), reqDTO.getCount());
if (ObjUtil.notEqual(reqDTO.getSpuId(), keyValue.getKey().getSpuId())
|| ObjUtil.notEqual(reqDTO.getCombinationPrice(), keyValue.getValue().getCombinationPrice())) {
throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
}
// 2. 组合数据创建拼团记录
MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId());
ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId());
ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId());
if (user == null || spu == null || sku == null) {
throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
}
CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO, keyValue.getKey(), user, spu, sku);
// 2.1. 如果是团长需要设置 headId 为 CombinationRecordDO#HEAD_ID_GROUP
if (!isJoinCombination(record.getHeadId())) {

View File

@@ -30,6 +30,7 @@ import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.HashSet;
import static cn.hutool.core.collection.CollUtil.isNotEmpty;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -124,10 +125,20 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
// 2. 校验商品 sku 都存在
List<ProductSkuRespDTO> skus = productSkuApi.getSkuListBySpuId(singletonList(spuId));
Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
if (new HashSet<>(convertList(products, SeckillProductBaseVO::getSkuId)).size() != products.size()) {
throw exception(SECKILL_ACTIVITY_PRODUCT_DUPLICATE);
}
products.forEach(product -> {
if (!skuMap.containsKey(product.getSkuId())) {
ProductSkuRespDTO sku = skuMap.get(product.getSkuId());
if (sku == null) {
throw exception(SKU_NOT_EXISTS);
}
if (product.getSeckillPrice() > sku.getPrice()) {
throw exception(SECKILL_ACTIVITY_PRODUCT_PRICE_INVALID);
}
if (product.getStock() > sku.getStock()) {
throw exception(SECKILL_ACTIVITY_PRODUCT_STOCK_EXCEED);
}
});
}
@@ -185,11 +196,21 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSeckillStockIncr(Long id, Long skuId, Integer count) {
validateSeckillActivityExists(id);
SeckillProductDO product = seckillProductMapper.selectByActivityIdAndSkuId(id, skuId);
if (product == null) {
throw exception(SECKILL_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
}
// 更新活动商品库存
seckillProductMapper.updateStockIncr(product.getId(), count);
int updateCount = seckillProductMapper.updateStockIncr(product.getId(), count);
if (updateCount == 0) {
throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
}
// 更新活动库存
seckillActivityMapper.updateStockIncr(id, count);
updateCount = seckillActivityMapper.updateStockIncr(id, count);
if (updateCount == 0) {
throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
}
}
/**
@@ -223,6 +244,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void closeSeckillActivity(Long id) {
// 校验存在
SeckillActivityDO activity = validateSeckillActivityExists(id);
@@ -233,6 +255,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
// 更新
SeckillActivityDO updateObj = new SeckillActivityDO().setId(id).setStatus(CommonStatusEnum.DISABLE.getStatus());
seckillActivityMapper.updateById(updateObj);
seckillProductMapper.updateActivityStatusByActivityId(id, CommonStatusEnum.DISABLE.getStatus());
}
@Override

View File

@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.Seck
import cn.iocoder.yudao.module.promotion.convert.seckill.SeckillConfigConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillconfig.SeckillConfigMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -36,6 +37,8 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
@Resource
private SeckillConfigMapper seckillConfigMapper;
@Resource
private SeckillActivityMapper seckillActivityMapper;
@Override
public Long createSeckillConfig(SeckillConfigCreateReqVO createReqVO) {
@@ -80,6 +83,9 @@ public class SeckillConfigServiceImpl implements SeckillConfigService {
public void deleteSeckillConfig(Long id) {
// 校验存在
validateSeckillConfigExists(id);
if (seckillActivityMapper.selectCountByConfigId(id) > 0) {
throw exception(SECKILL_CONFIG_DELETE_FAIL_USED);
}
// 删除
seckillConfigMapper.deleteById(id);

View File

@@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.promotion.controller.admin.bargain;
import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.activity.BargainActivityBaseVO;
import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.activity.BargainActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.bargain.vo.help.AppBargainHelpCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.bargain.vo.record.AppBargainRecordCreateReqVO;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import static org.assertj.core.api.Assertions.assertThat;
class BargainRequestValidationContractTest {
@Test
void activityRequestsRequireBoundedNameAndPositiveValues() throws Exception {
assertThat(BargainActivityBaseVO.class.getDeclaredField("name")
.isAnnotationPresent(NotBlank.class)).isTrue();
assertThat(BargainActivityBaseVO.class.getDeclaredField("name")
.getAnnotation(Size.class).max()).isEqualTo(255);
for (String field : new String[]{"spuId", "skuId", "bargainFirstPrice", "bargainMinPrice",
"stock", "totalLimitCount", "helpMaxCount", "bargainCount", "randomMinPrice", "randomMaxPrice"}) {
assertThat(BargainActivityBaseVO.class.getDeclaredField(field)
.isAnnotationPresent(Positive.class)).as(field).isTrue();
}
assertThat(BargainActivityUpdateReqVO.class.getDeclaredField("id")
.isAnnotationPresent(Positive.class)).isTrue();
}
@Test
void activityRequestRejectsReversedTimesAndInvalidPriceRelationships() {
BargainActivityBaseVO request = new BargainActivityBaseVO();
request.setStartTime(LocalDateTime.of(2026, 1, 2, 0, 0));
request.setEndTime(LocalDateTime.of(2026, 1, 1, 0, 0));
request.setBargainFirstPrice(100);
request.setBargainMinPrice(101);
request.setRandomMinPrice(20);
request.setRandomMaxPrice(19);
assertThat(request.isActivityTimeValid()).isFalse();
assertThat(request.isBargainPriceValid()).isFalse();
assertThat(request.isRandomPriceValid()).isFalse();
}
@Test
void appRequestsRequirePositiveIdentifiers() throws Exception {
assertThat(AppBargainRecordCreateReqVO.class.getDeclaredField("activityId")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(AppBargainHelpCreateReqVO.class.getDeclaredField("recordId")
.isAnnotationPresent(Positive.class)).isTrue();
}
}

View File

@@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityBaseVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
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 jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import org.junit.jupiter.api.Test;
import java.lang.reflect.AnnotatedParameterizedType;
import java.time.LocalDateTime;
import static org.assertj.core.api.Assertions.assertThat;
class CombinationRequestValidationContractTest {
@Test
void activityRequestsRequireProductsPositiveIdentifiersAndAtLeastTwoMembers() throws Exception {
assertThat(CombinationActivityCreateReqVO.class.getDeclaredField("products")
.isAnnotationPresent(NotEmpty.class)).isTrue();
AnnotatedParameterizedType productsType = (AnnotatedParameterizedType)
CombinationActivityCreateReqVO.class.getDeclaredField("products").getAnnotatedType();
assertThat(productsType.getAnnotatedActualTypeArguments()[0].isAnnotationPresent(Valid.class)).isTrue();
assertThat(CombinationActivityUpdateReqVO.class.getDeclaredField("id")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(CombinationActivityBaseVO.class.getDeclaredField("spuId")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(CombinationActivityBaseVO.class.getDeclaredField("userSize")
.getAnnotation(Min.class).value()).isEqualTo(2L);
assertThat(CombinationProductBaseVO.class.getDeclaredField("skuId")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(CombinationProductBaseVO.class.getDeclaredField("combinationPrice")
.isAnnotationPresent(Positive.class)).isTrue();
}
@Test
void activityRequestRejectsReversedTimesAndInvalidLimitRelationship() {
CombinationActivityBaseVO request = new CombinationActivityBaseVO();
request.setStartTime(LocalDateTime.of(2026, 1, 2, 0, 0));
request.setEndTime(LocalDateTime.of(2026, 1, 1, 0, 0));
request.setTotalLimitCount(2);
request.setSingleLimitCount(3);
assertThat(request.isActivityTimeValid()).isFalse();
assertThat(request.isLimitCountValid()).isFalse();
}
@Test
void recordCreationRequiresPositiveOrderUserProductAndNonNegativeHead() throws Exception {
assertThat(CombinationRecordCreateReqDTO.class.getDeclaredField("activityId")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(CombinationRecordCreateReqDTO.class.getDeclaredField("orderId")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(CombinationRecordCreateReqDTO.class.getDeclaredField("userId")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(CombinationRecordCreateReqDTO.class.getDeclaredField("combinationPrice")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(CombinationRecordCreateReqDTO.class.getDeclaredField("headId")
.isAnnotationPresent(PositiveOrZero.class)).isTrue();
}
}

View File

@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigBaseVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Positive;
import org.junit.jupiter.api.Test;
import java.lang.reflect.AnnotatedParameterizedType;
import static org.assertj.core.api.Assertions.assertThat;
class SeckillRequestValidationContractTest {
@Test
void activityRequestsRequirePositiveProductsAndIdentifiers() throws Exception {
assertThat(SeckillActivityCreateReqVO.class.getDeclaredField("products")
.isAnnotationPresent(NotEmpty.class)).isTrue();
AnnotatedParameterizedType productsType = (AnnotatedParameterizedType)
SeckillActivityCreateReqVO.class.getDeclaredField("products").getAnnotatedType();
assertThat(productsType.getAnnotatedActualTypeArguments()[0].isAnnotationPresent(Valid.class)).isTrue();
assertThat(SeckillActivityUpdateReqVO.class.getDeclaredField("id")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(SeckillProductBaseVO.class.getDeclaredField("seckillPrice")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(SeckillProductBaseVO.class.getDeclaredField("stock")
.isAnnotationPresent(Positive.class)).isTrue();
}
@Test
void configRequestsRequireAtLeastOneSliderAndRejectReversedTimes() throws Exception {
assertThat(SeckillConfigBaseVO.class.getDeclaredField("sliderPicUrls")
.isAnnotationPresent(NotEmpty.class)).isTrue();
SeckillConfigBaseVO request = new SeckillConfigBaseVO();
request.setStartTime("12:00");
request.setEndTime("11:00");
assertThat(request.isValidEndTimeValid()).isFalse();
}
}

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.promotion.convert.combination;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class CombinationActivityConvertContractTest {
@Test
void headCreationReturnsPersistedHeadRecordIdInsteadOfSentinel() {
CombinationRecordDO head = new CombinationRecordDO().setId(123L).setActivityId(45L)
.setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
assertThat(CombinationActivityConvert.INSTANCE.convert4(head))
.satisfies(response -> {
assertThat(response.getCombinationActivityId()).isEqualTo(45L);
assertThat(response.getCombinationRecordId()).isEqualTo(123L);
assertThat(response.getCombinationHeadId()).isEqualTo(123L);
});
}
}

View File

@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainHelpDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainRecordDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class PromotionActivityTenantContractTest {
@Test
void checkoutPromotionRecordsParticipateInFrameworkTenantIsolation() {
assertThat(TenantBaseDO.class).isAssignableFrom(DiscountActivityDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(DiscountProductDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(RewardActivityDO.class);
}
@Test
void seckillRecordsParticipateInFrameworkTenantIsolation() {
assertThat(TenantBaseDO.class).isAssignableFrom(SeckillActivityDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(SeckillProductDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(SeckillConfigDO.class);
}
@Test
void combinationRecordsParticipateInFrameworkTenantIsolation() {
assertThat(TenantBaseDO.class).isAssignableFrom(CombinationActivityDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(CombinationProductDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(CombinationRecordDO.class);
}
@Test
void bargainRecordsParticipateInFrameworkTenantIsolation() {
assertThat(TenantBaseDO.class).isAssignableFrom(BargainActivityDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(BargainRecordDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(BargainHelpDO.class);
}
}

View File

@@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.coupon;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class CouponTenantContractTest {
@Test
void nativeCouponRecordsParticipateInFrameworkTenantIsolation() {
assertThat(TenantBaseDO.class).isAssignableFrom(CouponTemplateDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(CouponDO.class);
}
}

View File

@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.promotion.service.bargain;
import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainRecordDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.bargain.BargainRecordMapper;
import cn.iocoder.yudao.module.promotion.enums.bargain.BargainRecordStatusEnum;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class BargainServiceBehaviorContractTest {
@Test
void calculateReducePriceUsesConfiguredRandomRange() {
BargainHelpServiceImpl service = new BargainHelpServiceImpl();
BargainActivityDO activity = new BargainActivityDO()
.setRandomMinPrice(7)
.setRandomMaxPrice(7)
.setBargainMinPrice(10);
BargainRecordDO record = new BargainRecordDO().setBargainPrice(100);
Integer reducePrice = ReflectionTestUtils.invokeMethod(service, "calculateReducePrice", activity, record);
assertThat(reducePrice).isEqualTo(7);
}
@Test
void successfulPriceUpdateStoresStatusAndEndTime() {
BargainRecordMapper mapper = mock(BargainRecordMapper.class);
BargainRecordServiceImpl service = new BargainRecordServiceImpl();
ReflectionTestUtils.setField(service, "bargainRecordMapper", mapper);
when(mapper.updateByIdAndBargainPrice(eq(1L), eq(100), org.mockito.ArgumentMatchers.any()))
.thenReturn(1);
assertThat(service.updateBargainRecordBargainPrice(1L, 100, 20, true)).isTrue();
ArgumentCaptor<BargainRecordDO> updateCaptor = ArgumentCaptor.forClass(BargainRecordDO.class);
verify(mapper).updateByIdAndBargainPrice(eq(1L), eq(100), updateCaptor.capture());
assertThat(updateCaptor.getValue().getBargainPrice()).isEqualTo(80);
assertThat(updateCaptor.getValue().getStatus()).isEqualTo(BargainRecordStatusEnum.SUCCESS.getStatus());
assertThat(updateCaptor.getValue().getEndTime()).isNotNull();
}
}

View File

@@ -103,7 +103,7 @@ public class AfterSaleController {
@PutMapping("/disagree")
@Operation(summary = "拒绝售后")
@PreAuthorize("@ss.hasPermission('trade:after-sale:disagree')")
public CommonResult<Boolean> disagreeAfterSale(@RequestBody AfterSaleDisagreeReqVO confirmReqVO) {
public CommonResult<Boolean> disagreeAfterSale(@RequestBody @Valid AfterSaleDisagreeReqVO confirmReqVO) {
afterSaleService.disagreeAfterSale(getLoginUserId(), confirmReqVO);
return success(true);
}
@@ -121,7 +121,7 @@ public class AfterSaleController {
@Operation(summary = "拒绝收货")
@Parameter(name = "id", description = "售后编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('trade:after-sale:receive')")
public CommonResult<Boolean> refuseAfterSale(AfterSaleRefuseReqVO refuseReqVO) {
public CommonResult<Boolean> refuseAfterSale(@Valid AfterSaleRefuseReqVO refuseReqVO) {
afterSaleService.refuseAfterSale(getLoginUserId(), refuseReqVO);
return success(true);
}

View File

@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderItemBas
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 售后订单的详情 Response VO")
@@ -17,7 +18,8 @@ public class AfterSaleDetailRespVO extends AfterSaleBaseVO {
@Schema(description = "售后编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "申请时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
/**
* 订单基本信息

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@Schema(description = "管理后台 - 交易售后拒绝收货 Request VO")
@@ -14,7 +15,7 @@ public class AfterSaleRefuseReqVO {
private Long id;
@Schema(description = "收货备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜")
@NotNull(message = "收货备注不能为空")
@NotBlank(message = "收货备注不能为空")
private String refuseMemo;
}

View File

@@ -85,6 +85,9 @@ public class BrokerageUserController {
@PreAuthorize("@ss.hasPermission('trade:brokerage-user:query')")
public CommonResult<BrokerageUserRespVO> getBrokerageUser(@RequestParam("id") Long id) {
BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(id);
if (brokerageUser == null) {
return success(null);
}
// TODO @疯狂:是不是搞成一个统一的 convert
BrokerageUserRespVO respVO = BrokerageUserConvert.INSTANCE.convert(brokerageUser);
return success(BrokerageUserConvert.INSTANCE.copyTo(memberUserApi.getUser(id), respVO));

View File

@@ -4,7 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@Schema(description = "管理后台 - 驳回申请 Request VO")
@@ -17,7 +17,7 @@ public class BrokerageWithdrawRejectReqVO {
private Long id;
@Schema(description = "审核驳回原因", example = "不对")
@NotEmpty(message = "审核驳回原因不能为空")
@NotBlank(message = "审核驳回原因不能为空")
private String auditReason;
}

View File

@@ -6,24 +6,25 @@ import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Positive;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import jakarta.validation.Validator;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
@Schema(description = "用户 App - 分销提现创建 Request VO")
@Data
public class AppBrokerageWithdrawCreateReqVO {
@Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "提现方式不能为空")
@InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
private Integer type;
@Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
@PositiveOrZero(message = "提现金额不能小于 0")
@Positive(message = "提现金额必须大于 0")
@Min(value = 30, message = "微信提现金额不能小于 0.3", groups = {WechatApi.class})
@NotNull(message = "提现金额不能为空")
private Integer price;

View File

@@ -44,6 +44,11 @@ public interface BrokerageRecordConvert {
Integer status = brokerageFrozenDays > 0
? BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus()
: BrokerageRecordStatusEnum.SETTLEMENT.getStatus();
// 即时结算也要记录结算时刻。排行、汇总接口按 unfreeze_time 过滤,留空会导致
// 无冻结期的正常佣金永远不出现在时间范围统计中。
if (BrokerageRecordStatusEnum.SETTLEMENT.getStatus().equals(status) && unfreezeTime == null) {
unfreezeTime = LocalDateTime.now();
}
return new BrokerageRecordDO().setUserId(user.getId())
.setBizType(bizType.getType()).setBizId(bizId)
.setPrice(brokeragePrice).setTotalPrice(user.getBrokeragePrice())

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.aftersale;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleStatusEnum;
@@ -23,7 +23,7 @@ import java.util.List;
@TableName(value = "trade_after_sale", autoResultMap = true)
@KeySequence("trade_after_sale_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class AfterSaleDO extends BaseDO {
public class AfterSaleDO extends TenantBaseDO {
/**
* 售后编号,主键自增

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.aftersale;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleOperateTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -21,7 +21,7 @@ import lombok.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AfterSaleLogDO extends BaseDO {
public class AfterSaleLogDO extends TenantBaseDO {
/**
* 编号

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -23,7 +23,7 @@ import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BrokerageRecordDO extends BaseDO {
public class BrokerageRecordDO extends TenantBaseDO {
/**
* 编号

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -21,7 +21,7 @@ import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BrokerageUserDO extends BaseDO {
public class BrokerageUserDO extends TenantBaseDO {
/**
* 用户编号

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -23,7 +23,7 @@ import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BrokerageWithdrawDO extends BaseDO {
public class BrokerageWithdrawDO extends TenantBaseDO {
/**
* 编号

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.cart;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -15,7 +15,7 @@ import lombok.Data;
@TableName("trade_cart")
@KeySequence("trade_cart_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class CartDO extends BaseDO {
public class CartDO extends TenantBaseDO {
// ========= 基础字段 BEGIN =========

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.config;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
@@ -27,7 +27,7 @@ import java.util.List;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TradeConfigDO extends BaseDO {
public class TradeConfigDO extends TenantBaseDO {
/**
* 自增主键

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.delivery;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -15,7 +15,7 @@ import lombok.Data;
@TableName(value ="trade_delivery_express")
@KeySequence("trade_delivery_express_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class DeliveryExpressDO extends BaseDO {
public class DeliveryExpressDO extends TenantBaseDO {
/**
* 编号,自增

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.delivery;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
@@ -18,7 +18,7 @@ import java.util.List;
@TableName(value ="trade_delivery_express_template_charge", autoResultMap = true)
@KeySequence("trade_delivery_express_template_charge_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class DeliveryExpressTemplateChargeDO extends BaseDO {
public class DeliveryExpressTemplateChargeDO extends TenantBaseDO {
/**
* 编号,自增

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.delivery;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -15,7 +15,7 @@ import lombok.*;
@TableName("trade_delivery_express_template")
@KeySequence("trade_delivery_express_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class DeliveryExpressTemplateDO extends BaseDO {
public class DeliveryExpressTemplateDO extends TenantBaseDO {
/**
* 编号,自增

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.delivery;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
@@ -18,7 +18,7 @@ import java.util.List;
@TableName(value ="trade_delivery_express_template_free", autoResultMap = true)
@KeySequence("trade_delivery_express_template_free_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class DeliveryExpressTemplateFreeDO extends BaseDO {
public class DeliveryExpressTemplateFreeDO extends TenantBaseDO {
/**
* 编号

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.delivery;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -21,7 +21,7 @@ import java.util.List;
@TableName(value ="trade_delivery_pick_up_store", autoResultMap = true)
@KeySequence("trade_delivery_pick_up_store_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class DeliveryPickUpStoreDO extends BaseDO {
public class DeliveryPickUpStoreDO extends TenantBaseDO {
/**
* 编号

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.order;
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
@@ -35,7 +35,7 @@ import java.util.Map;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TradeOrderDO extends BaseDO {
public class TradeOrderDO extends TenantBaseDO {
/**
* 发货物流公司编号 - 空(无需发货)

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.order;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
@@ -21,7 +21,7 @@ import java.util.List;
@TableName(value = "trade_order_item", autoResultMap = true)
@KeySequence("trade_order_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class TradeOrderItemDO extends BaseDO {
public class TradeOrderItemDO extends TenantBaseDO {
// ========== 订单项基本信息 ==========
/**
@@ -208,4 +208,3 @@ public class TradeOrderItemDO extends BaseDO {
}
}

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.order;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderOperateTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -21,7 +21,7 @@ import lombok.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TradeOrderLogDO extends BaseDO {
public class TradeOrderLogDO extends TenantBaseDO {
/**
* 用户类型 - 系统

View File

@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.trade.dal.dataobject;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleLogDO;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class AfterSaleTenantContractTest {
@Test
void activatedAfterSaleRecordsParticipateInFrameworkTenantIsolation() {
assertThat(TenantBaseDO.class).isAssignableFrom(AfterSaleDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(AfterSaleLogDO.class);
}
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.trade.dal.dataobject;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRejectReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class BrokerageTenantContractTest {
@Test
void nativeBrokerageRecordsParticipateInTenantIsolation() {
assertThat(TenantBaseDO.class).isAssignableFrom(BrokerageUserDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(BrokerageRecordDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(BrokerageWithdrawDO.class);
}
@Test
void withdrawalRequestsRejectMissingTypesNonPositiveAmountsAndBlankAuditReasons() throws Exception {
assertThat(AppBrokerageWithdrawCreateReqVO.class.getDeclaredField("type")
.isAnnotationPresent(NotNull.class)).isTrue();
assertThat(AppBrokerageWithdrawCreateReqVO.class.getDeclaredField("price")
.isAnnotationPresent(Positive.class)).isTrue();
assertThat(BrokerageWithdrawRejectReqVO.class.getDeclaredField("auditReason")
.isAnnotationPresent(NotBlank.class)).isTrue();
}
}

View File

@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.trade.dal.dataobject;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class DeliveryTenantContractTest {
@Test
void activatedDeliveryRecordsParticipateInFrameworkTenantIsolation() {
assertThat(TenantBaseDO.class).isAssignableFrom(DeliveryExpressDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(DeliveryExpressTemplateDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(DeliveryExpressTemplateChargeDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(DeliveryExpressTemplateFreeDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(DeliveryPickUpStoreDO.class);
}
}

View File

@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.trade.dal.dataobject;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
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.TradeOrderLogDO;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class TradeTenantContractTest {
@Test
void activatedTradeRecordsParticipateInFrameworkTenantIsolation() {
assertThat(TenantBaseDO.class).isAssignableFrom(TradeConfigDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(CartDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(TradeOrderDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(TradeOrderItemDO.class);
assertThat(TenantBaseDO.class).isAssignableFrom(TradeOrderLogDO.class);
}
}