Commit c880dbfa609c6244721db844168882322f5eba5b

Authored by jason
2 parents dbd7ab5f 35569c5e

Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

Showing 112 changed files with 2008 additions and 967 deletions
sql/mysql/brokerage.sql
... ... @@ -2,41 +2,50 @@
2 2 create table trade_config
3 3 (
4 4 id bigint auto_increment comment '自增主键' primary key,
5   - brokerage_enabled bit default 1 not null comment '是å¦å¯ç”¨åˆ†ä½£',
6   - brokerage_enabled_condition tinyint default 0 not null comment '分佣模å¼ï¼š1-人人分销 2-指定分销',
7   - brokerage_bind_mode tinyint default 0 not null comment '分销关系绑定模å¼: 1-没有推广人,2-新用户, 3-扫ç è¦†ç›–',
8   - brokerage_post_urls varchar(2000) default '' null comment 'åˆ†é”€æµ·æŠ¥å›¾åœ°å€æ•°ç»„',
9   - brokerage_first_percent int default 0 not null comment '一级返佣比例',
10   - brokerage_second_percent int default 0 not null comment '二级返佣比例',
11   - brokerage_withdraw_min_price int default 0 not null comment '用户æçŽ°æœ€ä½Žé‡‘é¢',
12   - brokerage_bank_names varchar(200) default '' not null comment 'æçŽ°é“¶è¡Œï¼ˆå­—å…¸ç±»åž‹=brokerage_bank_name)',
13   - brokerage_frozen_days int default 7 not null comment '佣金冻结时间(天)',
14   - brokerage_withdraw_type varchar(32) default '1,2,3,4' not null comment 'æçŽ°æ–¹å¼ï¼š1-钱包;2-银行å¡ï¼›3-微信;4-支付å®',
15   - creator varchar(64) collate utf8mb4_unicode_ci default '' null comment '创建者',
16   - create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
17   - updater varchar(64) collate utf8mb4_unicode_ci default '' null comment '更新者',
18   - update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment 'æ›´æ–°æ—¶é—´',
19   - deleted bit default b'0' not null comment '是å¦åˆ é™¤',
20   - tenant_id bigint default 0 not null comment '租户编å·'
  5 + brokerage_enabled bit default 1 not null comment '是å¦å¯ç”¨åˆ†ä½£',
  6 + brokerage_enabled_condition tinyint default 0 not null comment '分佣模å¼ï¼š1-人人分销 2-指定分销',
  7 + brokerage_bind_mode tinyint default 0 not null comment '分销关系绑定模å¼: 1-没有推广人,2-新用户, 3-扫ç è¦†ç›–',
  8 + brokerage_post_urls varchar(2000) default '' null comment 'åˆ†é”€æµ·æŠ¥å›¾åœ°å€æ•°ç»„',
  9 + brokerage_first_percent int default 0 not null comment '一级返佣比例',
  10 + brokerage_second_percent int default 0 not null comment '二级返佣比例',
  11 + brokerage_withdraw_min_price int default 0 not null comment '用户æçŽ°æœ€ä½Žé‡‘é¢',
  12 + brokerage_bank_names varchar(200) default '' not null comment 'æçŽ°é“¶è¡Œï¼ˆå­—å…¸ç±»åž‹=brokerage_bank_name)',
  13 + brokerage_frozen_days int default 7 not null comment '佣金冻结时间(天)',
  14 + brokerage_withdraw_type varchar(32) default '1,2,3,4' not null comment 'æçŽ°æ–¹å¼ï¼š1-钱包;2-银行å¡ï¼›3-微信;4-支付å®',
  15 + creator varchar(64) default '' null comment '创建者',
  16 + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
  17 + updater varchar(64) default '' null comment '更新者',
  18 + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment 'æ›´æ–°æ—¶é—´',
  19 + deleted bit default b'0' not null comment '是å¦åˆ é™¤',
  20 + tenant_id bigint default 0 not null comment '租户编å·'
21 21 ) comment '交易中心é…ç½®';
22 22  
  23 +# alter table trade_brokerage_user
  24 +# add level int not null default 1 comment '等级' after frozen_price;
  25 +# alter table trade_brokerage_user
  26 +# add path varchar(2000) null comment '路径' after level;
  27 +
  28 +
23 29 -- 增加分销用户扩展表
24 30 create table trade_brokerage_user
25 31 (
26 32 id bigint auto_increment comment '用户编å·' primary key,
27   - bind_user_id bigint null comment '推广员编å·',
28   - bind_user_time datetime null comment '推广员绑定时间',
29   - brokerage_enabled bit default 1 not null comment 'æ˜¯å¦æˆä¸ºæŽ¨å¹¿å‘˜',
30   - brokerage_time datetime null comment 'æˆä¸ºåˆ†é”€å‘˜æ—¶é—´',
31   - price int default 0 not null comment 'å¯ç”¨ä½£é‡‘',
32   - frozen_price int default 0 not null comment '冻结佣金',
33   - creator varchar(64) collate utf8mb4_unicode_ci default '' null comment '创建者',
34   - create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
35   - updater varchar(64) collate utf8mb4_unicode_ci default '' null comment '更新者',
36   - update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment 'æ›´æ–°æ—¶é—´',
37   - deleted bit default b'0' not null comment '是å¦åˆ é™¤',
38   - tenant_id bigint default 0 not null comment '租户编å·'
39   -) comment '分销用户';
  33 + bind_user_id bigint null comment '推广员编å·',
  34 + bind_user_time datetime null comment '推广员绑定时间',
  35 + brokerage_enabled bit default 1 not null comment 'æ˜¯å¦æˆä¸ºæŽ¨å¹¿å‘˜',
  36 + brokerage_time datetime null comment 'æˆä¸ºåˆ†é”€å‘˜æ—¶é—´',
  37 + price int default 0 not null comment 'å¯ç”¨ä½£é‡‘',
  38 + frozen_price int default 0 not null comment '冻结佣金',
  39 + level int default 1 not null comment '等级',
  40 + path varchar(2000) null comment '路径',
  41 + creator varchar(64) default '' null comment '创建者',
  42 + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
  43 + updater varchar(64) default '' null comment '更新者',
  44 + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment 'æ›´æ–°æ—¶é—´',
  45 + deleted bit default b'0' not null comment '是å¦åˆ é™¤',
  46 + tenant_id bigint default 0 not null comment '租户编å·'
  47 +)
  48 + comment '分销用户';
40 49  
41 50 create index idx_invite_user_id on trade_brokerage_user (bind_user_id) comment '推广员编å·';
42 51 create index idx_agent on trade_brokerage_user (brokerage_enabled) comment 'æ˜¯å¦æˆä¸ºæŽ¨å¹¿å‘˜';
... ... @@ -44,26 +53,26 @@ create index idx_agent on trade_brokerage_user (brokerage_enabled) comment '是å
44 53  
45 54 create table trade_brokerage_record
46 55 (
47   - id int auto_increment comment 'ç¼–å·'
  56 + id int auto_increment comment 'ç¼–å·'
48 57 primary key,
49   - user_id bigint not null comment '用户编å·',
50   - biz_id varchar(64) default '' not null comment '业务编å·',
51   - biz_type tinyint default 0 not null comment '业务类型:1-订å•,2-æçް',
52   - title varchar(64) default '' not null comment '标题',
53   - price int default 0 not null comment '金é¢',
54   - total_price int default 0 not null comment '当剿€»ä½£é‡‘',
55   - description varchar(500) default '' not null comment '说明',
56   - status tinyint default 0 not null comment '状æ€ï¼š0-待结算,1-已结算,2-已喿¶ˆ',
57   - frozen_days int default 0 not null comment '冻结时间(天)',
58   - unfreeze_time datetime null comment '解冻时间',
59   - source_user_type tinyint not null comment 'æ¥æºç”¨æˆ·ç±»åž‹ï¼š1-一级推广用户,2-二级推广用户',
60   - source_user_id bigint not null comment 'æ¥æºç”¨æˆ·ç¼–å·',
61   - creator varchar(64) collate utf8mb4_general_ci default '' null comment '创建者',
62   - create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
63   - updater varchar(64) collate utf8mb4_general_ci default '' null comment '更新者',
64   - update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment 'æ›´æ–°æ—¶é—´',
65   - deleted bit default b'0' not null comment '是å¦åˆ é™¤',
66   - tenant_id bigint default 0 not null comment '租户编å·'
  58 + user_id bigint not null comment '用户编å·',
  59 + biz_id varchar(64) default '' not null comment '业务编å·',
  60 + biz_type tinyint default 0 not null comment '业务类型:1-订å•,2-æçް',
  61 + title varchar(64) default '' not null comment '标题',
  62 + price int default 0 not null comment '金é¢',
  63 + total_price int default 0 not null comment '当剿€»ä½£é‡‘',
  64 + description varchar(500) default '' not null comment '说明',
  65 + status tinyint default 0 not null comment '状æ€ï¼š0-待结算,1-已结算,2-已喿¶ˆ',
  66 + frozen_days int default 0 not null comment '冻结时间(天)',
  67 + unfreeze_time datetime null comment '解冻时间',
  68 + source_user_level int not null comment 'æ¥æºç”¨æˆ·ç­‰çº§',
  69 + source_user_id bigint not null comment 'æ¥æºç”¨æˆ·ç¼–å·',
  70 + creator varchar(64) default '' null comment '创建者',
  71 + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
  72 + updater varchar(64) default '' null comment '更新者',
  73 + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment 'æ›´æ–°æ—¶é—´',
  74 + deleted bit default b'0' not null comment '是å¦åˆ é™¤',
  75 + tenant_id bigint default 0 not null comment '租户编å·'
67 76 )
68 77 comment '佣金记录';
69 78  
... ... @@ -76,26 +85,26 @@ create table trade_brokerage_withdraw
76 85 (
77 86 id int auto_increment comment 'ç¼–å·'
78 87 primary key,
79   - user_id bigint not null comment '用户编å·',
80   - price int default 0 not null comment 'æçް金é¢',
81   - fee_price int default 0 not null comment 'æçŽ°æ‰‹ç»­è´¹',
82   - total_price int default 0 not null comment '当剿€»ä½£é‡‘',
83   - type tinyint default 0 not null comment 'æçŽ°ç±»åž‹ï¼š1-钱包;2-银行å¡ï¼›3-微信;4-支付å®',
84   - name varchar(64) null comment '真实姓å',
85   - account_no varchar(64) null comment 'è´¦å·',
86   - bank_name varchar(100) null comment '银行åç§°',
87   - bank_address varchar(200) null comment '开户地å€',
88   - account_qr_code_url varchar(512) null comment '收款ç ',
89   - status tinyint(2) default 0 not null comment '状æ€ï¼š0-审核中,10-审核通过 20-审核ä¸é€šè¿‡ï¼›é¢„留:11 - æçްæˆåŠŸï¼›21-æçŽ°å¤±è´¥',
90   - audit_reason varchar(128) null comment '审核驳回原因',
91   - audit_time datetime null comment '审核时间',
92   - remark varchar(500) null comment '备注',
93   - creator varchar(64) collate utf8mb4_general_ci default '' null comment '创建者',
94   - create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
95   - updater varchar(64) collate utf8mb4_general_ci default '' null comment '更新者',
96   - update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment 'æ›´æ–°æ—¶é—´',
97   - deleted bit default b'0' not null comment '是å¦åˆ é™¤',
98   - tenant_id bigint default 0 not null comment '租户编å·'
  88 + user_id bigint not null comment '用户编å·',
  89 + price int default 0 not null comment 'æçް金é¢',
  90 + fee_price int default 0 not null comment 'æçŽ°æ‰‹ç»­è´¹',
  91 + total_price int default 0 not null comment '当剿€»ä½£é‡‘',
  92 + type tinyint default 0 not null comment 'æçŽ°ç±»åž‹ï¼š1-钱包;2-银行å¡ï¼›3-微信;4-支付å®',
  93 + name varchar(64) null comment '真实姓å',
  94 + account_no varchar(64) null comment 'è´¦å·',
  95 + bank_name varchar(100) null comment '银行åç§°',
  96 + bank_address varchar(200) null comment '开户地å€',
  97 + account_qr_code_url varchar(512) null comment '收款ç ',
  98 + status tinyint(2) default 0 not null comment '状æ€ï¼š0-审核中,10-审核通过 20-审核ä¸é€šè¿‡ï¼›é¢„留:11 - æçްæˆåŠŸï¼›21-æçŽ°å¤±è´¥',
  99 + audit_reason varchar(128) null comment '审核驳回原因',
  100 + audit_time datetime null comment '审核时间',
  101 + remark varchar(500) null comment '备注',
  102 + creator varchar(64) default '' null comment '创建者',
  103 + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
  104 + updater varchar(64) default '' null comment '更新者',
  105 + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment 'æ›´æ–°æ—¶é—´',
  106 + deleted bit default b'0' not null comment '是å¦åˆ é™¤',
  107 + tenant_id bigint default 0 not null comment '租户编å·'
99 108 )
100 109 comment '佣金æçް';
101 110  
... ... @@ -139,12 +148,12 @@ values ('brokerage_record_status', '待结算', 0, 0),
139 148  
140 149 insert into system_dict_type(type, name)
141 150 values ('brokerage_withdraw_status', '佣金æçŽ°çŠ¶æ€');
142   -insert into system_dict_data(dict_type, label, value, sort)
143   -values ('brokerage_withdraw_status', '审核中', 0, 0),
144   - ('brokerage_withdraw_status', '审核通过', 10, 10),
145   - ('brokerage_withdraw_status', 'æçްæˆåŠŸ', 11, 11),
146   - ('brokerage_withdraw_status', '审核ä¸é€šè¿‡', 20, 20),
147   - ('brokerage_withdraw_status', 'æçŽ°å¤±è´¥', 21, 21);
  151 +insert into system_dict_data(dict_type, label, value, sort, color_type)
  152 +values ('brokerage_withdraw_status', '审核中', 0, 0, ''),
  153 + ('brokerage_withdraw_status', '审核通过', 10, 10, 'success'),
  154 + ('brokerage_withdraw_status', 'æçްæˆåŠŸ', 11, 11, 'success'),
  155 + ('brokerage_withdraw_status', '审核ä¸é€šè¿‡', 20, 20, 'danger'),
  156 + ('brokerage_withdraw_status', 'æçŽ°å¤±è´¥', 21, 21, 'danger');
148 157  
149 158 insert into system_dict_type(type, name)
150 159 values ('brokerage_bank_name', '佣金æçŽ°é“¶è¡Œ');
... ... @@ -220,4 +229,10 @@ SELECT @parentId := LAST_INSERT_ID();
220 229 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
221 230 VALUES ('佣金æçŽ°æŸ¥è¯¢', 'trade:brokerage-withdraw:query', 3, 1, @parentId, '', '', '', 0);
222 231 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
223   -VALUES ('佣金æçŽ°å®¡æ ¸', 'trade:brokerage-withdraw:audit', 3, 2, @parentId, '', '', '', 0);
224 232 \ No newline at end of file
  233 +VALUES ('佣金æçŽ°å®¡æ ¸', 'trade:brokerage-withdraw:audit', 3, 2, @parentId, '', '', '', 0);
  234 +
  235 +-- 站内信模æ¿
  236 +INSERT INTO `ruoyi-vue-pro`.system_notify_template (name, code, nickname, content, type, params, status)
  237 +VALUES
  238 + ('佣金æçŽ°ï¼ˆå®¡æ ¸é€šè¿‡ï¼‰', 'brokerage_withdraw_audit_approve', 'system', '您在{createTime}æçŽ°ï¿¥{price}元的申请已通过审核', 2, '["createTime","price"]', 0),
  239 + ('佣金æçŽ°ï¼ˆå®¡æ ¸ä¸é€šè¿‡ï¼‰', 'brokerage_withdraw_audit_reject', 'system', '您在{createTime}æçŽ°ï¿¥{price}元的申请未通过审核,原因:{reason}', 2, '["createTime","price","reason"]', 0);
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
... ... @@ -43,6 +43,17 @@ public class LocalDateTimeUtils {
43 43 return LocalDateTime.of(year, mouth, day, 0, 0, 0);
44 44 }
45 45  
  46 + /**
  47 + * 创建指定时间
  48 + *
  49 + * @param timeStr 时间字符串
  50 + * @return 指定时间
  51 + */
  52 + public static LocalDateTime buildTime(String timeStr) {
  53 + // TODO @puhui999:这个方法的实现,和 LocalDateTimeUtil.parse() 的差异点是啥呀
  54 + return LocalDateTime.of(LocalDate.now(), LocalTime.parse(timeStr));
  55 + }
  56 +
46 57 public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,
47 58 int year2, int mouth2, int day2) {
48 59 return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
... ... @@ -63,6 +74,22 @@ public class LocalDateTimeUtils {
63 74 }
64 75  
65 76 /**
  77 + * 判断当前时间是否在该时间范围内
  78 + *
  79 + * @param startTime 开始时间
  80 + * @param endTime 结束时间
  81 + * @return 是否
  82 + */
  83 + public static boolean isBetween(String startTime, String endTime) {
  84 + if (startTime == null || endTime == null) {
  85 + return false;
  86 + }
  87 + LocalDate nowDate = LocalDate.now();
  88 + return LocalDateTimeUtil.isIn(LocalDateTime.now(), LocalDateTime.of(nowDate, LocalTime.parse(startTime)),
  89 + LocalDateTime.of(nowDate, LocalTime.parse(endTime)));
  90 + }
  91 +
  92 + /**
66 93 * 判断时间段是否重叠
67 94 *
68 95 * @param startTime1 开始 time1
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperX.java
... ... @@ -100,6 +100,14 @@ public class LambdaQueryWrapperX<T> extends LambdaQueryWrapper<T> {
100 100 return betweenIfPresent(column, val1, val2);
101 101 }
102 102  
  103 + // TODO @疯狂:这个是 mysql 独有的,不好做成通用的哈。如果多层级,有没可能先查询一个层级,再查询一个层级;形成 set 后,直接去 in?
  104 + public LambdaQueryWrapperX<T> findInSetIfPresent(SFunction<T, ?> column, Object val) {
  105 + if (val != null) {
  106 + return (LambdaQueryWrapperX<T>) super.apply("FIND_IN_SET({0}, " + columnToString(column) + ")", val);
  107 + }
  108 + return this;
  109 + }
  110 +
103 111 // ========== 重写父类方法,方便链式调用 ==========
104 112  
105 113 @Override
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm
... ... @@ -64,14 +64,14 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
64 64  
65 65 @Override
66 66 public ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id) {
67   - if (CollUtil.isEmpty(ids)) {
68   - return ListUtil.empty();
69   - }
70 67 return ${classNameVar}Mapper.selectById(id);
71 68 }
72 69  
73 70 @Override
74 71 public List<${table.className}DO> get${simpleClassName}List(Collection<${primaryColumn.javaType}> ids) {
  72 + if (CollUtil.isEmpty(ids)) {
  73 + return ListUtil.empty();
  74 + }
75 75 return ${classNameVar}Mapper.selectBatchIds(ids);
76 76 }
77 77  
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
... ... @@ -39,7 +39,7 @@ public interface ProductSkuApi {
39 39 List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds);
40 40  
41 41 /**
42   - * 更新 SKU 库存
  42 + * 更新 SKU 库存(增加 or 减少)
43 43 *
44 44 * @param updateStockReqDTO 更新请求
45 45 */
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java
... ... @@ -59,14 +59,13 @@ public class ProductSkuRespDTO {
59 59 * 商品体积,单位:m^3 平米
60 60 */
61 61 private Double volume;
62   - // TODO @puhui999:firstBrokeragePrice ;尴尬,我当时打错了;secondBrokeragePrice
63 62 /**
64 63 * 一级分销的佣金,单位:分
65 64 */
66   - private Integer firstBrokerageRecord;
  65 + private Integer firstBrokeragePrice;
67 66 /**
68 67 * 二级分销的佣金,单位:分
69 68 */
70   - private Integer secondBrokerageRecord;
  69 + private Integer secondBrokeragePrice;
71 70  
72 71 }
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java
1 1 package cn.iocoder.yudao.module.product.api.spu.dto;
2 2  
3   -import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
4 3 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
5 4 import lombok.Data;
6 5  
... ... @@ -32,6 +31,12 @@ public class ProductSpuRespDTO {
32 31 */
33 32 private String keyword;
34 33 /**
  34 + * 单位
  35 + *
  36 + * 对应 product_unit 数据字典
  37 + */
  38 + private Integer unit;
  39 + /**
35 40 * 商品简介
36 41 */
37 42 private String introduction;
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java
... ... @@ -51,10 +51,10 @@ public class ProductSkuBaseVO {
51 51 private Double volume;
52 52  
53 53 @Schema(description = "一级分销的佣金,单位:分", example = "199")
54   - private Integer firstBrokerageRecord;
  54 + private Integer firstBrokeragePrice;
55 55  
56 56 @Schema(description = "二级分销的佣金,单位:分", example = "19")
57   - private Integer secondBrokerageRecord;
  57 + private Integer secondBrokeragePrice;
58 58  
59 59 @Schema(description = "属性数组")
60 60 private List<Property> properties;
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java
... ... @@ -81,11 +81,11 @@ public class ProductSkuDO extends BaseDO {
81 81 /**
82 82 * 一级分销的佣金,单位:分
83 83 */
84   - private Integer firstBrokerageRecord;
  84 + private Integer firstBrokeragePrice;
85 85 /**
86 86 * 二级分销的佣金,单位:分
87 87 */
88   - private Integer secondBrokerageRecord;
  88 + private Integer secondBrokeragePrice;
89 89  
90 90 // ========== 营销相关字段 =========
91 91  
... ...
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java
... ... @@ -92,8 +92,8 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
92 92 o.setMarketPrice(generaInt());
93 93 o.setStock(generaInt());
94 94 o.setWarnStock(10);
95   - o.setFirstBrokerageRecord(generaInt());
96   - o.setSecondBrokerageRecord(generaInt());
  95 + o.setFirstBrokeragePrice(generaInt());
  96 + o.setSecondBrokeragePrice(generaInt());
97 97 // 限制分数为两位数
98 98 o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP));
99 99 o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP));
... ... @@ -143,8 +143,8 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
143 143 o.setMarketPrice(generaInt());
144 144 o.setStock(generaInt());
145 145 o.setWarnStock(10);
146   - o.setFirstBrokerageRecord(generaInt());
147   - o.setSecondBrokerageRecord(generaInt());
  146 + o.setFirstBrokeragePrice(generaInt());
  147 + o.setSecondBrokeragePrice(generaInt());
148 148 // 限制分数为两位数
149 149 o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP));
150 150 o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP));
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationApi.java renamed to yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApi.java
1 1 package cn.iocoder.yudao.module.promotion.api.combination;
2 2  
3   -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationActivityUpdateStockReqDTO;
4   -
5   -// TODO @puhui999:是不是改成 CombinationActivityApi
6 3 /**
7 4 * 拼团活动 Api 接口
8 5 *
9 6 * @author HUIHUI
10 7 */
11   -public interface CombinationApi {
  8 +public interface CombinationActivityApi {
12 9  
13 10 /**
14   - * 更新活动库存
  11 + * 校验是否满足拼团条件
15 12 *
16   - * @param reqDTO 请求
  13 + * @param activityId 活动编号
  14 + * @param userId 用户编号
  15 + * @param skuId sku 编号
  16 + * @param count 数量
17 17 */
18   - // TODO @puhui999:应该是更新哇?还是校验哈;
19   - void validateCombination(CombinationActivityUpdateStockReqDTO reqDTO);
  18 + void validateCombination(Long activityId, Long userId, Long skuId, Integer count);
20 19  
21 20 }
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
1 1 package cn.iocoder.yudao.module.promotion.api.combination;
2 2  
3 3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
4   -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
5 4  
6 5 import javax.validation.Valid;
7 6 import java.time.LocalDateTime;
8   -import java.util.List;
9 7  
10 8 // TODO @芋艿:后面也再撸撸这几个接口
11 9  
... ... @@ -33,26 +31,7 @@ public interface CombinationRecordApi {
33 31 boolean isCombinationRecordSuccess(Long userId, Long orderId);
34 32  
35 33 /**
36   - * 获取拼团记录
37   - *
38   - * @param userId 用户编号
39   - * @param activityId 活动编号
40   - * @return 拼团记录列表
41   - */
42   - List<CombinationRecordRespDTO> getRecordListByUserIdAndActivityId(Long userId, Long activityId);
43   -
44   - /**
45   - * 验证组合限制数
46   - * 校验是否满足限购要求
47   - *
48   - * @param count 本次购买数量
49   - * @param sumCount 已购买数量合计
50   - * @param activityId 活动编号
51   - */
52   - void validateCombinationLimitCount(Long activityId, Integer count, Integer sumCount);
53   -
54   - /**
55   - * 更新拼团状态为成功
  34 + * 更新拼团状态为【成功】
56 35 *
57 36 * @param userId 用户编号
58 37 * @param orderId 订单编号
... ... @@ -60,7 +39,7 @@ public interface CombinationRecordApi {
60 39 void updateRecordStatusToSuccess(Long userId, Long orderId);
61 40  
62 41 /**
63   - * 更新拼团状态为失败
  42 + * 更新拼团状态为【失败】
64 43 *
65 44 * @param userId 用户编号
66 45 * @param orderId 订单编号
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationActivityUpdateStockReqDTO.java deleted
1   -package cn.iocoder.yudao.module.promotion.api.combination.dto;
2   -
3   -import lombok.Data;
4   -
5   -import javax.validation.Valid;
6   -import javax.validation.constraints.NotNull;
7   -
8   -/**
9   - * 拼团活动更新活动库存 Request DTO
10   - *
11   - * @author HUIHUI
12   - */
13   -@Data
14   -public class CombinationActivityUpdateStockReqDTO {
15   -
16   - // TODO @puhui999:是不是一个 activityId、count、skuId 参数就完事啦。
17   -
18   - @NotNull(message = "活动编号不能为空")
19   - private Long activityId;
20   -
21   - @NotNull(message = "购买数量不能为空")
22   - private Integer count;
23   -
24   - @NotNull(message = "活动商品不能为空")
25   - private Item item;
26   -
27   - @Data
28   - @Valid
29   - public static class Item {
30   -
31   - @NotNull(message = "SPU 编号不能为空")
32   - private Long spuId;
33   -
34   - @NotNull(message = "SKU 编号活动商品不能为空")
35   - private Long skuId;
36   -
37   - @NotNull(message = "购买数量不能为空")
38   - private Integer count;
39   -
40   - }
41   -
42   -}
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordCreateReqDTO.java
... ... @@ -13,26 +13,39 @@ import javax.validation.constraints.NotNull;
13 13 @Data
14 14 public class CombinationRecordCreateReqDTO {
15 15  
16   - // TODO @puhui999:注释还是要的哈
17   -
  16 + /**
  17 + * 拼团活动编号
  18 + */
18 19 @NotNull(message = "拼团活动编号不能为空")
19 20 private Long activityId;
20   -
  21 + /**
  22 + * spu 编号
  23 + */
21 24 @NotNull(message = "spu 编号不能为空")
22 25 private Long spuId;
23   -
  26 + /**
  27 + * sku 编号
  28 + */
24 29 @NotNull(message = "sku 编号不能为空")
25 30 private Long skuId;
26   -
  31 + /**
  32 + * 订单编号
  33 + */
27 34 @NotNull(message = "订单编号不能为空")
28 35 private Long orderId;
29   -
  36 + /**
  37 + * 用户编号
  38 + */
30 39 @NotNull(message = "用户编号不能为空")
31 40 private Long userId;
32   -
  41 + /**
  42 + * 团长编号
  43 + */
33 44 @NotNull(message = "团长编号不能为空")
34 45 private Long headId;
35   -
  46 + /**
  47 + * 拼团商品单价
  48 + */
36 49 @NotNull(message = "拼团商品单价不能为空")
37 50 private Integer combinationPrice;
38 51  
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java
1 1 package cn.iocoder.yudao.module.promotion.api.seckill;
2 2  
3   -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
4   -
5 3 /**
6 4 * 秒杀活动 API 接口
7 5 *
... ... @@ -9,11 +7,14 @@ import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateSt
9 7 */
10 8 public interface SeckillActivityApi {
11 9  
  10 + // TODO @puhui999:activityId 改成 id 好点哈;
12 11 /**
13 12 * 更新秒杀库存
14 13 *
15   - * @param updateStockReqDTO 请求
  14 + * @param activityId 活动编号
  15 + * @param skuId sku 编号
  16 + * @param count 数量
16 17 */
17   - void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO);
  18 + void updateSeckillStock(Long activityId, Long skuId, Integer count);
18 19  
19 20 }
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillActivityUpdateStockReqDTO.java deleted
1   -package cn.iocoder.yudao.module.promotion.api.seckill.dto;
2   -
3   -import lombok.Data;
4   -
5   -import javax.validation.Valid;
6   -import javax.validation.constraints.NotNull;
7   -
8   -/**
9   - * 更新秒杀库存 request DTO
10   - *
11   - * @author HUIHUI
12   - */
13   -@Data
14   -public class SeckillActivityUpdateStockReqDTO {
15   -
16   - // TODO @puhui999:可以不用 dto,直接 activityId、skuId、count 即可
17   -
18   - @NotNull(message = "活动编号不能为空")
19   - private Long activityId;
20   -
21   - @NotNull(message = "购买数量不能为空")
22   - private Integer count;
23   -
24   - @NotNull(message = "活动商品不能为空")
25   - private Item item;
26   -
27   - @Data
28   - @Valid
29   - public static class Item {
30   -
31   - @NotNull(message = "SPU 编号不能为空")
32   - private Long spuId;
33   -
34   - @NotNull(message = "SKU 编号活动商品不能为空")
35   - private Long skuId;
36   -
37   - @NotNull(message = "购买数量不能为空")
38   - private Integer count;
39   -
40   - }
41   -
42   -}
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
... ... @@ -56,6 +56,7 @@ public interface ErrorCodeConstants {
56 56 ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013008004, "秒杀活动未关闭或未结束,不能删除");
57 57 ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1013008005, "秒杀活动已关闭,不能重复关闭");
58 58 ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1013008006, "秒杀失败,原因秒杀库存不足");
  59 + ErrorCode SECKILL_ACTIVITY_FAIL_STATUS_CLOSED = new ErrorCode(1013008007, "秒杀活动已关闭");
59 60  
60 61 // ========== 秒杀时段 1013009000 ==========
61 62 ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1013009000, "秒杀时段不存在");
... ...
yudao-module-mall/yudao-module-promotion-biz/pom.xml
... ... @@ -77,6 +77,10 @@
77 77 <groupId>cn.iocoder.boot</groupId>
78 78 <artifactId>yudao-spring-boot-starter-excel</artifactId>
79 79 </dependency>
  80 + <dependency>
  81 + <groupId>cn.iocoder.boot</groupId>
  82 + <artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
  83 + </dependency>
80 84 </dependencies>
81 85  
82 86 </project>
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationApiImpl.java renamed to yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApiImpl.java
1 1 package cn.iocoder.yudao.module.promotion.api.combination;
2 2  
3   -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationActivityUpdateStockReqDTO;
4 3 import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
5 4 import org.springframework.stereotype.Service;
6   -import org.springframework.validation.annotation.Validated;
7 5  
8 6 import javax.annotation.Resource;
9 7  
... ... @@ -13,15 +11,14 @@ import javax.annotation.Resource;
13 11 * @author HUIHUI
14 12 */
15 13 @Service
16   -@Validated
17   -public class CombinationApiImpl implements CombinationApi {
  14 +public class CombinationActivityApiImpl implements CombinationActivityApi {
18 15  
19 16 @Resource
20 17 private CombinationActivityService activityService;
21 18  
22 19 @Override
23   - public void validateCombination(CombinationActivityUpdateStockReqDTO reqDTO) {
24   - activityService.validateCombination(reqDTO);
  20 + public void validateCombination(Long activityId, Long userId, Long skuId, Integer count) {
  21 + activityService.validateCombination(activityId, userId, skuId, count);
25 22 }
26 23  
27 24 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
1 1 package cn.iocoder.yudao.module.promotion.api.combination;
2 2  
3 3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
4   -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
5   -import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
6 4 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
7 5 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
8 6 import org.springframework.stereotype.Service;
9 7  
10 8 import javax.annotation.Resource;
11 9 import java.time.LocalDateTime;
12   -import java.util.List;
13 10  
14 11 /**
15 12 * 拼团活动 API 实现类
... ... @@ -33,16 +30,6 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
33 30 }
34 31  
35 32 @Override
36   - public List<CombinationRecordRespDTO> getRecordListByUserIdAndActivityId(Long userId, Long activityId) {
37   - return CombinationActivityConvert.INSTANCE.convert(recordService.getRecordListByUserIdAndActivityId(userId, activityId));
38   - }
39   -
40   - @Override
41   - public void validateCombinationLimitCount(Long activityId, Integer count, Integer sumCount) {
42   - recordService.validateCombinationLimitCount(activityId, count, sumCount);
43   - }
44   -
45   - @Override
46 33 public void updateRecordStatusToSuccess(Long userId, Long orderId) {
47 34 recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.SUCCESS.getStatus(), userId, orderId);
48 35 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java
1 1 package cn.iocoder.yudao.module.promotion.api.seckill;
2 2  
3   -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
4 3 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
5 4 import org.springframework.stereotype.Service;
6 5  
... ... @@ -18,8 +17,8 @@ public class SeckillActivityApiImpl implements SeckillActivityApi {
18 17 private SeckillActivityService activityService;
19 18  
20 19 @Override
21   - public void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO) {
22   - activityService.updateSeckillStock(updateStockReqDTO);
  20 + public void updateSeckillStock(Long activityId, Long skuId, Integer count) {
  21 + activityService.updateSeckillStock(activityId, skuId, count);
23 22 }
24 23  
25 24 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/BargainActivityController.java
... ... @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.promotion.controller.admin.bargain;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
  7 +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
6 8 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityCreateReqVO;
7 9 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityPageReqVO;
8 10 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityRespVO;
... ... @@ -19,8 +21,10 @@ import org.springframework.web.bind.annotation.*;
19 21  
20 22 import javax.annotation.Resource;
21 23 import javax.validation.Valid;
  24 +import java.util.List;
22 25  
23 26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  27 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
24 28  
25 29 @Tag(name = "管理后台 - 砍价活动")
26 30 @RestController
... ... @@ -30,6 +34,8 @@ public class BargainActivityController {
30 34  
31 35 @Resource
32 36 private BargainActivityService activityService;
  37 + @Resource
  38 + private ProductSpuApi spuApi;
33 39  
34 40 @PostMapping("/create")
35 41 @Operation(summary = "创建砍价活动")
... ... @@ -73,7 +79,10 @@ public class BargainActivityController {
73 79 if (CollUtil.isEmpty(pageResult.getList())) {
74 80 return success(PageResult.empty(pageResult.getTotal()));
75 81 }
76   - return success(BargainActivityConvert.INSTANCE.convertPage(activityService.getBargainActivityPage(pageVO)));
  82 +
  83 + // 拼接数据
  84 + List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(pageResult.getList(), BargainActivityDO::getSpuId));
  85 + return success(BargainActivityConvert.INSTANCE.convertPage(pageResult, spuList));
77 86 }
78 87  
79 88 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/AppBargainActivityController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.bargain;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
  7 +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
  8 +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
7 9 import cn.iocoder.yudao.module.promotion.controller.app.bargain.vo.activity.AppBargainActivityDetailRespVO;
8 10 import cn.iocoder.yudao.module.promotion.controller.app.bargain.vo.activity.AppBargainActivityRespVO;
  11 +import cn.iocoder.yudao.module.promotion.convert.bargain.BargainActivityConvert;
  12 +import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
  13 +import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
9 14 import io.swagger.v3.oas.annotations.Operation;
  15 +import io.swagger.v3.oas.annotations.Parameter;
10 16 import io.swagger.v3.oas.annotations.tags.Tag;
11 17 import org.springframework.validation.annotation.Validated;
12 18 import org.springframework.web.bind.annotation.GetMapping;
... ... @@ -14,97 +20,60 @@ import org.springframework.web.bind.annotation.RequestMapping;
14 20 import org.springframework.web.bind.annotation.RequestParam;
15 21 import org.springframework.web.bind.annotation.RestController;
16 22  
17   -import java.time.Duration;
18   -import java.util.ArrayList;
  23 +import javax.annotation.Resource;
19 24 import java.util.List;
20 25  
21 26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  27 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
22 28  
23 29 @Tag(name = "用户 App - 砍价活动")
24 30 @RestController
25 31 @RequestMapping("/promotion/bargain-activity")
26 32 @Validated
27 33 public class AppBargainActivityController {
  34 + @Resource
  35 + private BargainActivityService bargainActivityService;
  36 + @Resource
  37 + private ProductSpuApi spuApi;
28 38  
29 39 @GetMapping("/page")
30   - @Operation(summary = "获得砍价活动活动") // TODO 芋艿:只查询进行中,且在时间范围内的
31   - // TODO 芋艿:缺少 swagger 注解
  40 + @Operation(summary = "获得砍价活动分页")
32 41 public CommonResult<PageResult<AppBargainActivityRespVO>> getBargainActivityPage(PageParam pageReqVO) {
33   - List<AppBargainActivityRespVO> activityList = new ArrayList<>();
34   - AppBargainActivityRespVO activity1 = new AppBargainActivityRespVO();
35   - activity1.setId(1L);
36   - activity1.setName("618 大砍价");
37   - activity1.setSpuId(2048L);
38   - activity1.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg");
39   - activity1.setMarketPrice(50);
40   - activity1.setBargainPrice(100);
41   - activity1.setStartTime(LocalDateTimeUtils.addTime(Duration.ofDays(-2)));
42   - activity1.setEndTime(LocalDateTimeUtils.addTime(Duration.ofDays(1)));
43   - activity1.setStock(10);
44   - activityList.add(activity1);
45   -
46   - AppBargainActivityRespVO activity2 = new AppBargainActivityRespVO();
47   - activity2.setId(2L);
48   - activity2.setName("双十一砍价");
49   - activity2.setSpuId(4096L);
50   - activity2.setPicUrl("https://static.iocoder.cn/mall/132.jpeg");
51   - activity2.setMarketPrice(100);
52   - activity2.setBargainPrice(200);
53   - activity2.setStartTime(LocalDateTimeUtils.addTime(Duration.ofDays(-2)));
54   - activity2.setEndTime(LocalDateTimeUtils.addTime(Duration.ofDays(1)));
55   - activity2.setStock(0);
56   - activityList.add(activity2);
57   -
58   - return success(new PageResult<>(activityList, 10L));
  42 + PageResult<BargainActivityDO> result = bargainActivityService.getBargainActivityPageForApp(pageReqVO);
  43 + if (CollUtil.isEmpty(result.getList())) {
  44 + return success(PageResult.empty(result.getTotal()));
  45 + }
  46 + // 拼接数据
  47 + List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(result.getList(), BargainActivityDO::getSpuId));
  48 + return success(BargainActivityConvert.INSTANCE.convertAppPage(result, spuList));
59 49 }
60 50  
  51 + // TODO 芋艿:增加 Spring Cache
61 52 @GetMapping("/list")
62 53 @Operation(summary = "获得砍价活动列表", description = "用于小程序首页")
63   - // TODO 芋艿:增加 Spring Cache
64   - // TODO 芋艿:缺少 swagger 注解
  54 + @Parameter(name = "count", description = "需要展示的数量", example = "6")
65 55 public CommonResult<List<AppBargainActivityRespVO>> getBargainActivityList(
66 56 @RequestParam(name = "count", defaultValue = "6") Integer count) {
67   - List<AppBargainActivityRespVO> activityList = new ArrayList<>();
68   - AppBargainActivityRespVO activity1 = new AppBargainActivityRespVO();
69   - activity1.setId(1L);
70   - activity1.setName("618 大砍价");
71   - activity1.setSpuId(2048L);
72   - activity1.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg");
73   - activity1.setMarketPrice(50);
74   - activity1.setBargainPrice(100);
75   - activityList.add(activity1);
76   -
77   - AppBargainActivityRespVO activity2 = new AppBargainActivityRespVO();
78   - activity2.setId(2L);
79   - activity2.setName("双十一砍价");
80   - activity2.setSpuId(4096L);
81   - activity2.setPicUrl("https://static.iocoder.cn/mall/132.jpeg");
82   - activity2.setMarketPrice(100);
83   - activity2.setBargainPrice(200);
84   - activityList.add(activity2);
85   -
86   - return success(activityList);
  57 + List<BargainActivityDO> list = bargainActivityService.getBargainActivityListForApp(count);
  58 + if (CollUtil.isEmpty(list)) {
  59 + return success(BargainActivityConvert.INSTANCE.convertAppList(list));
  60 + }
  61 + // 拼接数据
  62 + List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(list, BargainActivityDO::getSpuId));
  63 + return success(BargainActivityConvert.INSTANCE.convertAppList(list, spuList));
87 64 }
88 65  
89 66 @GetMapping("/get-detail")
90 67 @Operation(summary = "获得砍价活动详情")
91   - // TODO 芋艿:缺少 swagger 注解
  68 + @Parameter(name = "id", description = "活动编号", example = "1")
92 69 public CommonResult<AppBargainActivityDetailRespVO> getBargainActivityDetail(@RequestParam("id") Long id) {
93   - AppBargainActivityDetailRespVO activity = new AppBargainActivityDetailRespVO();
94   - activity.setId(2L);
95   - activity.setName("618 大砍价");
96   - activity.setSpuId(2048L);
97   - activity.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg");
98   - activity.setMarketPrice(50);
99   - activity.setBargainPrice(100);
100   - activity.setStock(10);
101   - activity.setUnitName("件");
102   - activity.setPrice(40);
103   - activity.setStartTime(LocalDateTimeUtils.addTime(Duration.ofDays(-2)));
104   - activity.setEndTime(LocalDateTimeUtils.addTime(Duration.ofDays(-10)));
105   - activity.setDescription("我吃西红柿");
106   - activity.setSuccessCount(10);
107   - return success(activity);
  70 + BargainActivityDO activity = bargainActivityService.getBargainActivity(id);
  71 + if (activity == null) {
  72 + return success(null);
  73 + }
  74 + // 拼接数据
  75 + ProductSpuRespDTO spu = spuApi.getSpu(activity.getSpuId());
  76 + return success(BargainActivityConvert.INSTANCE.convert(activity, spu));
108 77 }
109 78  
110 79 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityDetailRespVO.java
... ... @@ -45,7 +45,7 @@ public class AppBargainActivityDetailRespVO {
45 45 @Schema(description = "商品单位", required = true, example = "个") // 从 SPU 的 unit 读取,然后转换
46 46 private String unitName;
47 47  
48   - @Schema(description = "砍价最低金额,单位:分", required = true, example = "100") // 从砍价商品里取最低价
  48 + @Schema(description = "砍价最低金额,单位:分", required = true, example = "100")
49 49 private Integer bargainPrice;
50 50  
51 51 @Schema(description = "砍价成功数量", required = true, example = "100")
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/vo/activity/AppBargainActivityRespVO.java
... ... @@ -36,7 +36,7 @@ public class AppBargainActivityRespVO {
36 36 @Schema(description = "商品市场价,单位:分", required = true, example = "50") // 从 SPU 的 marketPrice 读取
37 37 private Integer marketPrice;
38 38  
39   - @Schema(description = "砍价最低金额,单位:分", required = true, example = "100") // 从砍价商品里取最低价
  39 + @Schema(description = "砍价最低金额,单位:分", required = true, example = "100")
40 40 private Integer bargainPrice;
41 41  
42 42 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillActivityController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.seckill;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
  8 +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
5 9 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityDetailRespVO;
6 10 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityNowRespVO;
7 11 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
8 12 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityRespVO;
9   -import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.config.AppSeckillConfigRespVO;
  13 +import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
  14 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
  15 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
  16 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig.SeckillConfigDO;
  17 +import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
  18 +import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigService;
10 19 import io.swagger.v3.oas.annotations.Operation;
11 20 import io.swagger.v3.oas.annotations.Parameter;
12 21 import io.swagger.v3.oas.annotations.tags.Tag;
  22 +import org.springframework.context.annotation.Lazy;
13 23 import org.springframework.validation.annotation.Validated;
14 24 import org.springframework.web.bind.annotation.GetMapping;
15 25 import org.springframework.web.bind.annotation.RequestMapping;
16 26 import org.springframework.web.bind.annotation.RequestParam;
17 27 import org.springframework.web.bind.annotation.RestController;
18 28  
19   -import java.time.LocalDateTime;
20   -import java.util.ArrayList;
  29 +import javax.annotation.Resource;
  30 +import java.util.Arrays;
21 31 import java.util.List;
22 32  
  33 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
23 34 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  36 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.isBetween;
  37 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_ACTIVITY_FAIL_STATUS_CLOSED;
24 38  
25 39 @Tag(name = "用户 App - 秒杀活动")
26 40 @RestController
27 41 @RequestMapping("/promotion/seckill-activity")
28 42 @Validated
29 43 public class AppSeckillActivityController {
  44 + @Resource
  45 + private SeckillActivityService activityService;
  46 + @Resource
  47 + @Lazy
  48 + private SeckillConfigService configService;
  49 +
  50 + @Resource
  51 + private ProductSpuApi spuApi;
30 52  
31 53 @GetMapping("/get-now")
32 54 @Operation(summary = "获得当前秒杀活动") // 提供给首页使用
33 55 // TODO 芋艿:需要增加 spring cache
34 56 public CommonResult<AppSeckillActivityNowRespVO> getNowSeckillActivity() {
35   - AppSeckillActivityNowRespVO respVO = new AppSeckillActivityNowRespVO();
36   - respVO.setConfig(new AppSeckillConfigRespVO().setId(10L).setStartTime("01:00").setEndTime("09:59"));
37   - List<AppSeckillActivityRespVO> activityList = new ArrayList<>();
38   - AppSeckillActivityRespVO activity1 = new AppSeckillActivityRespVO();
39   - activity1.setId(1L);
40   - activity1.setName("618 大秒杀");
41   - activity1.setSpuId(2048L);
42   - activity1.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg");
43   - activity1.setMarketPrice(50);
44   - activity1.setSeckillPrice(100);
45   - activityList.add(activity1);
  57 + // 1. 获取当前时间处在哪个秒杀阶段
  58 + // TODO @puhui999:可以考虑在 service 写个方法;这样 controller 不用关注过多逻辑
  59 + List<SeckillConfigDO> configList = configService.getSeckillConfigList();
  60 + SeckillConfigDO filteredConfig = findFirst(configList, config -> ObjectUtil.equal(config.getStatus(),
  61 + CommonStatusEnum.ENABLE.getStatus()) && isBetween(config.getStartTime(), config.getEndTime()));
  62 + if (filteredConfig == null) { // 时段不存在直接返回 null
  63 + return success(null);
  64 + }
  65 +
  66 + // 2. 查询满足当前阶段的活动
  67 + // TODO @puhui999:最好直接返回开启的;不多查询数据
  68 + List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByConfigIds(Arrays.asList(filteredConfig.getId()));
  69 + List<SeckillActivityDO> filteredList = filterList(activityList, item -> ObjectUtil.equal(item.getStatus(), CommonStatusEnum.ENABLE.getStatus()));
46 70  
47   - AppSeckillActivityRespVO activity2 = new AppSeckillActivityRespVO();
48   - activity2.setId(2L);
49   - activity2.setName("双十一大秒杀");
50   - activity2.setSpuId(4096L);
51   - activity2.setPicUrl("https://static.iocoder.cn/mall/132.jpeg");
52   - activity2.setMarketPrice(100);
53   - activity2.setSeckillPrice(200);
54   - activityList.add(activity2);
55   - respVO.setActivities(activityList);
56   - return success(respVO);
  71 + // 3. 拼接数据
  72 + List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(filteredList, SeckillActivityDO::getSpuId));
  73 + return success(SeckillActivityConvert.INSTANCE.convert(filteredConfig, filteredList, spuList));
57 74 }
58 75  
59 76 @GetMapping("/page")
60 77 @Operation(summary = "获得秒杀活动分页")
61   - // TODO @芋艿:分页参数
62 78 public CommonResult<PageResult<AppSeckillActivityRespVO>> getSeckillActivityPage(AppSeckillActivityPageReqVO pageReqVO) {
63   - List<AppSeckillActivityRespVO> activityList = new ArrayList<>();
64   - AppSeckillActivityRespVO activity1 = new AppSeckillActivityRespVO();
65   - activity1.setId(1L);
66   - activity1.setName("618 大秒杀");
67   - activity1.setSpuId(2048L);
68   - activity1.setPicUrl("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg");
69   - activity1.setMarketPrice(50);
70   - activity1.setSeckillPrice(100);
71   - activity1.setUnitName("个");
72   - activity1.setStock(1);
73   - activity1.setTotalStock(2);
74   - activityList.add(activity1);
  79 + // 1. 查询满足当前阶段的活动
  80 + PageResult<SeckillActivityDO> pageResult = activityService.getSeckillActivityAppPageByConfigId(pageReqVO);
75 81  
76   - AppSeckillActivityRespVO activity2 = new AppSeckillActivityRespVO();
77   - activity2.setId(2L);
78   - activity2.setName("双十一大秒杀");
79   - activity2.setSpuId(4096L);
80   - activity2.setPicUrl("https://static.iocoder.cn/mall/132.jpeg");
81   - activity2.setMarketPrice(100);
82   - activity2.setSeckillPrice(200);
83   - activity2.setUnitName("套");
84   - activity2.setStock(2);
85   - activity2.setTotalStock(3);
86   - activityList.add(activity2);
87   - return success(new PageResult<>(activityList, 100L));
  82 + // 2. 拼接数据
  83 + List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(pageResult.getList(), SeckillActivityDO::getSpuId));
  84 + return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, spuList));
88 85 }
89 86  
90 87 @GetMapping("/get-detail")
91 88 @Operation(summary = "获得秒杀活动明细")
92 89 @Parameter(name = "id", description = "活动编号", required = true, example = "1024")
93 90 public CommonResult<AppSeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
  91 + // 1、获取当前时间处在哪个秒杀阶段
  92 + // TODO puhui999:这里,和 58 行是雷同的
  93 + List<SeckillConfigDO> configList = configService.getSeckillConfigList();
  94 + SeckillConfigDO filteredConfig = findFirst(configList, config -> ObjectUtil.equal(config.getStatus(),
  95 + CommonStatusEnum.ENABLE.getStatus()) && isBetween(config.getStartTime(), config.getEndTime()));
  96 + if (filteredConfig == null) { // 时段不存在直接返回 null
  97 + return success(null);
  98 + }
  99 +
  100 + // 2. 获取活动
  101 + SeckillActivityDO seckillActivity = activityService.getSeckillActivity(id);
  102 + if (seckillActivity == null) {
  103 + return success(null);
  104 + }
94 105 // TODO 芋艿:如果禁用的时候,需要抛出异常;
95   - AppSeckillActivityDetailRespVO obj = new AppSeckillActivityDetailRespVO();
96   - // 设置其属性的值
97   - obj.setId(id);
98   - obj.setName("晚九点限时秒杀");
99   - obj.setStatus(1);
100   - obj.setStartTime(LocalDateTime.of(2023, 6, 16, 0, 0, 0));
101   - obj.setEndTime(LocalDateTime.of(2023, 6, 20, 23, 59, 0));
102   - obj.setSpuId(633L);
103   - obj.setSingleLimitCount(2);
104   - obj.setTotalLimitCount(3);
105   - obj.setStock(100);
106   - obj.setTotalStock(200);
  106 + if (ObjectUtil.equal(seckillActivity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
  107 + throw exception(SECKILL_ACTIVITY_FAIL_STATUS_CLOSED);
  108 + }
107 109  
108   - // 创建一个Product对象的列表
109   - List<AppSeckillActivityDetailRespVO.Product> productList = new ArrayList<>();
110   - // 创建三个新的Product对象并设置其属性的值
111   - AppSeckillActivityDetailRespVO.Product product1 = new AppSeckillActivityDetailRespVO.Product();
112   - product1.setSkuId(1L);
113   - product1.setSeckillPrice(100);
114   - product1.setStock(50);
115   - // 将第一个Product对象添加到列表中
116   - productList.add(product1);
117   - // 创建第二个Product对象并设置其属性的值
118   - AppSeckillActivityDetailRespVO.Product product2 = new AppSeckillActivityDetailRespVO.Product();
119   - product2.setSkuId(2L);
120   - product2.setSeckillPrice(200);
121   - product2.setStock(100);
122   - // 将第二个Product对象添加到列表中
123   - productList.add(product2);
124   - // 创建第三个Product对象并设置其属性的值
125   - AppSeckillActivityDetailRespVO.Product product3 = new AppSeckillActivityDetailRespVO.Product();
126   - product3.setSkuId(3L);
127   - product3.setSeckillPrice(300);
128   - product3.setStock(150);
129   - // 将第三个Product对象添加到列表中
130   - productList.add(product3);
131   - // 将Product列表设置为对象的属性值
132   - obj.setProducts(productList);
133   - return success(obj);
  110 + // 3. 拼接数据
  111 + List<SeckillProductDO> products = activityService.getSeckillProductListByActivityId(seckillActivity.getId());
  112 + return success(SeckillActivityConvert.INSTANCE.convert3(seckillActivity, products, filteredConfig));
134 113 }
135 114  
136 115 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillConfigController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.seckill;
2 2  
  3 +import cn.hutool.core.collection.CollectionUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 6 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.config.AppSeckillConfigRespVO;
  7 +import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert;
  8 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig.SeckillConfigDO;
  9 +import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigService;
5 10 import io.swagger.v3.oas.annotations.Operation;
6 11 import io.swagger.v3.oas.annotations.tags.Tag;
7 12 import org.springframework.validation.annotation.Validated;
... ... @@ -9,7 +14,8 @@ import org.springframework.web.bind.annotation.GetMapping;
9 14 import org.springframework.web.bind.annotation.RequestMapping;
10 15 import org.springframework.web.bind.annotation.RestController;
11 16  
12   -import java.util.Arrays;
  17 +import javax.annotation.Resource;
  18 +import java.util.Collections;
13 19 import java.util.List;
14 20  
15 21 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -19,18 +25,19 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
19 25 @RequestMapping("/promotion/seckill-config")
20 26 @Validated
21 27 public class AppSeckillConfigController {
  28 + @Resource
  29 + private SeckillConfigService configService;
22 30  
23 31 @GetMapping("/list")
24 32 @Operation(summary = "获得秒杀时间段列表")
25 33 public CommonResult<List<AppSeckillConfigRespVO>> getSeckillConfigList() {
26   - return success(Arrays.asList(
27   - new AppSeckillConfigRespVO().setId(1L).setStartTime("00:00").setEndTime("09:59")
28   - .setSliderPicUrls(Arrays.asList("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg",
29   - "https://static.iocoder.cn/mall/132.jpeg")),
30   - new AppSeckillConfigRespVO().setId(2L).setStartTime("10:00").setEndTime("12:59"),
31   - new AppSeckillConfigRespVO().setId(2L).setStartTime("13:00").setEndTime("22:59"),
32   - new AppSeckillConfigRespVO().setId(2L).setStartTime("23:00").setEndTime("23:59")
33   - ));
  34 + List<SeckillConfigDO> list = configService.getSeckillConfigListByStatus(CommonStatusEnum.ENABLE.getStatus());
  35 + // TODO @puhui999:如果这种,不用判空也问题不大;
  36 + if (CollectionUtil.isEmpty(list)) {
  37 + return success(Collections.emptyList());
  38 + }
  39 +
  40 + return success(SeckillConfigConvert.INSTANCE.convertList2(list));
34 41 }
35 42  
36 43 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/vo/activity/AppSeckillActivityDetailRespVO.java
... ... @@ -19,7 +19,7 @@ public class AppSeckillActivityDetailRespVO {
19 19 @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
20 20 private Integer status;
21 21  
22   - // TODO @芋艿:开始时间、结束时间,要和场次结合起来;就是要算到当前场次,是几点哈;
  22 + // TODO @芋艿:开始时间、结束时间,要和场次结合起来;就是要算到当前场次,是几点哈;
23 23  
24 24 @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
25 25 private LocalDateTime startTime;
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/vo/activity/AppSeckillActivityRespVO.java
... ... @@ -31,7 +31,6 @@ public class AppSeckillActivityRespVO {
31 31 private Integer totalStock;
32 32  
33 33 @Schema(description = "秒杀金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
34   - // 从秒杀商品里取最低价
35 34 private Integer seckillPrice;
36 35  
37 36 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/bargain/BargainActivityConvert.java
1 1 package cn.iocoder.yudao.module.promotion.convert.bargain;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  5 +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
  6 +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
  7 +import cn.iocoder.yudao.module.product.enums.DictTypeConstants;
4 8 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityBaseVO;
5 9 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityRespVO;
6 10 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityUpdateReqVO;
  11 +import cn.iocoder.yudao.module.promotion.controller.app.bargain.vo.activity.AppBargainActivityDetailRespVO;
  12 +import cn.iocoder.yudao.module.promotion.controller.app.bargain.vo.activity.AppBargainActivityRespVO;
7 13 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
8 14 import org.mapstruct.Mapper;
9 15 import org.mapstruct.factory.Mappers;
10 16  
11 17 import java.util.List;
  18 +import java.util.Map;
  19 +
  20 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  21 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
12 22  
13 23 /**
14 24 * 拼团活动 Convert
... ... @@ -30,4 +40,65 @@ public interface BargainActivityConvert {
30 40  
31 41 PageResult<BargainActivityRespVO> convertPage(PageResult<BargainActivityDO> page);
32 42  
  43 + default PageResult<BargainActivityRespVO> convertPage(PageResult<BargainActivityDO> page, List<ProductSpuRespDTO> spuList) {
  44 + PageResult<BargainActivityRespVO> result = convertPage(page);
  45 + // 拼接关联属性
  46 + Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  47 + List<BargainActivityRespVO> list = CollectionUtils.convertList(result.getList(), item -> {
  48 + findAndThen(spuMap, item.getSpuId(), spu -> {
  49 + // TODO @puhui999:这里可以使用链式哈
  50 + item.setPicUrl(spu.getPicUrl());
  51 + item.setSpuName(spu.getName());
  52 + });
  53 + return item;
  54 + });
  55 + result.setList(list);
  56 + return result;
  57 + }
  58 +
  59 + AppBargainActivityDetailRespVO convert1(BargainActivityDO bean);
  60 +
  61 + default AppBargainActivityDetailRespVO convert(BargainActivityDO bean, ProductSpuRespDTO spu) {
  62 + AppBargainActivityDetailRespVO detail = convert1(bean);
  63 + if (spu != null) {
  64 + detail.setPicUrl(spu.getPicUrl());
  65 + detail.setMarketPrice(spu.getMarketPrice());
  66 + detail.setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit()));
  67 + }
  68 + return detail;
  69 + }
  70 +
  71 + PageResult<AppBargainActivityRespVO> convertAppPage(PageResult<BargainActivityDO> page);
  72 +
  73 + default PageResult<AppBargainActivityRespVO> convertAppPage(PageResult<BargainActivityDO> page, List<ProductSpuRespDTO> spuList) {
  74 + PageResult<AppBargainActivityRespVO> result = convertAppPage(page);
  75 + // 拼接关联属性
  76 + Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  77 + List<AppBargainActivityRespVO> list = CollectionUtils.convertList(result.getList(), item -> {
  78 + findAndThen(spuMap, item.getSpuId(), spu -> {
  79 + // TODO @puhui999:这里可以使用链式哈
  80 + item.setPicUrl(spu.getPicUrl());
  81 + item.setMarketPrice(spu.getMarketPrice());
  82 + });
  83 + return item;
  84 + });
  85 + result.setList(list);
  86 + return result;
  87 + }
  88 +
  89 + List<AppBargainActivityRespVO> convertAppList(List<BargainActivityDO> list);
  90 +
  91 + default List<AppBargainActivityRespVO> convertAppList(List<BargainActivityDO> list, List<ProductSpuRespDTO> spuList) {
  92 + List<AppBargainActivityRespVO> activityList = convertAppList(list);
  93 + Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  94 + return CollectionUtils.convertList(activityList, item -> {
  95 + findAndThen(spuMap, item.getSpuId(), spu -> {
  96 + // TODO @puhui999:这里可以使用链式哈
  97 + item.setPicUrl(spu.getPicUrl());
  98 + item.setMarketPrice(spu.getMarketPrice());
  99 + });
  100 + return item;
  101 + });
  102 + }
  103 +
33 104 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java
... ... @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.promotion.convert.combination;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 5 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  6 +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
  7 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
6 8 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
7 9 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
8 10 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
... ... @@ -92,6 +94,21 @@ public interface CombinationActivityConvert {
92 94  
93 95 CombinationRecordDO convert(CombinationRecordCreateReqDTO reqDTO);
94 96  
  97 + default CombinationRecordDO convert(CombinationRecordCreateReqDTO reqDTO,
  98 + CombinationActivityDO activity, MemberUserRespDTO user,
  99 + ProductSpuRespDTO spu, ProductSkuRespDTO sku) {
  100 + // TODO @puhui999:搞成链式的 set;这样会更规整一点;
  101 + CombinationRecordDO record = convert(reqDTO);
  102 + record.setVirtualGroup(false);
  103 + record.setExpireTime(record.getStartTime().plusHours(activity.getLimitDuration()));
  104 + record.setUserSize(activity.getUserSize());
  105 + record.setNickname(user.getNickname());
  106 + record.setAvatar(user.getAvatar());
  107 + record.setSpuName(spu.getName());
  108 + record.setPicUrl(sku.getPicUrl());
  109 + return record;
  110 + }
  111 +
95 112 List<CombinationRecordRespDTO> convert(List<CombinationRecordDO> bean);
96 113  
97 114 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/seckillactivity/SeckillActivityConvert.java
... ... @@ -3,15 +3,22 @@ package cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 5 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  6 +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
6 7 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
  8 +import cn.iocoder.yudao.module.product.enums.DictTypeConstants;
7 9 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
8 10 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityDetailRespVO;
9 11 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityRespVO;
10 12 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
11 13 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
12 14 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductRespVO;
  15 +import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityDetailRespVO;
  16 +import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityNowRespVO;
  17 +import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityRespVO;
  18 +import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert;
13 19 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
14 20 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
  21 +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig.SeckillConfigDO;
15 22 import org.mapstruct.Mapper;
16 23 import org.mapstruct.Mapping;
17 24 import org.mapstruct.Mappings;
... ... @@ -20,6 +27,10 @@ import org.mapstruct.factory.Mappers;
20 27 import java.util.List;
21 28 import java.util.Map;
22 29  
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
  32 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
  33 +
23 34 /**
24 35 * 秒杀活动 Convert
25 36 *
... ... @@ -79,4 +90,53 @@ public interface SeckillActivityConvert {
79 90  
80 91 List<SeckillProductRespVO> convertList2(List<SeckillProductDO> list);
81 92  
  93 + List<AppSeckillActivityRespVO> convertList3(List<SeckillActivityDO> activityList);
  94 +
  95 + default AppSeckillActivityNowRespVO convert(SeckillConfigDO filteredConfig, List<SeckillActivityDO> activityList, List<ProductSpuRespDTO> spuList) {
  96 + AppSeckillActivityNowRespVO respVO = new AppSeckillActivityNowRespVO();
  97 + respVO.setConfig(SeckillConfigConvert.INSTANCE.convert1(filteredConfig));
  98 + Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  99 + respVO.setActivities(CollectionUtils.convertList(convertList3(activityList), item -> {
  100 + findAndThen(spuMap, item.getSpuId(), spu -> {
  101 + // TODO @puhui999:可以尝试链式 set 哈;
  102 + item.setPicUrl(spu.getPicUrl());
  103 + item.setMarketPrice(spu.getMarketPrice());
  104 + item.setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit()));
  105 + });
  106 + return item;
  107 + }));
  108 + return respVO;
  109 + }
  110 +
  111 + PageResult<AppSeckillActivityRespVO> convertPage1(PageResult<SeckillActivityDO> pageResult);
  112 +
  113 + default PageResult<AppSeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> pageResult, List<ProductSpuRespDTO> spuList) {
  114 + PageResult<AppSeckillActivityRespVO> result = convertPage1(pageResult);
  115 + Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  116 + List<AppSeckillActivityRespVO> list = CollectionUtils.convertList(result.getList(), item -> {
  117 + findAndThen(spuMap, item.getSpuId(), spu -> {
  118 + // TODO @puhui999:可以尝试链式 set 哈;
  119 + item.setPicUrl(spu.getPicUrl());
  120 + item.setMarketPrice(spu.getMarketPrice());
  121 + item.setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit()));
  122 + });
  123 + return item;
  124 + });
  125 + result.setList(list);
  126 + return result;
  127 + }
  128 +
  129 + AppSeckillActivityDetailRespVO convert2(SeckillActivityDO seckillActivity);
  130 +
  131 + List<AppSeckillActivityDetailRespVO.Product> convertList1(List<SeckillProductDO> products);
  132 +
  133 + default AppSeckillActivityDetailRespVO convert3(SeckillActivityDO seckillActivity, List<SeckillProductDO> products, SeckillConfigDO filteredConfig) {
  134 + AppSeckillActivityDetailRespVO respVO = convert2(seckillActivity);
  135 + respVO.setProducts(convertList1(products));
  136 + // TODO @puhui999:可以尝试链式 set 哈;
  137 + respVO.setStartTime(buildTime(filteredConfig.getStartTime()));
  138 + respVO.setEndTime(buildTime(filteredConfig.getEndTime()));
  139 + return respVO;
  140 + }
  141 +
82 142 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/seckillconfig/SeckillConfigConvert.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.Seck
5 5 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigRespVO;
6 6 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigSimpleRespVO;
7 7 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigUpdateReqVO;
  8 +import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.config.AppSeckillConfigRespVO;
8 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig.SeckillConfigDO;
9 10 import org.mapstruct.Mapper;
10 11 import org.mapstruct.factory.Mappers;
... ... @@ -33,4 +34,7 @@ public interface SeckillConfigConvert {
33 34  
34 35 PageResult<SeckillConfigRespVO> convertPage(PageResult<SeckillConfigDO> page);
35 36  
  37 + List<AppSeckillConfigRespVO> convertList2(List<SeckillConfigDO> list);
  38 +
  39 + AppSeckillConfigRespVO convert1(SeckillConfigDO filteredConfig);
36 40 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/seckill/seckillactivity/SeckillActivityDO.java
... ... @@ -87,7 +87,7 @@ public class SeckillActivityDO extends BaseDO {
87 87 */
88 88 private Integer singleLimitCount;
89 89 /**
90   - * 秒杀库存
  90 + * 秒杀库存(剩余库存秒杀时扣减)
91 91 */
92 92 private Integer stock;
93 93 /**
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java
1 1 package cn.iocoder.yudao.module.promotion.dal.mysql.bargain;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
... ... @@ -8,6 +9,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityD
8 9 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
9 10 import org.apache.ibatis.annotations.Mapper;
10 11  
  12 +import java.time.LocalDateTime;
11 13 import java.util.List;
12 14  
13 15 /**
... ... @@ -39,8 +41,15 @@ public interface BargainActivityMapper extends BaseMapperX&lt;BargainActivityDO&gt; {
39 41 default int updateActivityStock(Long id, int count) {
40 42 return update(null, new LambdaUpdateWrapper<BargainActivityDO>()
41 43 .eq(BargainActivityDO::getId, id)
42   - .gt(BargainActivityDO::getStock, 0) // TODO @puhui999:不是 > 0,是要大于 count 哈
  44 + .ge(BargainActivityDO::getStock, count)
43 45 .setSql("stock = stock - " + count));
44 46 }
45 47  
  48 + default PageResult<BargainActivityDO> selectAppPage(PageParam pageReqVO, Integer status, LocalDateTime now) {
  49 + return selectPage(pageReqVO, new LambdaQueryWrapperX<BargainActivityDO>()
  50 + .eq(BargainActivityDO::getStatus, status)
  51 + .le(BargainActivityDO::getStartTime, now)
  52 + .ge(BargainActivityDO::getEndTime, now));
  53 + }
  54 +
46 55 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 7 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
  8 +import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
8 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
9 10 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
10 11 import org.apache.ibatis.annotations.Mapper;
... ... @@ -24,7 +25,7 @@ public interface SeckillActivityMapper extends BaseMapperX&lt;SeckillActivityDO&gt; {
24 25 .likeIfPresent(SeckillActivityDO::getName, reqVO.getName())
25 26 .eqIfPresent(SeckillActivityDO::getStatus, reqVO.getStatus())
26 27 .betweenIfPresent(SeckillActivityDO::getCreateTime, reqVO.getCreateTime())
27   - .apply(ObjectUtil.isNotNull(reqVO.getConfigId()), "FIND_IN_SET(" + reqVO.getConfigId() + ",time_ids) > 0")
  28 + .apply(ObjectUtil.isNotNull(reqVO.getConfigId()), "FIND_IN_SET(" + reqVO.getConfigId() + ", config_ids) > 0")
28 29 .orderByDesc(SeckillActivityDO::getId));
29 30 }
30 31  
... ... @@ -48,4 +49,11 @@ public interface SeckillActivityMapper extends BaseMapperX&lt;SeckillActivityDO&gt; {
48 49 .setSql("totalStock = totalStock - " + count));
49 50 }
50 51  
  52 + default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) {
  53 + return selectPage(pageReqVO, new LambdaQueryWrapperX<SeckillActivityDO>()
  54 + .eqIfPresent(SeckillActivityDO::getStatus, status)
  55 + // TODO 芋艿:对 find in set 的想法;
  56 + .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0"));
  57 + }
  58 +
51 59 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java
... ... @@ -34,7 +34,7 @@ public interface SeckillProductMapper extends BaseMapperX&lt;SeckillProductDO&gt; {
34 34 default int updateActivityStock(Long id, int count) {
35 35 return update(null, new LambdaUpdateWrapper<SeckillProductDO>()
36 36 .eq(SeckillProductDO::getId, id)
37   - .gt(SeckillProductDO::getStock, 0) // TODO @puhui999:不是 > 0,是要大于 count 哈
  37 + .gt(SeckillProductDO::getStock, count)
38 38 .setSql("stock = stock - " + count));
39 39 }
40 40  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java
1 1 package cn.iocoder.yudao.module.promotion.service.bargain;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityCreateReqVO;
5 6 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityPageReqVO;
... ... @@ -7,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActi
7 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
8 9  
9 10 import javax.validation.Valid;
  11 +import java.util.List;
10 12  
11 13 /**
12 14 * 砍价活动 Service 接口
... ... @@ -61,4 +63,22 @@ public interface BargainActivityService {
61 63 */
62 64 PageResult<BargainActivityDO> getBargainActivityPage(BargainActivityPageReqVO pageReqVO);
63 65  
  66 + // TODO @puhui999:这里可以改成进行中的活动;尽量避免专门为 app 定制,或者类似的名字哈;mapper 那也是
  67 +
  68 + /**
  69 + * 获取 APP 端活动分页数据
  70 + *
  71 + * @param pageReqVO 分页请求
  72 + * @return 砍价活动分页
  73 + */
  74 + PageResult<BargainActivityDO> getBargainActivityPageForApp(PageParam pageReqVO);
  75 +
  76 + /**
  77 + * 获取 APP 端活动展示数据
  78 + *
  79 + * @param count 需要的数量
  80 + * @return 砍价活动分页
  81 + */
  82 + List<BargainActivityDO> getBargainActivityListForApp(Integer count);
  83 +
64 84 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.service.bargain;
2 2  
3 3 import cn.hutool.core.util.ObjectUtil;
4 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 7 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
7 8 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
... ... @@ -16,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional;
16 17 import org.springframework.validation.annotation.Validated;
17 18  
18 19 import javax.annotation.Resource;
  20 +import java.time.LocalDateTime;
19 21 import java.util.List;
20 22  
21 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -113,9 +115,9 @@ public class BargainActivityServiceImpl implements BargainActivityService {
113 115 // 校验存在
114 116 BargainActivityDO activityDO = validateBargainActivityExists(id);
115 117 // 校验状态
116   - if (ObjectUtil.equal(activityDO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
117   - throw exception(BARGAIN_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END);
118   - }
  118 + //if (ObjectUtil.equal(activityDO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
  119 + // throw exception(BARGAIN_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END);
  120 + //}
119 121  
120 122 // 删除
121 123 bargainActivityMapper.deleteById(id);
... ... @@ -139,4 +141,22 @@ public class BargainActivityServiceImpl implements BargainActivityService {
139 141 return bargainActivityMapper.selectPage(pageReqVO);
140 142 }
141 143  
  144 + @Override
  145 + public PageResult<BargainActivityDO> getBargainActivityPageForApp(PageParam pageReqVO) {
  146 + // 只查询进行中,且在时间范围内的
  147 + return bargainActivityMapper.selectAppPage(pageReqVO, CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now());
  148 + }
  149 +
  150 + @Override
  151 + public List<BargainActivityDO> getBargainActivityListForApp(Integer count) {
  152 + // TODO @puhui999:这种 default count 的逻辑,可以放到 controller 哈;然后可以使用 ObjectUtils.default 方法
  153 + if (count == null) {
  154 + count = 6;
  155 + }
  156 + // TODO @puhui999:这种不要用 page;会浪费一次 count;
  157 + PageResult<BargainActivityDO> result = bargainActivityMapper.selectAppPage(new PageParam().setPageSize(count),
  158 + CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now());
  159 + return result.getList();
  160 + }
  161 +
142 162 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java
1 1 package cn.iocoder.yudao.module.promotion.service.combination;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationActivityUpdateStockReqDTO;
5 4 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
6 5 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
7 6 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
... ... @@ -74,10 +73,14 @@ public interface CombinationActivityService {
74 73 List<CombinationProductDO> getCombinationProductsByActivityIds(Collection<Long> activityIds);
75 74  
76 75 /**
77   - * 更新拼图活动库存
  76 + * 校验是否满足拼团条件
  77 + * 如果不满足,会抛出异常
78 78 *
79   - * @param reqDTO 请求
  79 + * @param activityId 活动编号
  80 + * @param userId 用户编号
  81 + * @param skuId sku 编号
  82 + * @param count 数量
80 83 */
81   - void validateCombination(CombinationActivityUpdateStockReqDTO reqDTO);
  84 + void validateCombination(Long activityId, Long userId, Long skuId, Integer count);
82 85  
83 86 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java
... ... @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
9 9 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
10 10 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
11 11 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
12   -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationActivityUpdateStockReqDTO;
13 12 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
14 13 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
15 14 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
... ... @@ -34,7 +33,6 @@ import java.util.Map;
34 33  
35 34 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
36 35 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
37   -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
38 36 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
39 37 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
40 38 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
... ... @@ -88,7 +86,7 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
88 86 /**
89 87 * 校验拼团商品参与的活动是否存在冲突
90 88 *
91   - * @param spuId 商品 SPU 编号
  89 + * @param spuId 商品 SPU 编号
92 90 * @param activityId 拼团活动编号
93 91 */
94 92 private void validateProductConflict(Long spuId, Long activityId) {
... ... @@ -107,7 +105,7 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
107 105 /**
108 106 * 校验拼团商品是否都存在
109 107 *
110   - * @param spuId 商品 SPU 编号
  108 + * @param spuId 商品 SPU 编号
111 109 * @param products 拼团商品
112 110 */
113 111 private void validateProductExists(Long spuId, List<CombinationProductBaseVO> products) {
... ... @@ -152,7 +150,7 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
152 150 * 更新拼团商品
153 151 *
154 152 * @param activity 拼团活动
155   - * @param products 该活动的最新商品配置
  153 + * @param products 该活动的最新商品配置
156 154 */
157 155 private void updateCombinationProduct(CombinationActivityDO activity, List<CombinationProductBaseVO> products) {
158 156 // 第一步,对比新老数据,获得添加、修改、删除的列表
... ... @@ -217,34 +215,33 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
217 215 }
218 216  
219 217 @Override
220   - public void validateCombination(CombinationActivityUpdateStockReqDTO reqDTO) {
  218 + public void validateCombination(Long activityId, Long userId, Long skuId, Integer count) {
221 219 // 1.1 校验拼团活动是否存在
222   - CombinationActivityDO activity = validateCombinationActivityExists(reqDTO.getActivityId());
  220 + CombinationActivityDO activity = validateCombinationActivityExists(activityId);
223 221 // 1.2 校验活动是否开启
224 222 if (ObjectUtil.equal(activity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
225 223 throw exception(COMBINATION_ACTIVITY_STATUS_DISABLE);
226 224 }
227 225 // 1.3 校验是否超出单次限购数量
228   - if (activity.getSingleLimitCount() < reqDTO.getCount()) {
  226 + // TODO puhui999:count > activity.getSingleLimitCount() 会更好理解点;
  227 + if (activity.getSingleLimitCount() < count) {
229 228 throw exception(COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED);
230 229 }
231 230  
232 231 // 2. 校验是否超出总限购数量
233   - // TODO @puhui999:userId 应该接口传递哈;要保证 service 无状态
234   - List<CombinationRecordDO> recordList = combinationRecordService.getRecordListByUserIdAndActivityId(
235   - getLoginUserId(), reqDTO.getActivityId());
236   - // TODO @puhui999:最好 if true return;减少括号层数
237   - if (CollUtil.isNotEmpty(recordList)) {
238   - // 过滤出拼团成功的
239   - // TODO @puhui999:count 要不存一个在 record 里?
240   - List<Long> skuIds = convertList(recordList, CombinationRecordDO::getSkuId,
241   - item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus()));
242   - Integer countSum = tradeOrderApi.getOrderItemCountSumByOrderIdAndSkuId(convertList(recordList,
243   - CombinationRecordDO::getOrderId,
244   - item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus())), skuIds);
245   - if (activity.getTotalLimitCount() < countSum) {
246   - throw exception(COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED);
247   - }
  232 + List<CombinationRecordDO> recordList = combinationRecordService.getRecordListByUserIdAndActivityId(userId, activityId);
  233 + if (CollUtil.isEmpty(recordList)) {
  234 + return;
  235 + }
  236 + // 过滤出拼团成功的
  237 + // TODO @puhui999:count 要不存一个在 record 里?
  238 + List<Long> skuIds = convertList(recordList, CombinationRecordDO::getSkuId,
  239 + item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus()));
  240 + Integer countSum = tradeOrderApi.getOrderItemCountSumByOrderIdAndSkuId(convertList(recordList,
  241 + CombinationRecordDO::getOrderId,
  242 + item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus())), skuIds);
  243 + if (activity.getTotalLimitCount() < countSum) {
  244 + throw exception(COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED);
248 245 }
249 246 }
250 247  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java
... ... @@ -57,14 +57,4 @@ public interface CombinationRecordService {
57 57 */
58 58 List<CombinationRecordDO> getRecordListByUserIdAndActivityId(Long userId, Long activityId);
59 59  
60   - /**
61   - * 验证组合限制数
62   - * 校验是否满足限购要求
63   - *
64   - * @param count 本次购买数量
65   - * @param sumCount 已购买数量合计
66   - * @param activityId 活动编号
67   - */
68   - void validateCombinationLimitCount(Long activityId, Integer count, Integer sumCount);
69   -
70 60 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java
... ... @@ -129,19 +129,10 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
129 129 }
130 130  
131 131 // 2. 创建拼团记录
132   - // TODO @puhui999:可以把 user、spu、sku 一起放 convert 里哈;
133   - CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO);
134   - record.setVirtualGroup(false);
135   - record.setExpireTime(record.getStartTime().plusHours(activity.getLimitDuration()));
136   - record.setUserSize(activity.getUserSize());
137 132 MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId());
138   - record.setNickname(user.getNickname());
139   - record.setAvatar(user.getAvatar());
140   - ProductSpuRespDTO spu = productSpuApi.getSpu(record.getSpuId());
141   - record.setSpuName(spu.getName());
142   - ProductSkuRespDTO sku = productSkuApi.getSku(record.getSkuId());
143   - record.setPicUrl(sku.getPicUrl());
144   - recordMapper.insert(record);
  133 + ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId());
  134 + ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId());
  135 + recordMapper.insert(CombinationActivityConvert.INSTANCE.convert(reqDTO, activity, user, spu, sku));
145 136 }
146 137  
147 138 @Override
... ... @@ -154,20 +145,6 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
154 145 return recordMapper.selectListByUserIdAndActivityId(userId, activityId);
155 146 }
156 147  
157   - @Override
158   - public void validateCombinationLimitCount(Long activityId, Integer count, Integer sumCount) {
159   - // 1.1 校验拼团活动
160   - CombinationActivityDO activity = combinationActivityService.validateCombinationActivityExists(activityId);
161   - // 校验是否达到限购总限购标准
162   - if ((sumCount + count) > activity.getTotalLimitCount()) {
163   - throw exception(COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED);
164   - }
165   - // 单次购买是否达到限购标准
166   - if (count > activity.getSingleLimitCount()) {
167   - throw exception(COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED);
168   - }
169   - }
170   -
171 148 /**
172 149 * APP 端获取开团记录
173 150 *
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
1 1 package cn.iocoder.yudao.module.promotion.service.seckill;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
5 4 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
6 5 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
7 6 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
  7 +import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
8 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
9 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
10 10  
... ... @@ -37,9 +37,11 @@ public interface SeckillActivityService {
37 37 /**
38 38 * 更新秒杀库存
39 39 *
40   - * @param updateStockReqDTO 更新信息
  40 + * @param activityId 活动编号
  41 + * @param skuId sku 编号
  42 + * @param count 数量
41 43 */
42   - void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO);
  44 + void updateSeckillStock(Long activityId, Long skuId, Integer count);
43 45  
44 46 /**
45 47 * 关闭秒杀活动
... ... @@ -87,4 +89,20 @@ public interface SeckillActivityService {
87 89 */
88 90 List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> activityIds);
89 91  
  92 + /**
  93 + * 通过活动时段获取秒杀活动
  94 + *
  95 + * @param ids 时段配置编号
  96 + * @return 秒杀活动列表
  97 + */
  98 + List<SeckillActivityDO> getSeckillActivityListByConfigIds(Collection<Long> ids);
  99 +
  100 + /**
  101 + * 通过活动时段获取秒杀活动
  102 + *
  103 + * @param pageReqVO 请求
  104 + * @return 秒杀活动列表
  105 + */
  106 + PageResult<SeckillActivityDO> getSeckillActivityAppPageByConfigId(AppSeckillActivityPageReqVO pageReqVO);
  107 +
90 108 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
... ... @@ -3,15 +3,16 @@ package cn.iocoder.yudao.module.promotion.service.seckill;
3 3 import cn.hutool.core.util.ObjectUtil;
4 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 7 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
7 8 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
8 9 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
9 10 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
10   -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
11 11 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
12 12 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
13 13 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
14 14 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
  15 +import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
15 16 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
16 17 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
17 18 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
... ... @@ -147,32 +148,32 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
147 148  
148 149 @Override
149 150 @Transactional(rollbackFor = Exception.class)
150   - public void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO) {
  151 + public void updateSeckillStock(Long activityId, Long skuId, Integer count) {
151 152 // 1、校验秒杀活动是否存在
152   - SeckillActivityDO seckillActivity = getSeckillActivity(updateStockReqDTO.getActivityId());
  153 + SeckillActivityDO seckillActivity = getSeckillActivity(activityId);
153 154 // 1.1、校验库存是否充足
154   - if (seckillActivity.getTotalStock() < updateStockReqDTO.getCount()) {
  155 + if (seckillActivity.getTotalStock() < count) {
155 156 throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
156 157 }
157 158  
158 159 // 2、获取活动商品
159   - List<SeckillProductDO> products = getSeckillProductListByActivityId(updateStockReqDTO.getActivityId());
  160 + List<SeckillProductDO> products = getSeckillProductListByActivityId(activityId);
160 161 // 2.1、过滤出购买的商品
161   - SeckillProductDO product = findFirst(products, item -> ObjectUtil.equal(updateStockReqDTO.getItem().getSkuId(), item.getSkuId()));
  162 + SeckillProductDO product = findFirst(products, item -> ObjectUtil.equal(skuId, item.getSkuId()));
162 163 // 2.2、检查活动商品库存是否充足
163   - boolean isSufficient = product == null || (product.getStock() == 0 || (product.getStock() < updateStockReqDTO.getItem().getCount()) || (product.getStock() - updateStockReqDTO.getItem().getCount()) < 0);
  164 + boolean isSufficient = product == null || (product.getStock() == 0 || (product.getStock() < count) || (product.getStock() - count) < 0);
164 165 if (isSufficient) {
165 166 throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
166 167 }
167 168  
168 169 // 3、更新活动商品库存
169   - int updateCount = seckillProductMapper.updateActivityStock(product.getId(), updateStockReqDTO.getItem().getCount());
  170 + int updateCount = seckillProductMapper.updateActivityStock(product.getId(), count);
170 171 if (updateCount == 0) {
171 172 throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
172 173 }
173 174  
174 175 // 4、更新活动库存
175   - updateCount = seckillActivityMapper.updateActivityStock(seckillActivity.getId(), updateStockReqDTO.getCount());
  176 + updateCount = seckillActivityMapper.updateActivityStock(seckillActivity.getId(), count);
176 177 if (updateCount == 0) {
177 178 throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
178 179 }
... ... @@ -265,4 +266,15 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
265 266 return seckillProductMapper.selectListByActivityId(activityIds);
266 267 }
267 268  
  269 + @Override
  270 + public List<SeckillActivityDO> getSeckillActivityListByConfigIds(Collection<Long> ids) {
  271 + return CollectionUtils.filterList(seckillActivityMapper.selectList(),
  272 + item -> CollectionUtils.anyMatch(item.getConfigIds(), ids::contains));
  273 + }
  274 +
  275 + @Override
  276 + public PageResult<SeckillActivityDO> getSeckillActivityAppPageByConfigId(AppSeckillActivityPageReqVO pageReqVO) {
  277 + return seckillActivityMapper.selectPage(pageReqVO, CommonStatusEnum.ENABLE.getStatus());
  278 + }
  279 +
268 280 }
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApi.java deleted
1   -package cn.iocoder.yudao.module.trade.api.brokerage;
2   -
3   -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
4   -import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
5   -
6   -import javax.validation.constraints.NotNull;
7   -import java.time.LocalDateTime;
8   -
9   -// TODO @疯狂:是不是不需要这个啦?
10   -/**
11   - * 分销 API 接口
12   - *
13   - * @author owen
14   - */
15   -public interface BrokerageApi {
16   -
17   - /**
18   - * 获得分销用户
19   - *
20   - * @param userId 用户编号
21   - * @return 分销用户信息
22   - */
23   - BrokerageUserDTO getBrokerageUser(Long userId);
24   -
25   - /**
26   - * 【会员】绑定推广员
27   - *
28   - * @param userId 用户编号
29   - * @param bindUserId 推广员编号
30   - * @param registerTime 用户注册时间
31   - * @return 是否绑定
32   - */
33   - default boolean bindUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime) {
34   - // 注册时间在30秒内的,都算新用户
35   - // TODO @疯狂:这个要不抽到 service 里哈?
36   - boolean isNewUser = LocalDateTimeUtils.afterNow(registerTime.minusSeconds(30));
37   - return bindUser(userId, bindUserId, isNewUser);
38   - }
39   -
40   - /**
41   - * 绑定推广员
42   - *
43   - * @param userId 用户编号
44   - * @param bindUserId 推广员编号
45   - * @param isNewUser 是否为新用户
46   - * @return 是否绑定
47   - */
48   - boolean bindUser(@NotNull(message = "用户编号不能为空") Long userId,
49   - @NotNull(message = "推广员编号不能为空") Long bindUserId,
50   - @NotNull Boolean isNewUser);
51   -
52   -}
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/dto/BrokerageUserDTO.java deleted
1   -package cn.iocoder.yudao.module.trade.api.brokerage.dto;
2   -
3   -import lombok.Data;
4   -
5   -import java.time.LocalDateTime;
6   -
7   -/**
8   - * 分销用户 DTO
9   - *
10   - * @author owen
11   - */
12   -@Data
13   -public class BrokerageUserDTO {
14   -
15   - /**
16   - * 用户编号
17   - * <p>
18   - * 对应 MemberUserDO 的 id 字段
19   - */
20   - private Long id;
21   -
22   - /**
23   - * 推广员编号
24   - * <p>
25   - * 关联 MemberUserDO 的 id 字段
26   - */
27   - private Long bindUserId;
28   - /**
29   - * 推广员绑定时间
30   - */
31   - private LocalDateTime bindUserTime;
32   -
33   - /**
34   - * 推广资格
35   - */
36   - private Boolean brokerageEnabled;
37   - /**
38   - * 成为分销员时间
39   - */
40   - private LocalDateTime brokerageTime;
41   -
42   - /**
43   - * 可用佣金
44   - */
45   - private Integer price;
46   - /**
47   - * 冻结佣金
48   - */
49   - private Integer frozenPrice;
50   -
51   -}
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
... ... @@ -58,8 +58,9 @@ public interface ErrorCodeConstants {
58 58  
59 59 // ========== Price 相关 1011003000 ============
60 60 ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011003000, "支付价格计算异常,原因:价格小于等于 0");
61   - ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY = new ErrorCode(1011003001, "计算快递运费异常,收件人地址编号为空");
  61 + ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDRESS_IS_EMPTY = new ErrorCode(1011003001, "计算快递运费异常,收件人地址编号为空");
62 62 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1011003002, "计算快递运费异常,找不到对应的运费模板");
  63 + ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY = new ErrorCode(1011003003, "计算快递运费异常,自提点为空");
63 64  
64 65 // ========== 物流 Express 模块 1011004000 ==========
65 66 ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1011004000, "快递公司不存在");
... ... @@ -88,4 +89,9 @@ public interface ErrorCodeConstants {
88 89 ErrorCode BROKERAGE_BIND_OVERRIDE = new ErrorCode(1011007006, "已绑定了推广人");
89 90 ErrorCode BROKERAGE_BIND_LOOP = new ErrorCode(1011007007, "下级不能绑定自己的上级");
90 91  
  92 +
  93 + // ========== 分销提现 模块 1011008000 ==========
  94 + ErrorCode BROKERAGE_WITHDRAW_NOT_EXISTS = new ErrorCode(1011008000, "佣金提现记录不存在");
  95 + ErrorCode BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING = new ErrorCode(1011008001, "佣金提现记录状态不是审核中");
  96 +
91 97 }
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/MessageTemplateConstants.java
... ... @@ -10,4 +10,7 @@ public interface MessageTemplateConstants {
10 10  
11 11 String ORDER_DELIVERY = "order_delivery"; // 短信模版编号
12 12  
  13 + String BROKERAGE_WITHDRAW_AUDIT_APPROVE = "brokerage_withdraw_audit_approve"; // 佣金提现(审核通过)
  14 + String BROKERAGE_WITHDRAW_AUDIT_REJECT = "brokerage_withdraw_audit_reject"; // 佣金提现(审核不通过)
  15 +
13 16 }
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageUserTypeEnum.java deleted
1   -package cn.iocoder.yudao.module.trade.enums.brokerage;
2   -
3   -import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
4   -import lombok.AllArgsConstructor;
5   -import lombok.Getter;
6   -
7   -import java.util.Arrays;
8   -
9   -// TODO @疯狂:是不是搞成层级,类似 level 这样?因为本质上,它是 1 级、2 级、3 级这样的关系哈
10   -/**
11   - * 分销用户类型枚举
12   - *
13   - * @author owen
14   - */
15   -@AllArgsConstructor
16   -@Getter
17   -public enum BrokerageUserTypeEnum implements IntArrayValuable {
18   -
19   - ALL(0, "全部"),
20   - FIRST(1, "一级推广人"),
21   - SECOND(2, "二级推广人"),
22   - ;
23   -
24   - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageUserTypeEnum::getType).toArray();
25   -
26   - /**
27   - * 类型
28   - */
29   - private final Integer type;
30   - /**
31   - * 名字
32   - */
33   - private final String name;
34   -
35   - @Override
36   - public int[] array() {
37   - return ARRAYS;
38   - }
39   -
40   -}
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/delivery/DeliveryTypeEnum.java
... ... @@ -15,16 +15,15 @@ import java.util.Arrays;
15 15 @AllArgsConstructor
16 16 public enum DeliveryTypeEnum implements IntArrayValuable {
17 17  
18   - NULL(0, "无需物流"),
19 18 EXPRESS(1, "快递发货"),
20 19 PICK_UP(2, "用户自提"),;
21 20  
22   - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DeliveryTypeEnum::getMode).toArray();
  21 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DeliveryTypeEnum::getType).toArray();
23 22  
24 23 /**
25 24 * 配送方式
26 25 */
27   - private final Integer mode;
  26 + private final Integer type;
28 27 /**
29 28 * 状态名
30 29 */
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApiImpl.java deleted
1   -package cn.iocoder.yudao.module.trade.api.brokerage;
2   -
3   -import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
4   -import cn.iocoder.yudao.module.trade.convert.brokerage.user.BrokerageUserConvert;
5   -import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
6   -import org.springframework.stereotype.Service;
7   -import org.springframework.validation.annotation.Validated;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 分销 API 接口实现类
13   - *
14   - * @author owen
15   - */
16   -@Service
17   -@Validated
18   -public class BrokerageApiImpl implements BrokerageApi {
19   -
20   - @Resource
21   - private BrokerageUserService brokerageUserService;
22   -
23   - @Override
24   - public BrokerageUserDTO getBrokerageUser(Long userId) {
25   - return BrokerageUserConvert.INSTANCE.convertDTO(brokerageUserService.getBrokerageUser(userId));
26   - }
27   -
28   - @Override
29   - public boolean bindUser(Long userId, Long bindUserId, Boolean isNewUser) {
30   - return brokerageUserService.bindBrokerageUser(userId, bindUserId, isNewUser);
31   - }
32   -
33   -}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordBaseVO.java
... ... @@ -57,8 +57,8 @@ public class BrokerageRecordBaseVO {
57 57 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
58 58 private LocalDateTime unfreezeTime;
59 59  
60   - @Schema(description = "来源用户类型")
61   - private Integer sourceUserType;
  60 + @Schema(description = "来源用户等级")
  61 + private Integer sourceUserLevel;
62 62  
63 63 @Schema(description = "来源用户编号")
64 64 private Long sourceUserId;
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordPageReqVO.java
1 1 package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4   -import cn.iocoder.yudao.framework.common.validation.InEnum;
5   -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
6 4 import io.swagger.v3.oas.annotations.media.Schema;
7 5 import lombok.Data;
8 6 import lombok.EqualsAndHashCode;
... ... @@ -32,8 +30,7 @@ public class BrokerageRecordPageReqVO extends PageParam {
32 30 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
33 31 private LocalDateTime[] createTime;
34 32  
35   - @Schema(description = "用户类型")
36   - @InEnum(value = BrokerageUserTypeEnum.class, message = "用户类型必须是 {value}")
37   - private Integer sourceUserType;
  33 + @Schema(description = "用户类型", example = "1")
  34 + private Integer sourceUserLevel;
38 35  
39 36 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/BrokerageUserController.java
... ... @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.trade.convert.brokerage.user.BrokerageUserConvert
9 9 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
10 10 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
11 11 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
12   -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
13 12 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
14 13 import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
15 14 import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
... ... @@ -96,7 +95,7 @@ public class BrokerageUserController {
96 95 // 合计推广用户数量
97 96 Map<Long, Long> brokerageUserCountMap = convertMap(userIds,
98 97 userId -> userId,
99   - userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId, BrokerageUserTypeEnum.ALL));
  98 + userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId, null));
100 99  
101 100 // todo 合计提现
102 101  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserPageReqVO.java
1 1 package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4   -import cn.iocoder.yudao.framework.common.validation.InEnum;
5   -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
6 4 import io.swagger.v3.oas.annotations.media.Schema;
7 5 import lombok.Data;
8 6 import lombok.EqualsAndHashCode;
... ... @@ -22,18 +20,18 @@ public class BrokerageUserPageReqVO extends PageParam {
22 20 @Schema(description = "推广员编号", example = "4587")
23 21 private Long bindUserId;
24 22  
25   - @Schema(description = "推广资格")
  23 + @Schema(description = "推广资格", example = "true")
26 24 private Boolean brokerageEnabled;
27 25  
28 26 @Schema(description = "创建时间")
29 27 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
30 28 private LocalDateTime[] createTime;
31 29  
32   - @Schema(description = "用户类型")
33   - @InEnum(value = BrokerageUserTypeEnum.class, message = "用户类型必须是 {value}")
34   - private Integer userType;
  30 + @Schema(description = "用户等级", example = "1") // 注意,这了不是用户的会员等级,而是过滤推广的层级
  31 + private Integer level;
35 32  
36 33 @Schema(description = "绑定时间")
37 34 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
38 35 private LocalDateTime[] bindUserTime;
  36 +
39 37 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/BrokerageWithdrawController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
  6 +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
  7 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawRejectReqVO;
  8 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawPageReqVO;
  9 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawRespVO;
  10 +import cn.iocoder.yudao.module.trade.convert.brokerage.withdraw.BrokerageWithdrawConvert;
  11 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
  12 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
  13 +import cn.iocoder.yudao.module.trade.service.brokerage.withdraw.BrokerageWithdrawService;
  14 +import io.swagger.v3.oas.annotations.Operation;
  15 +import io.swagger.v3.oas.annotations.Parameter;
  16 +import io.swagger.v3.oas.annotations.tags.Tag;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.*;
  20 +
  21 +import javax.annotation.Resource;
  22 +import javax.validation.Valid;
  23 +import java.util.Map;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  27 +
  28 +@Tag(name = "管理后台 - 佣金提现")
  29 +@RestController
  30 +@RequestMapping("/trade/brokerage-withdraw")
  31 +@Validated
  32 +public class BrokerageWithdrawController {
  33 +
  34 + @Resource
  35 + private BrokerageWithdrawService brokerageWithdrawService;
  36 +
  37 + @Resource
  38 + private MemberUserApi memberUserApi;
  39 +
  40 + @PutMapping("/approve")
  41 + @Operation(summary = "通过申请")
  42 + @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')")
  43 + public CommonResult<Boolean> approveBrokerageWithdraw(@RequestParam("id") Integer id) {
  44 + brokerageWithdrawService.auditBrokerageWithdraw(id, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS, "");
  45 + return success(true);
  46 + }
  47 +
  48 + @PutMapping("/reject")
  49 + @Operation(summary = "驳回申请")
  50 + @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')")
  51 + public CommonResult<Boolean> rejectBrokerageWithdraw(@Valid @RequestBody BrokerageWithdrawRejectReqVO reqVO) {
  52 + brokerageWithdrawService.auditBrokerageWithdraw(reqVO.getId(), BrokerageWithdrawStatusEnum.AUDIT_FAIL, reqVO.getAuditReason());
  53 + return success(true);
  54 + }
  55 +
  56 + @GetMapping("/get")
  57 + @Operation(summary = "获得佣金提现")
  58 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  59 + @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:query')")
  60 + public CommonResult<BrokerageWithdrawRespVO> getBrokerageWithdraw(@RequestParam("id") Integer id) {
  61 + BrokerageWithdrawDO brokerageWithdraw = brokerageWithdrawService.getBrokerageWithdraw(id);
  62 + return success(BrokerageWithdrawConvert.INSTANCE.convert(brokerageWithdraw));
  63 + }
  64 +
  65 + @GetMapping("/page")
  66 + @Operation(summary = "获得佣金提现分页")
  67 + @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:query')")
  68 + public CommonResult<PageResult<BrokerageWithdrawRespVO>> getBrokerageWithdrawPage(@Valid BrokerageWithdrawPageReqVO pageVO) {
  69 + // 分页查询
  70 + PageResult<BrokerageWithdrawDO> pageResult = brokerageWithdrawService.getBrokerageWithdrawPage(pageVO);
  71 +
  72 + // 拼接信息
  73 + Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(
  74 + convertSet(pageResult.getList(), BrokerageWithdrawDO::getUserId));
  75 + return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap));
  76 + }
  77 +
  78 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +/**
  13 + * 佣金提现 Base VO,提供给添加、修改、详细的子 VO 使用
  14 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  15 + */
  16 +@Data
  17 +public class BrokerageWithdrawBaseVO {
  18 +
  19 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11436")
  20 + @NotNull(message = "用户编号不能为空")
  21 + private Long userId;
  22 +
  23 + @Schema(description = "提现金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "18781")
  24 + @NotNull(message = "提现金额不能为空")
  25 + private Integer price;
  26 +
  27 + @Schema(description = "提现手续费", requiredMode = Schema.RequiredMode.REQUIRED, example = "11417")
  28 + @NotNull(message = "提现手续费不能为空")
  29 + private Integer feePrice;
  30 +
  31 + @Schema(description = "当前总佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "18576")
  32 + @NotNull(message = "当前总佣金不能为空")
  33 + private Integer totalPrice;
  34 +
  35 + @Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  36 + @NotNull(message = "提现类型不能为空")
  37 + private Integer type;
  38 +
  39 + @Schema(description = "真实姓名", example = "赵六")
  40 + private String name;
  41 +
  42 + @Schema(description = "账号", example = "88677912132")
  43 + private String accountNo;
  44 +
  45 + @Schema(description = "银行名称", example = "1")
  46 + private String bankName;
  47 +
  48 + @Schema(description = "开户地址", example = "海淀支行")
  49 + private String bankAddress;
  50 +
  51 + @Schema(description = "收款码", example = "https://www.iocoder.cn")
  52 + private String accountQrCodeUrl;
  53 +
  54 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  55 + @NotNull(message = "状态不能为空")
  56 + private Integer status;
  57 +
  58 + @Schema(description = "审核驳回原因", example = "不对")
  59 + private String auditReason;
  60 +
  61 + @Schema(description = "审核时间")
  62 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  63 + private LocalDateTime auditTime;
  64 +
  65 + @Schema(description = "备注", example = "随便")
  66 + private String remark;
  67 +
  68 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
  6 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +import lombok.EqualsAndHashCode;
  10 +import lombok.ToString;
  11 +import org.springframework.format.annotation.DateTimeFormat;
  12 +
  13 +import java.time.LocalDateTime;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  16 +
  17 +@Schema(description = "管理后台 - 佣金提现分页 Request VO")
  18 +@Data
  19 +@EqualsAndHashCode(callSuper = true)
  20 +@ToString(callSuper = true)
  21 +public class BrokerageWithdrawPageReqVO extends PageParam {
  22 +
  23 + @Schema(description = "用户编号", example = "11436")
  24 + private Long userId;
  25 +
  26 + @Schema(description = "提现类型", example = "1")
  27 + @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现类型必须是 {value}")
  28 + private Integer type;
  29 +
  30 + @Schema(description = "真实姓名", example = "赵六")
  31 + private String name;
  32 +
  33 + @Schema(description = "账号", example = "886779132")
  34 + private String accountNo;
  35 +
  36 + @Schema(description = "银行名称", example = "1")
  37 + private String bankName;
  38 +
  39 + @Schema(description = "状态", example = "1")
  40 + @InEnum(value = BrokerageWithdrawStatusEnum.class, message = "状态必须是 {value}")
  41 + private Integer status;
  42 +
  43 + @Schema(description = "创建时间")
  44 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  45 + private LocalDateTime[] createTime;
  46 +
  47 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawRejectReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.ToString;
  6 +
  7 +import javax.validation.constraints.NotEmpty;
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +@Schema(description = "管理后台 - 驳回申请 Request VO")
  11 +@Data
  12 +@ToString(callSuper = true)
  13 +public class BrokerageWithdrawRejectReqVO {
  14 +
  15 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7161")
  16 + @NotNull(message = "编号不能为空")
  17 + private Integer id;
  18 +
  19 + @Schema(description = "审核驳回原因", example = "不对")
  20 + @NotEmpty(message = "审核驳回原因不能为空")
  21 + private String auditReason;
  22 +
  23 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - 佣金提现 Response VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class BrokerageWithdrawRespVO extends BrokerageWithdrawBaseVO {
  15 +
  16 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7161")
  17 + private Integer id;
  18 +
  19 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  20 + private LocalDateTime createTime;
  21 +
  22 + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
  23 + private String userNickname;
  24 +
  25 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.http
... ... @@ -8,14 +8,15 @@ GET {{appApi}}/trade/order/settlement?type=0&amp;items[0].cartId=50&amp;couponId=1
8 8 Authorization: Bearer {{appToken}}
9 9 tenant-id: {{appTenentId}}
10 10  
11   -### /trade-order/create 创建订单(基于商品)
  11 +### /trade-order/create 创建订单(基于商品)【快递】
12 12 POST {{appApi}}/trade/order/create
13 13 Content-Type: application/json
14 14 Authorization: Bearer {{appToken}}
15 15 tenant-id: {{appTenentId}}
16 16  
17 17 {
18   - "type": 0,
  18 + "pointStatus": true,
  19 + "deliveryType": 1,
19 20 "addressId": 21,
20 21 "items": [
21 22 {
... ... @@ -26,6 +27,27 @@ tenant-id: {{appTenentId}}
26 27 "remark": "我是备注"
27 28 }
28 29  
  30 +### /trade-order/create 创建订单(基于商品)【自提】
  31 +POST {{appApi}}/trade/order/create
  32 +Content-Type: application/json
  33 +Authorization: Bearer {{appToken}}
  34 +tenant-id: {{appTenentId}}
  35 +
  36 +{
  37 + "pointStatus": true,
  38 + "deliveryType": 2,
  39 + "pickUpStoreId": 1,
  40 + "items": [
  41 + {
  42 + "skuId": 1,
  43 + "count": 2
  44 + }
  45 + ],
  46 + "remark": "我是备注",
  47 + "receiverName": "土豆",
  48 + "receiverMobile": "15601691300"
  49 +}
  50 +
29 51 ### 获得订单交易的分页
30 52 GET {{appApi}}/trade/order/page?pageNo=1&pageSize=10
31 53 Authorization: Bearer {{appToken}}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
1 1 package cn.iocoder.yudao.module.trade.controller.app.order;
2 2  
3   -import cn.hutool.core.map.MapUtil;
4 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
... ... @@ -12,11 +11,8 @@ import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
12 11 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
13 12 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
14 13 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
15   -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderOperateTypeEnum;
16 14 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
17 15 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
18   -import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
19   -import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
20 16 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
21 17 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
22 18 import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
... ... @@ -65,10 +61,7 @@ public class AppTradeOrderController {
65 61 @PostMapping("/create")
66 62 @Operation(summary = "创建订单")
67 63 @PreAuthenticated
68   - @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.TEST)
69   - public CommonResult<AppTradeOrderCreateRespVO> createOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO) {
70   - TradeOrderLogUtils.setOrderInfo(10L, 1, 2,
71   - MapUtil.<String, Object>builder().put("nickname", "小明").put("thing", "种土豆").build());
  64 + public CommonResult<AppTradeOrderCreateRespVO> createOrder(@Valid @RequestBody AppTradeOrderCreateReqVO createReqVO) {
72 65 TradeOrderDO order = tradeOrderUpdateService.createOrder(getLoginUserId(), getClientIP(), createReqVO);
73 66 return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId()));
74 67 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java
1 1 package cn.iocoder.yudao.module.trade.controller.app.order.vo;
2 2  
  3 +import com.fasterxml.jackson.annotation.JsonIgnore;
3 4 import io.swagger.v3.oas.annotations.media.Schema;
4 5 import lombok.Data;
5 6  
  7 +import javax.validation.constraints.AssertTrue;
  8 +
6 9 @Schema(description = "用户 App - 交易订单创建 Request VO")
7 10 @Data
8 11 public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO {
... ... @@ -10,4 +13,10 @@ public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO {
10 13 @Schema(description = "备注", example = "这个是我的订单哟")
11 14 private String remark;
12 15  
  16 + @AssertTrue(message = "配送方式不能为空")
  17 + @JsonIgnore
  18 + public boolean isDeliveryTypeNotNull() {
  19 + return getDeliveryType() != null;
  20 + }
  21 +
13 22 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
... ... @@ -30,7 +30,7 @@ public class AppTradeOrderSettlementReqVO {
30 30 private Boolean pointStatus;
31 31  
32 32 // ========== 配送相关相关字段 ==========
33   - @Schema(description = "配送方式", required = true, example = "1")
  33 + @Schema(description = "配送方式", example = "1")
34 34 @InEnum(value = DeliveryTypeEnum.class, message = "配送方式不正确")
35 35 private Integer deliveryType;
36 36  
... ... @@ -62,6 +62,8 @@ public class AppTradeOrderSettlementReqVO {
62 62 @Schema(description = "砍价活动编号", example = "123")
63 63 private Long bargainActivityId;
64 64  
  65 + // TODO @puhui999:可以写个参数校验,如果 seckillActivityId 或 combinationActivityId 或 combinationHeadId 的情况,items 应该只有一个
  66 +
65 67 @Data
66 68 @Schema(description = "用户 App - 商品项")
67 69 @Valid
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/record/BrokerageRecordConvert.java
... ... @@ -35,7 +35,7 @@ public interface BrokerageRecordConvert {
35 35  
36 36 default BrokerageRecordDO convert(BrokerageUserDO user, BrokerageRecordBizTypeEnum bizType, String bizId,
37 37 Integer brokerageFrozenDays, int brokeragePrice, LocalDateTime unfreezeTime,
38   - String title, Long sourceUserId, Integer sourceUserType) {
  38 + String title, Long sourceUserId, Integer sourceUserLevel) {
39 39 brokerageFrozenDays = ObjectUtil.defaultIfNull(brokerageFrozenDays, 0);
40 40 // 不冻结时,佣金直接就是结算状态
41 41 Integer status = brokerageFrozenDays > 0
... ... @@ -47,7 +47,7 @@ public interface BrokerageRecordConvert {
47 47 .setTitle(title)
48 48 .setDescription(StrUtil.format(bizType.getDescription(), String.format("¥%.2f", brokeragePrice / 100d)))
49 49 .setStatus(status).setFrozenDays(brokerageFrozenDays).setUnfreezeTime(unfreezeTime)
50   - .setSourceUserType(sourceUserType).setSourceUserId(sourceUserId);
  50 + .setSourceUserLevel(sourceUserLevel).setSourceUserId(sourceUserId);
51 51 }
52 52  
53 53 default PageResult<BrokerageRecordRespVO> convertPage(PageResult<BrokerageRecordDO> pageResult, Map<Long, MemberUserRespDTO> userMap) {
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/user/BrokerageUserConvert.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.convert.brokerage.user;
3 3 import cn.hutool.core.map.MapUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
6   -import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
7 6 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserRespVO;
8 7 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
9 8 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
... ... @@ -56,6 +55,4 @@ public interface BrokerageUserConvert {
56 55 user -> target.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
57 56 return target;
58 57 }
59   -
60   - BrokerageUserDTO convertDTO(BrokerageUserDO brokerageUser);
61 58 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/withdraw/BrokerageWithdrawConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.convert.brokerage.withdraw;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
  5 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawRejectReqVO;
  6 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawRespVO;
  7 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
  8 +import org.mapstruct.Mapper;
  9 +import org.mapstruct.factory.Mappers;
  10 +
  11 +import java.util.List;
  12 +import java.util.Map;
  13 +import java.util.Optional;
  14 +
  15 +/**
  16 + * 佣金提现 Convert
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@Mapper
  21 +public interface BrokerageWithdrawConvert {
  22 +
  23 + BrokerageWithdrawConvert INSTANCE = Mappers.getMapper(BrokerageWithdrawConvert.class);
  24 +
  25 + BrokerageWithdrawDO convert(BrokerageWithdrawRejectReqVO bean);
  26 +
  27 + BrokerageWithdrawRespVO convert(BrokerageWithdrawDO bean);
  28 +
  29 + List<BrokerageWithdrawRespVO> convertList(List<BrokerageWithdrawDO> list);
  30 +
  31 + PageResult<BrokerageWithdrawRespVO> convertPage(PageResult<BrokerageWithdrawDO> page);
  32 +
  33 + default PageResult<BrokerageWithdrawRespVO> convertPage(PageResult<BrokerageWithdrawDO> pageResult, Map<Long, MemberUserRespDTO> userMap) {
  34 + PageResult<BrokerageWithdrawRespVO> result = convertPage(pageResult);
  35 + for (BrokerageWithdrawRespVO vo : result.getList()) {
  36 + vo.setUserNickname(Optional.ofNullable(userMap.get(vo.getUserId())).map(MemberUserRespDTO::getNickname).orElse(null));
  37 + }
  38 + return result;
  39 + }
  40 +
  41 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
... ... @@ -14,9 +14,7 @@ import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDT
14 14 import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
15 15 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
16 16 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
17   -import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationActivityUpdateStockReqDTO;
18 17 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
19   -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
20 18 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
21 19 import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO;
22 20 import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
... ... @@ -29,7 +27,6 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
29 27 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
30 28 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
31 29 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
32   -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
33 30 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
34 31 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
35 32 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
... ... @@ -59,6 +56,7 @@ public interface TradeOrderConvert {
59 56  
60 57 @Mappings({
61 58 @Mapping(target = "id", ignore = true),
  59 + @Mapping(source = "userId", target = "userId"),
62 60 @Mapping(source = "createReqVO.couponId", target = "couponId"),
63 61 @Mapping(target = "remark", ignore = true),
64 62 @Mapping(source = "createReqVO.remark", target = "userRemark"),
... ... @@ -67,14 +65,10 @@ public interface TradeOrderConvert {
67 65 @Mapping(source = "calculateRespBO.price.deliveryPrice", target = "deliveryPrice"),
68 66 @Mapping(source = "calculateRespBO.price.couponPrice", target = "couponPrice"),
69 67 @Mapping(source = "calculateRespBO.price.pointPrice", target = "pointPrice"),
70   - @Mapping(source = "calculateRespBO.price.payPrice", target = "payPrice"),
71   - @Mapping(source = "address.name", target = "receiverName"),
72   - @Mapping(source = "address.mobile", target = "receiverMobile"),
73   - @Mapping(source = "address.areaId", target = "receiverAreaId"),
74   - @Mapping(source = "address.detailAddress", target = "receiverDetailAddress"),
  68 + @Mapping(source = "calculateRespBO.price.payPrice", target = "payPrice")
75 69 })
76 70 TradeOrderDO convert(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO,
77   - TradePriceCalculateRespBO calculateRespBO, AddressRespDTO address);
  71 + TradePriceCalculateRespBO calculateRespBO);
78 72  
79 73 TradeOrderRespDTO convert(TradeOrderDO orderDO);
80 74  
... ... @@ -92,23 +86,16 @@ public interface TradeOrderConvert {
92 86 TradeOrderItemDO convert(TradePriceCalculateRespBO.OrderItem item);
93 87  
94 88 default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) {
95   - return new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(list));
  89 + List<ProductSkuUpdateStockReqDTO.Item> items = CollectionUtils.convertList(list, item ->
  90 + new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(-item.getCount()));
  91 + return new ProductSkuUpdateStockReqDTO(items);
96 92 }
97   -
98   - default ProductSkuUpdateStockReqDTO convertNegative(List<TradeOrderItemDO> list) {
99   - List<ProductSkuUpdateStockReqDTO.Item> items = TradeOrderConvert.INSTANCE.convertList(list);
100   - items.forEach(item -> item.setIncrCount(-item.getIncrCount()));
  93 + default ProductSkuUpdateStockReqDTO convertNegative(List<AppTradeOrderSettlementReqVO.Item> list) {
  94 + List<ProductSkuUpdateStockReqDTO.Item> items = CollectionUtils.convertList(list, item ->
  95 + new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(-item.getCount()));
101 96 return new ProductSkuUpdateStockReqDTO(items);
102 97 }
103 98  
104   - List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
105   -
106   - @Mappings({
107   - @Mapping(source = "skuId", target = "id"),
108   - @Mapping(source = "count", target = "incrCount"),
109   - })
110   - ProductSkuUpdateStockReqDTO.Item convert(TradeOrderItemDO bean);
111   -
112 99 default PayOrderCreateReqDTO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
113 100 TradePriceCalculateRespBO calculateRespBO, TradeOrderProperties orderProperties) {
114 101 PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO()
... ... @@ -221,8 +208,10 @@ public interface TradeOrderConvert {
221 208  
222 209 default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO,
223 210 List<CartDO> cartList) {
224   - TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO();
225   - reqBO.setUserId(userId).setCouponId(settlementReqVO.getCouponId()).setAddressId(settlementReqVO.getAddressId())
  211 + TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO().setUserId(userId)
  212 + .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus())
  213 + .setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId())
  214 + .setPickUpStoreId(settlementReqVO.getPickUpStoreId())
226 215 .setItems(new ArrayList<>(settlementReqVO.getItems().size()));
227 216 // 商品项的构建
228 217 Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId);
... ... @@ -276,19 +265,13 @@ public interface TradeOrderConvert {
276 265  
277 266 TradeOrderDO convert(TradeOrderRemarkReqVO reqVO);
278 267  
279   - default BrokerageAddReqBO convert(TradeOrderItemDO item, ProductSkuRespDTO sku) {
  268 + default BrokerageAddReqBO convert(MemberUserRespDTO user, TradeOrderItemDO item, ProductSkuRespDTO sku) {
280 269 return new BrokerageAddReqBO().setBizId(String.valueOf(item.getId())).setSourceUserId(item.getUserId())
281 270 .setBasePrice(item.getPayPrice() * item.getCount())
282   - .setTitle(BrokerageRecordBizTypeEnum.ORDER.getTitle()) // TODO @疯狂:标题类似:木晴冰雪成功购买云时代的JVM原理与实战;茫农成功购买深入拆解消息队列47讲
283   - .setFirstFixedPrice(sku.getFirstBrokerageRecord()).setSecondFixedPrice(sku.getSecondBrokerageRecord());
  271 + .setTitle(StrUtil.format("{}成功购买{}", user.getNickname(), item.getSpuName()))
  272 + .setFirstFixedPrice(sku.getFirstBrokeragePrice()).setSecondFixedPrice(sku.getSecondBrokeragePrice());
284 273 }
285 274  
286   - @Mapping(target = "activityId", source = "reqBO.seckillActivityId")
287   - SeckillActivityUpdateStockReqDTO convert(TradeBeforeOrderCreateReqBO reqBO);
288   -
289   - @Mapping(target = "activityId", source = "reqBO.combinationActivityId")
290   - CombinationActivityUpdateStockReqDTO convert1(TradeBeforeOrderCreateReqBO reqBO);
291   -
292 275 TradeBeforeOrderCreateReqBO convert(AppTradeOrderCreateReqVO createReqVO);
293 276  
294 277 @Mappings({
... ... @@ -300,6 +283,7 @@ public interface TradeOrderConvert {
300 283 @Mapping(target = "userId", source = "userId"),
301 284 @Mapping(target = "payPrice", source = "tradeOrderDO.payPrice"),
302 285 })
303   - TradeAfterOrderCreateReqBO convert(Long userId, AppTradeOrderCreateReqVO createReqVO, TradeOrderDO tradeOrderDO, TradeOrderItemDO orderItem);
  286 + TradeAfterOrderCreateReqBO convert(Long userId, AppTradeOrderCreateReqVO createReqVO,
  287 + TradeOrderDO tradeOrderDO, TradeOrderItemDO orderItem);
304 288  
305 289 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/record/BrokerageRecordDO.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record;
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 4 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
5 5 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
6   -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
7 6 import com.baomidou.mybatisplus.annotation.KeySequence;
8 7 import com.baomidou.mybatisplus.annotation.TableId;
9 8 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -83,11 +82,11 @@ public class BrokerageRecordDO extends BaseDO {
83 82 private LocalDateTime unfreezeTime;
84 83  
85 84 /**
86   - * 来源用户类型
  85 + * 来源用户等级
87 86 * <p>
88   - * 枚举 {@link BrokerageUserTypeEnum},被推广用户和 {@link #userId} 的推广层级关系
  87 + * 被推广用户和 {@link #userId} 的推广层级关系
89 88 */
90   - private Integer sourceUserType;
  89 + private Integer sourceUserLevel;
91 90 /**
92 91 * 来源用户编号
93 92 * <p>
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/user/BrokerageUserDO.java
... ... @@ -60,4 +60,12 @@ public class BrokerageUserDO extends BaseDO {
60 60 */
61 61 private Integer frozenPrice;
62 62  
  63 + /**
  64 + * 等级
  65 + */
  66 + private Integer level;
  67 + /**
  68 + * 路径
  69 + */
  70 + private String path;
63 71 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/withdraw/BrokerageWithdrawDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
  5 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
  6 +import com.baomidou.mybatisplus.annotation.KeySequence;
  7 +import com.baomidou.mybatisplus.annotation.TableId;
  8 +import com.baomidou.mybatisplus.annotation.TableName;
  9 +import lombok.*;
  10 +
  11 +import java.time.LocalDateTime;
  12 +
  13 +/**
  14 + * 佣金提现 DO
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@TableName("trade_brokerage_withdraw")
  19 +@KeySequence("trade_brokerage_withdraw_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  20 +@Data
  21 +@EqualsAndHashCode(callSuper = true)
  22 +@ToString(callSuper = true)
  23 +@Builder
  24 +@NoArgsConstructor
  25 +@AllArgsConstructor
  26 +public class BrokerageWithdrawDO extends BaseDO {
  27 +
  28 + /**
  29 + * 编号
  30 + */
  31 + @TableId
  32 + private Integer id;
  33 + /**
  34 + * 用户编号
  35 + *
  36 + * 关联 MemberUserDO 的 id 字段
  37 + */
  38 + private Long userId;
  39 +
  40 + /**
  41 + * 提现金额,单位:分
  42 + */
  43 + private Integer price;
  44 + /**
  45 + * 提现手续费,单位:分
  46 + */
  47 + private Integer feePrice;
  48 + /**
  49 + * 当前总佣金,单位:分
  50 + */
  51 + private Integer totalPrice;
  52 + /**
  53 + * 提现类型
  54 + * <p>
  55 + * 枚举 {@link BrokerageWithdrawTypeEnum}
  56 + */
  57 + private Integer type;
  58 +
  59 + /**
  60 + * 真实姓名
  61 + */
  62 + private String name;
  63 + /**
  64 + * 账号
  65 + */
  66 + private String accountNo;
  67 + /**
  68 + * 银行名称
  69 + */
  70 + private String bankName;
  71 + /**
  72 + * 开户地址
  73 + */
  74 + private String bankAddress;
  75 + /**
  76 + * 收款码
  77 + */
  78 + private String accountQrCodeUrl;
  79 + /**
  80 + * 状态
  81 + * <p>
  82 + * 枚举 {@link BrokerageWithdrawStatusEnum}
  83 + */
  84 + private Integer status;
  85 + /**
  86 + * 审核驳回原因
  87 + */
  88 + private String auditReason;
  89 + /**
  90 + * 审核时间
  91 + */
  92 + private LocalDateTime auditTime;
  93 + /**
  94 + * 备注
  95 + */
  96 + private String remark;
  97 +
  98 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
... ... @@ -224,6 +224,10 @@ public class TradeOrderDO extends BaseDO {
224 224 * 关联 {@link DeliveryPickUpStoreDO#getId()}
225 225 */
226 226 private Long pickUpStoreId;
  227 + /**
  228 + * 自提核销码
  229 + */
  230 + private String pickUpVerifyCode;
227 231  
228 232 // ========== 售后基本信息 ==========
229 233 /**
... ... @@ -251,12 +255,19 @@ public class TradeOrderDO extends BaseDO {
251 255 * 对应 taobao 的 trade.coupon_fee 字段
252 256 */
253 257 private Integer couponPrice;
254   - // TODO 芋艿:需要记录使用的积分;
  258 + /**
  259 + * 使用的积分
  260 + */
  261 + private Integer usePoint;
255 262 /**
256 263 * 积分抵扣的金额,单位:分
257 264 *
258 265 * 对应 taobao 的 trade.point_fee 字段
259 266 */
260 267 private Integer pointPrice;
  268 +// /**
  269 +// * 奖励的积分 TODO 疯狂:可以使用这个字段哈;
  270 +// */
  271 +// private Integer rewardPoint;
261 272  
262 273 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/record/BrokerageRecordMapper.java
... ... @@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
7 7 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
8   -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
9 8 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
10 9 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
11 10 import org.apache.ibatis.annotations.Mapper;
... ... @@ -24,14 +23,12 @@ import java.util.List;
24 23 public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
25 24  
26 25 default PageResult<BrokerageRecordDO> selectPage(BrokerageRecordPageReqVO reqVO) {
27   - boolean sourceUserTypeCondition = reqVO.getSourceUserType() != null &&
28   - !BrokerageUserTypeEnum.ALL.getType().equals(reqVO.getSourceUserType());
29 26 // 分页查询
30 27 return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageRecordDO>()
31 28 .eqIfPresent(BrokerageRecordDO::getUserId, reqVO.getUserId())
32 29 .eqIfPresent(BrokerageRecordDO::getBizType, reqVO.getBizType())
33 30 .eqIfPresent(BrokerageRecordDO::getStatus, reqVO.getStatus())
34   - .eq(sourceUserTypeCondition, BrokerageRecordDO::getSourceUserType, reqVO.getSourceUserType())
  31 + .eqIfPresent(BrokerageRecordDO::getSourceUserLevel, reqVO.getSourceUserLevel())
35 32 .betweenIfPresent(BrokerageRecordDO::getCreateTime, reqVO.getCreateTime())
36 33 .orderByDesc(BrokerageRecordDO::getId));
37 34 }
... ... @@ -59,4 +56,5 @@ public interface BrokerageRecordMapper extends BaseMapperX&lt;BrokerageRecordDO&gt; {
59 56 UserBrokerageSummaryBO selectCountAndSumPriceByUserIdAndBizTypeAndStatus(@Param("userId") Long userId,
60 57 @Param("bizType") Integer bizType,
61 58 @Param("status") Integer status);
  59 +
62 60 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/BrokerageUserMapper.java
1 1 package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4   -import cn.hutool.core.util.StrUtil;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
7 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
8 7 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
9 8 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
10   -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
11   -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
12 9 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
13 10 import org.apache.ibatis.annotations.Mapper;
14   -import org.apache.ibatis.annotations.Select;
  11 +
  12 +import java.util.List;
15 13  
16 14 /**
17 15 * 分销用户 Mapper
... ... @@ -21,35 +19,16 @@ import org.apache.ibatis.annotations.Select;
21 19 @Mapper
22 20 public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
23 21  
24   - default PageResult<BrokerageUserDO> selectPage(BrokerageUserPageReqVO reqVO) {
  22 + default PageResult<BrokerageUserDO> selectPage(BrokerageUserPageReqVO reqVO, List<Integer> levels) {
25 23 return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageUserDO>()
26 24 .eqIfPresent(BrokerageUserDO::getBrokerageEnabled, reqVO.getBrokerageEnabled())
27 25 .betweenIfPresent(BrokerageUserDO::getCreateTime, reqVO.getCreateTime())
28 26 .betweenIfPresent(BrokerageUserDO::getBindUserTime, reqVO.getBindUserTime())
29   - .and(reqVO.getBindUserId() != null, w -> buildBindUserCondition(reqVO, w))
  27 + .findInSetIfPresent(BrokerageUserDO::getPath, reqVO.getBindUserId())
  28 + .inIfPresent(BrokerageUserDO::getLevel, levels)
30 29 .orderByDesc(BrokerageUserDO::getId));
31 30 }
32 31  
33   - static void buildBindUserCondition(BrokerageUserPageReqVO reqVO, LambdaQueryWrapper<BrokerageUserDO> wrapper) {
34   - if (BrokerageUserTypeEnum.FIRST.getType().equals(reqVO.getUserType())) {
35   - buildFirstBindUserCondition(reqVO.getBindUserId(), wrapper);
36   - } else if (BrokerageUserTypeEnum.SECOND.getType().equals(reqVO.getUserType())) {
37   - buildSecondBindUserCondition(reqVO.getBindUserId(), wrapper);
38   - } else {
39   - // TODO @疯狂:要不要把这个逻辑,挪到 Service 里,算出子用户有哪些,然后 IN?
40   - buildFirstBindUserCondition(reqVO.getBindUserId(), wrapper);
41   - buildSecondBindUserCondition(reqVO.getBindUserId(), wrapper.or()); // 通过 or 实现多个条件
42   - }
43   - }
44   -
45   - static void buildFirstBindUserCondition(Long bindUserId, LambdaQueryWrapper<BrokerageUserDO> wrapper) {
46   - wrapper.eq(BrokerageUserDO::getBindUserId, bindUserId);
47   - }
48   -
49   - static void buildSecondBindUserCondition(Long bindUserId, LambdaQueryWrapper<BrokerageUserDO> wrapper) {
50   - wrapper.inSql(BrokerageUserDO::getBindUserId, StrUtil.format("SELECT id FROM trade_brokerage_user WHERE bind_user_id = {}", bindUserId));
51   - }
52   -
53 32 /**
54 33 * 更新用户可用佣金(增加)
55 34 *
... ... @@ -128,7 +107,8 @@ public interface BrokerageUserMapper extends BaseMapperX&lt;BrokerageUserDO&gt; {
128 107 default void updateBindUserIdAndBindUserTimeToNull(Long id) {
129 108 update(null, new LambdaUpdateWrapper<BrokerageUserDO>()
130 109 .eq(BrokerageUserDO::getId, id)
131   - .set(BrokerageUserDO::getBindUserId, null).set(BrokerageUserDO::getBindUserTime, null));
  110 + .set(BrokerageUserDO::getBindUserId, null).set(BrokerageUserDO::getBindUserTime, null)
  111 + .set(BrokerageUserDO::getLevel, 1).set(BrokerageUserDO::getPath, ""));
132 112 }
133 113  
134 114 default void updateEnabledFalseAndBrokerageTimeToNull(Long id) {
... ... @@ -137,11 +117,10 @@ public interface BrokerageUserMapper extends BaseMapperX&lt;BrokerageUserDO&gt; {
137 117 .set(BrokerageUserDO::getBrokerageEnabled, false).set(BrokerageUserDO::getBrokerageTime, null));
138 118 }
139 119  
140   - default Long selectCountByBindUserId(Long bindUserId) {
141   - return selectCount(BrokerageUserDO::getBindUserId, bindUserId);
  120 + default Long selectCountByBindUserIdAndLevelIn(Long bindUserId, List<Integer> levels) {
  121 + return selectCount(new LambdaQueryWrapperX<BrokerageUserDO>()
  122 + .findInSetIfPresent(BrokerageUserDO::getPath, bindUserId)
  123 + .inIfPresent(BrokerageUserDO::getLevel, levels));
142 124 }
143 125  
144   - @Select("SELECT COUNT(1) from trade_brokerage_user WHERE bind_user_id IN (SELECT id FROM trade_brokerage_user WHERE bind_user_id = #{bindUserId})")
145   - Long selectCountByBindUserIdInBindUserId(Long bindUserId);
146   -
147 126 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/withdraw/BrokerageWithdrawMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.withdraw;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawPageReqVO;
  7 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
  8 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  9 +import org.apache.ibatis.annotations.Mapper;
  10 +
  11 +/**
  12 + * 佣金提现 Mapper
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Mapper
  17 +public interface BrokerageWithdrawMapper extends BaseMapperX<BrokerageWithdrawDO> {
  18 +
  19 + default PageResult<BrokerageWithdrawDO> selectPage(BrokerageWithdrawPageReqVO reqVO) {
  20 + return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageWithdrawDO>()
  21 + .eqIfPresent(BrokerageWithdrawDO::getUserId, reqVO.getUserId())
  22 + .eqIfPresent(BrokerageWithdrawDO::getType, reqVO.getType())
  23 + .likeIfPresent(BrokerageWithdrawDO::getName, reqVO.getName())
  24 + .eqIfPresent(BrokerageWithdrawDO::getAccountNo, reqVO.getAccountNo())
  25 + .likeIfPresent(BrokerageWithdrawDO::getBankName, reqVO.getBankName())
  26 + .eqIfPresent(BrokerageWithdrawDO::getStatus, reqVO.getStatus())
  27 + .betweenIfPresent(BrokerageWithdrawDO::getCreateTime, reqVO.getCreateTime())
  28 + .orderByAsc(BrokerageWithdrawDO::getStatus).orderByDesc(BrokerageWithdrawDO::getId));
  29 + }
  30 +
  31 + default int updateByIdAndStatus(Integer id, Integer status, BrokerageWithdrawDO updateObj) {
  32 + return update(updateObj, new LambdaUpdateWrapper<BrokerageWithdrawDO>()
  33 + .eq(BrokerageWithdrawDO::getId, id)
  34 + .eq(BrokerageWithdrawDO::getStatus, status));
  35 + }
  36 +
  37 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordServiceImpl.java
... ... @@ -15,7 +15,6 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
15 15 import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record.BrokerageRecordMapper;
16 16 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
17 17 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
18   -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
19 18 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
20 19 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
21 20 import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
... ... @@ -29,6 +28,7 @@ import javax.annotation.Resource;
29 28 import java.time.LocalDateTime;
30 29 import java.util.ArrayList;
31 30 import java.util.List;
  31 +import java.util.Objects;
32 32  
33 33 /**
34 34 * 佣金记录 Service 实现类
... ... @@ -74,7 +74,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
74 74 }
75 75 // 1.2 计算一级分佣
76 76 addBrokerage(firstUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageFirstPercent(),
77   - bizType, BrokerageUserTypeEnum.FIRST);
  77 + bizType, 1);
78 78  
79 79 // 2.1 获得二级推广员
80 80 if (firstUser.getBindUserId() == null) {
... ... @@ -86,7 +86,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
86 86 }
87 87 // 2.2 计算二级分佣
88 88 addBrokerage(secondUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageSecondPercent(),
89   - bizType, BrokerageUserTypeEnum.SECOND);
  89 + bizType, 2);
90 90 }
91 91  
92 92 @Override
... ... @@ -142,10 +142,10 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
142 142 * @param brokerageFrozenDays 冻结天数
143 143 * @param brokeragePercent 佣金比例
144 144 * @param bizType 业务类型
145   - * @param sourceUserType 来源用户类型
  145 + * @param sourceUserLevel 来源用户等级
146 146 */
147 147 private void addBrokerage(BrokerageUserDO user, List<BrokerageAddReqBO> list, Integer brokerageFrozenDays,
148   - Integer brokeragePercent, BrokerageRecordBizTypeEnum bizType, BrokerageUserTypeEnum sourceUserType) {
  148 + Integer brokeragePercent, BrokerageRecordBizTypeEnum bizType, Integer sourceUserLevel) {
149 149 // 1.1 处理冻结时间
150 150 LocalDateTime unfreezeTime = null;
151 151 if (brokerageFrozenDays != null && brokerageFrozenDays > 0) {
... ... @@ -157,12 +157,12 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
157 157 for (BrokerageAddReqBO item : list) {
158 158 // 计算金额
159 159 Integer fixedPrice;
160   - if (BrokerageUserTypeEnum.FIRST.equals(sourceUserType)) {
  160 + if (Objects.equals(sourceUserLevel, 1)) {
161 161 fixedPrice = item.getFirstFixedPrice();
162   - } else if (BrokerageUserTypeEnum.SECOND.equals(sourceUserType)) {
  162 + } else if (Objects.equals(sourceUserLevel, 2)) {
163 163 fixedPrice = item.getSecondFixedPrice();
164 164 } else {
165   - throw new IllegalArgumentException(StrUtil.format("来源用户({}) 不合法", sourceUserType));
  165 + throw new IllegalArgumentException(StrUtil.format("用户等级({}) 不合法", sourceUserLevel));
166 166 }
167 167 int brokeragePrice = calculatePrice(item.getBasePrice(), brokeragePercent, fixedPrice);
168 168 if (brokeragePrice <= 0) {
... ... @@ -172,7 +172,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
172 172 // 创建记录实体
173 173 records.add(BrokerageRecordConvert.INSTANCE.convert(user, bizType, item.getBizId(),
174 174 brokerageFrozenDays, brokeragePrice, unfreezeTime, item.getTitle(),
175   - item.getSourceUserId(), sourceUserType.getType()));
  175 + item.getSourceUserId(), sourceUserLevel));
176 176 }
177 177 if (CollUtil.isEmpty(records)) {
178 178 return;
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserService.java
1 1 package cn.iocoder.yudao.module.trade.service.brokerage.user;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
4 5 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
5 6 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
6   -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
7 7  
  8 +import javax.validation.constraints.NotNull;
  9 +import java.time.LocalDateTime;
8 10 import java.util.Collection;
9 11 import java.util.List;
10 12  
... ... @@ -91,10 +93,24 @@ public interface BrokerageUserService {
91 93 * 获得推广用户数量
92 94 *
93 95 * @param bindUserId 绑定的推广员编号
94   - * @param userType 用户类型
  96 + * @param level 推广用户等级
95 97 * @return 推广用户数量
96 98 */
97   - Long getBrokerageUserCountByBindUserId(Long bindUserId, BrokerageUserTypeEnum userType);
  99 + Long getBrokerageUserCountByBindUserId(Long bindUserId, Integer level);
  100 +
  101 + /**
  102 + * 【会员】绑定推广员
  103 + *
  104 + * @param userId 用户编号
  105 + * @param bindUserId 推广员编号
  106 + * @param registerTime 用户注册时间
  107 + * @return 是否绑定
  108 + */
  109 + default boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime) {
  110 + // 注册时间在30秒内的,都算新用户
  111 + boolean isNewUser = LocalDateTimeUtils.afterNow(registerTime.minusSeconds(30));
  112 + return bindBrokerageUser(userId, bindUserId, isNewUser);
  113 + }
98 114  
99 115 /**
100 116 * 【会员】绑定推广员
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImpl.java
1 1 package cn.iocoder.yudao.module.trade.service.brokerage.user;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.lang.Assert;
4 5 import cn.hutool.core.util.BooleanUtil;
  6 +import cn.hutool.core.util.ObjectUtil;
  7 +import cn.hutool.core.util.StrUtil;
5 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 9 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
7 10 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
... ... @@ -9,17 +12,13 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
9 12 import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user.BrokerageUserMapper;
10 13 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
11 14 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
12   -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
13 15 import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
14 16 import org.springframework.stereotype.Service;
15 17 import org.springframework.validation.annotation.Validated;
16 18  
17 19 import javax.annotation.Resource;
18 20 import java.time.LocalDateTime;
19   -import java.util.Collection;
20   -import java.util.List;
21   -import java.util.Objects;
22   -import java.util.Optional;
  21 +import java.util.*;
23 22  
24 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
25 24 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
... ... @@ -51,7 +50,8 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
51 50  
52 51 @Override
53 52 public PageResult<BrokerageUserDO> getBrokerageUserPage(BrokerageUserPageReqVO pageReqVO) {
54   - return brokerageUserMapper.selectPage(pageReqVO);
  53 + List<Integer> levels = buildUserQueryLevels(pageReqVO.getBindUserId(), pageReqVO.getLevel());
  54 + return brokerageUserMapper.selectPage(pageReqVO, levels);
55 55 }
56 56  
57 57 @Override
... ... @@ -66,10 +66,15 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
66 66 return;
67 67 }
68 68  
  69 + // 绑定关系未发生变化
  70 + // TODO @疯狂:这个放到“情况一”之前,貌似也没关系?
  71 + if (Objects.equals(brokerageUser.getBindUserId(), bindUserId)) {
  72 + return;
  73 + }
  74 +
69 75 // 情况二:修改推广员
70 76 validateCanBindUser(brokerageUser, bindUserId);
71   - brokerageUserMapper.updateById(new BrokerageUserDO().setId(id)
72   - .setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now()));
  77 + brokerageUserMapper.updateById(fillBindUserData(bindUserId, new BrokerageUserDO().setId(id)));
73 78 }
74 79  
75 80 @Override
... ... @@ -132,19 +137,12 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
132 137 }
133 138  
134 139 @Override
135   - public Long getBrokerageUserCountByBindUserId(Long bindUserId, BrokerageUserTypeEnum userType) {
136   - switch (userType) {
137   - case ALL: // TODO @疯狂:ALL 是不是不用搞个枚举,默认为空就是不过滤哈~
138   - Long firstCount = brokerageUserMapper.selectCountByBindUserId(bindUserId);
139   - Long secondCount = brokerageUserMapper.selectCountByBindUserIdInBindUserId(bindUserId);
140   - return firstCount + secondCount;
141   - case FIRST:
142   - return brokerageUserMapper.selectCountByBindUserId(bindUserId);
143   - case SECOND:
144   - return brokerageUserMapper.selectCountByBindUserIdInBindUserId(bindUserId);
145   - default:
146   - return 0L;
  140 + public Long getBrokerageUserCountByBindUserId(Long bindUserId, Integer level) {
  141 + List<Integer> levels = buildUserQueryLevels(bindUserId, level);
  142 + if (CollUtil.isEmpty(levels)) {
  143 + return 0L;
147 144 }
  145 + return brokerageUserMapper.selectCountByBindUserIdAndLevelIn(bindUserId, levels);
148 146 }
149 147  
150 148 @Override
... ... @@ -171,14 +169,30 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
171 169 brokerageUser.setBrokerageEnabled(true).setBrokerageTime(LocalDateTime.now());
172 170 }
173 171 brokerageUser.setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now());
174   - brokerageUserMapper.insert(brokerageUser);
  172 + brokerageUserMapper.insert(fillBindUserData(bindUserId, brokerageUser));
175 173 } else {
176   - brokerageUserMapper.updateById(new BrokerageUserDO().setId(userId)
177   - .setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now()));
  174 + brokerageUserMapper.updateById(fillBindUserData(bindUserId, new BrokerageUserDO().setId(userId)));
178 175 }
179 176 return true;
180 177 }
181 178  
  179 + private BrokerageUserDO fillBindUserData(Long bindUserId, BrokerageUserDO brokerageUser) {
  180 + BrokerageUserDO bindUser = getBrokerageUser(bindUserId);
  181 +
  182 + Integer bindUserLevel = 0;
  183 + String bindUserPath = "";
  184 + if (bindUser != null) {
  185 + bindUserLevel = ObjectUtil.defaultIfNull(bindUser.getLevel(), 0);
  186 + bindUserPath = bindUser.getPath();
  187 + }
  188 +
  189 + String path = StrUtil.isEmpty(bindUserPath)
  190 + ? String.valueOf(bindUserId)
  191 + : String.format("%s,%s", bindUserPath, bindUserId);
  192 + return brokerageUser.setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now())
  193 + .setLevel(bindUserLevel + 1).setPath(path);
  194 + }
  195 +
182 196 @Override
183 197 public Boolean getUserBrokerageEnabled(Long userId) {
184 198 // 全局分销功能是否开启
... ... @@ -231,11 +245,30 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
231 245 throw exception(BROKERAGE_BIND_SELF);
232 246 }
233 247  
234   - // TODO @疯狂:这块是不是一直查询到根节点,中间不允许出现自己;就是不能形成环。虽然目前是 2 级,但是未来可能会改多级; = = 环的话,就会存在问题哈
235   - // A->B->A:下级不能绑定自己的上级, A->B->C->A可以!!
236   - if (Objects.equals(user.getId(), bindUser.getBindUserId())) {
  248 + // 下级不能绑定自己的上级
  249 + if (StrUtil.split(bindUser.getPath(), ",").contains(String.valueOf(user.getId()))) {
237 250 throw exception(BROKERAGE_BIND_LOOP);
238 251 }
239 252 }
240 253  
  254 + // TODO @芋艿:这个层级,要微信讨论下;
  255 + private List<Integer> buildUserQueryLevels(Long bindUserId, Integer level) {
  256 + List<Integer> levels = new ArrayList<>(2);
  257 +
  258 + BrokerageUserDO bindUser = getBrokerageUser(bindUserId);
  259 + if (bindUser == null) {
  260 + return levels;
  261 + }
  262 +
  263 + if (level == null) {
  264 + // 默认查两层
  265 + levels.add(bindUser.getLevel() + 1);
  266 + levels.add(bindUser.getLevel() + 2);
  267 + } else {
  268 + levels.add(bindUser.getLevel() + level);
  269 + }
  270 + return levels;
  271 +
  272 + }
  273 +
241 274 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/withdraw/BrokerageWithdrawService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.brokerage.withdraw;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawPageReqVO;
  5 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
  6 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
  7 +
  8 +/**
  9 + * 佣金提现 Service 接口
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +public interface BrokerageWithdrawService {
  14 +
  15 + /**
  16 + * 审核佣金提现
  17 + *
  18 + * @param id 佣金编号
  19 + * @param status 审核状态
  20 + * @param auditReason 驳回原因
  21 + */
  22 +
  23 + void auditBrokerageWithdraw(Integer id, BrokerageWithdrawStatusEnum status, String auditReason);
  24 +
  25 + /**
  26 + * 获得佣金提现
  27 + *
  28 + * @param id 编号
  29 + * @return 佣金提现
  30 + */
  31 + BrokerageWithdrawDO getBrokerageWithdraw(Integer id);
  32 +
  33 + /**
  34 + * 获得佣金提现分页
  35 + *
  36 + * @param pageReqVO 分页查询
  37 + * @return 佣金提现分页
  38 + */
  39 + PageResult<BrokerageWithdrawDO> getBrokerageWithdrawPage(BrokerageWithdrawPageReqVO pageReqVO);
  40 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/withdraw/BrokerageWithdrawServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.brokerage.withdraw;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.map.MapUtil;
  5 +import cn.hutool.core.util.ObjectUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
  8 +import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
  9 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawPageReqVO;
  10 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
  11 +import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.withdraw.BrokerageWithdrawMapper;
  12 +import cn.iocoder.yudao.module.trade.enums.MessageTemplateConstants;
  13 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
  14 +import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
  15 +import org.springframework.stereotype.Service;
  16 +import org.springframework.transaction.annotation.Transactional;
  17 +import org.springframework.validation.annotation.Validated;
  18 +
  19 +import javax.annotation.Resource;
  20 +import java.time.LocalDateTime;
  21 +import java.util.Map;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  24 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_WITHDRAW_NOT_EXISTS;
  25 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING;
  26 +
  27 +/**
  28 + * 佣金提现 Service 实现类
  29 + *
  30 + * @author 芋道源码
  31 + */
  32 +@Service
  33 +@Validated
  34 +public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
  35 +
  36 + @Resource
  37 + private BrokerageWithdrawMapper brokerageWithdrawMapper;
  38 +
  39 + @Resource
  40 + private BrokerageRecordService brokerageRecordService;
  41 +
  42 + @Resource
  43 + private NotifyMessageSendApi notifyMessageSendApi;
  44 +
  45 + @Override
  46 + @Transactional(rollbackFor = Exception.class)
  47 + public void auditBrokerageWithdraw(Integer id, BrokerageWithdrawStatusEnum status, String auditReason) {
  48 + // 1.1 校验存在
  49 + BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
  50 + // 1.2 校验状态为审核中
  51 + if (ObjectUtil.notEqual(BrokerageWithdrawStatusEnum.AUDITING.getStatus(), withdraw.getStatus())) {
  52 + throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
  53 + }
  54 +
  55 + // 2. 更新
  56 + BrokerageWithdrawDO updateObj = new BrokerageWithdrawDO()
  57 + .setStatus(status.getStatus())
  58 + .setAuditReason(auditReason)
  59 + .setAuditTime(LocalDateTime.now());
  60 + int rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDITING.getStatus(), updateObj);
  61 + if (rows == 0) {
  62 + throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
  63 + }
  64 +
  65 + // 3. 驳回时需要退还用户佣金
  66 + String templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_APPROVE;
  67 + if (BrokerageWithdrawStatusEnum.AUDIT_FAIL.equals(status)) {
  68 + templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_REJECT;
  69 +
  70 + // todo @owen
  71 +// brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW, withdraw.getPrice(), "");
  72 + }
  73 +
  74 + // 4. 通知用户
  75 + Map<String, Object> templateParams = MapUtil.<String, Object>builder()
  76 + .put("createTime", LocalDateTimeUtil.formatNormal(withdraw.getCreateTime()))
  77 + .put("price", String.format("%.2f", withdraw.getPrice() / 100d))
  78 + .put("reason", withdraw.getAuditReason())
  79 + .build();
  80 + NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO()
  81 + .setUserId(withdraw.getUserId())
  82 + .setTemplateCode(templateCode).setTemplateParams(templateParams);
  83 + notifyMessageSendApi.sendSingleMessageToMember(reqDTO);
  84 + }
  85 +
  86 + private BrokerageWithdrawDO validateBrokerageWithdrawExists(Integer id) {
  87 + BrokerageWithdrawDO withdraw = brokerageWithdrawMapper.selectById(id);
  88 + if (withdraw == null) {
  89 + throw exception(BROKERAGE_WITHDRAW_NOT_EXISTS);
  90 + }
  91 + return withdraw;
  92 + }
  93 +
  94 + @Override
  95 + public BrokerageWithdrawDO getBrokerageWithdraw(Integer id) {
  96 + return brokerageWithdrawMapper.selectById(id);
  97 + }
  98 +
  99 + @Override
  100 + public PageResult<BrokerageWithdrawDO> getBrokerageWithdrawPage(BrokerageWithdrawPageReqVO pageReqVO) {
  101 + return brokerageWithdrawMapper.selectPage(pageReqVO);
  102 + }
  103 +
  104 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.service.order;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.hutool.core.util.ObjectUtil;
  6 +import cn.hutool.core.util.RandomUtil;
6 7 import cn.hutool.core.util.StrUtil;
7 8 import cn.hutool.extra.spring.SpringUtil;
8 9 import cn.iocoder.yudao.framework.common.core.KeyValue;
... ... @@ -12,6 +13,8 @@ import cn.iocoder.yudao.module.member.api.address.AddressApi;
12 13 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
13 14 import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
14 15 import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
  16 +import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
  17 +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
15 18 import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
16 19 import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
17 20 import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
... ... @@ -40,11 +43,11 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
40 43 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
41 44 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
42 45 import cn.iocoder.yudao.module.trade.dal.redis.no.TradeOrderNoRedisDAO;
43   -import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
44 46 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
45 47 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
46 48 import cn.iocoder.yudao.module.trade.enums.order.*;
47 49 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
  50 +import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
48 51 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
49 52 import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
50 53 import cn.iocoder.yudao.module.trade.service.cart.CartService;
... ... @@ -115,6 +118,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
115 118 @Resource
116 119 private BargainRecordApi bargainRecordApi;
117 120 @Resource
  121 + private MemberUserApi memberUserApi;
  122 + @Resource
118 123 private MemberLevelApi memberLevelApi;
119 124 @Resource
120 125 private MemberPointApi memberPointApi;
... ... @@ -178,140 +183,127 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
178 183  
179 184 @Override
180 185 @Transactional(rollbackFor = Exception.class)
  186 + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CREATE)
181 187 public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) {
182   - // 1、执行订单创建前置处理器
183   - // TODO @puhui999:最好也抽个 beforeOrderCreate 方法;
184   - TradeBeforeOrderCreateReqBO beforeOrderCreateReqBO = TradeOrderConvert.INSTANCE.convert(createReqVO);
185   - beforeOrderCreateReqBO.setOrderType(validateActivity(createReqVO));
186   - beforeOrderCreateReqBO.setCount(getSumValue(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCount, Integer::sum));
187   - // TODO @puhui999:这里有个纠结点;handler 的定义是只处理指定类型的订单的拓展逻辑;还是通用的 handler,类似可以处理优惠劵等等
188   - tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(beforeOrderCreateReqBO));
189   -
190   - // 2. 价格计算
  188 + // 0. 价格计算
191 189 TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
192 190  
  191 + // 1. 订单创建前的逻辑
  192 + beforeCreateTradeOrder(userId, createReqVO, calculateRespBO);
  193 +
193 194 // 2.1 插入 TradeOrderDO 订单
194 195 TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO);
195 196 // 2.2 插入 TradeOrderItemDO 订单项
196 197 List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO);
197 198  
198   - // 3. 订单创建完后的逻辑
  199 + // 3. 订单创建后的逻辑
199 200 afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO);
200   -
201   - // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
202 201 return order;
203 202 }
204 203  
205   -
206 204 // TODO @puhui999:订单超时,自动取消;
207 205  
208   - /**
209   - * 校验收件地址是否存在
210   - *
211   - * @param userId 用户编号
212   - * @param addressId 收件地址编号
213   - * @return 收件地址
214   - */
215   - private AddressRespDTO validateAddress(Long userId, Long addressId) {
216   - AddressRespDTO address = addressApi.getAddress(addressId, userId);
217   - if (address == null) {
218   - throw exception(ErrorCodeConstants.ORDER_CREATE_ADDRESS_NOT_FOUND);
219   - }
220   - return address;
221   - }
222   -
223 206 private TradeOrderDO createTradeOrder(Long userId, String clientIp, AppTradeOrderCreateReqVO createReqVO,
224 207 TradePriceCalculateRespBO calculateRespBO) {
225   - // 用户选择物流配送的时候才需要填写收货地址
226   - AddressRespDTO address = new AddressRespDTO();
227   - if (Objects.equals(createReqVO.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getMode())) {
228   - // 用户收件地址的校验
229   - address = validateAddress(userId, createReqVO.getAddressId());
230   - }
231   - TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address);
232   - String no = orderNoRedisDAO.generate(TradeOrderNoRedisDAO.TRADE_ORDER_NO_PREFIX);
233   - order.setType(validateActivity(createReqVO));
234   - order.setNo(no);
  208 + TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO);
  209 + order.setType(calculateRespBO.getType());
  210 + order.setNo(orderNoRedisDAO.generate(TradeOrderNoRedisDAO.TRADE_ORDER_NO_PREFIX));
235 211 order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
236 212 order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
237 213 order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
238 214 order.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源?
239   - // 支付信息
  215 + // 支付 + 退款信息
240 216 order.setAdjustPrice(0).setPayStatus(false);
  217 + order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
241 218 // 物流信息
242 219 order.setDeliveryType(createReqVO.getDeliveryType());
243   - // 退款信息
244   - order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
  220 + if (Objects.equals(createReqVO.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getType())) {
  221 + AddressRespDTO address = addressApi.getAddress(createReqVO.getAddressId(), userId);
  222 + Assert.notNull(address, "地址({}) 不能为空", createReqVO.getAddressId()); // 价格计算时,已经计算
  223 + order.setReceiverName(address.getName()).setReceiverMobile(address.getMobile())
  224 + .setReceiverAreaId(address.getAreaId()).setReceiverDetailAddress(address.getDetailAddress());
  225 + } else if (Objects.equals(createReqVO.getDeliveryType(), DeliveryTypeEnum.PICK_UP.getType())) {
  226 + order.setReceiverName(createReqVO.getReceiverName()).setReceiverMobile(createReqVO.getReceiverMobile());
  227 + order.setPickUpVerifyCode(RandomUtil.randomNumbers(8)); // 随机一个核销码,长度为 8 位
  228 + }
245 229 tradeOrderMapper.insert(order);
246   - // TODO @puhui999:如果是门店订单,则需要生成核销码;
247 230 return order;
248 231 }
249 232  
  233 + private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO,
  234 + TradePriceCalculateRespBO calculateRespBO) {
  235 + List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO);
  236 + tradeOrderItemMapper.insertBatch(orderItems);
  237 + return orderItems;
  238 + }
  239 +
250 240 /**
251   - * 校验活动,并返回订单类型
  241 + * 订单创建前,执行前置逻辑
252 242 *
253   - * @param createReqVO 请求参数
254   - * @return 订单类型
  243 + * @param userId 用户编号
  244 + * @param createReqVO 创建订单请求
  245 + * @param calculateRespBO 订单价格计算结果
255 246 */
256   - private Integer validateActivity(AppTradeOrderCreateReqVO createReqVO) {
257   - if (createReqVO.getSeckillActivityId() != null) {
258   - return TradeOrderTypeEnum.SECKILL.getType();
259   - }
260   - if (createReqVO.getCombinationActivityId() != null) {
261   - return TradeOrderTypeEnum.COMBINATION.getType();
262   - }
263   - // TODO 砍价敬请期待
264   - return TradeOrderTypeEnum.NORMAL.getType();
265   - }
  247 + private void beforeCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
  248 + TradePriceCalculateRespBO calculateRespBO) {
  249 + // 1. 执行订单创建前置处理器
  250 + TradeBeforeOrderCreateReqBO beforeOrderCreateReqBO = TradeOrderConvert.INSTANCE.convert(createReqVO);
  251 + beforeOrderCreateReqBO.setOrderType(calculateRespBO.getType());
  252 + beforeOrderCreateReqBO.setUserId(userId);
  253 + beforeOrderCreateReqBO.setCount(getSumValue(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCount, Integer::sum));
  254 + // TODO @puhui999:这里有个纠结点;handler 的定义是只处理指定类型的订单的拓展逻辑;还是通用的 handler,类似可以处理优惠劵等等
  255 + tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(beforeOrderCreateReqBO));
266 256  
267   - private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) {
268   - List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO);
269   - tradeOrderItemMapper.insertBatch(orderItems);
270   - return orderItems;
  257 + // 2. 下单时扣减商品库存
  258 + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(createReqVO.getItems()));
271 259 }
272 260  
273 261 /**
274   - * 执行创建完创建完订单后的逻辑
  262 + * 订单创建后,执行后置逻辑
275 263 *
276 264 * 例如说:优惠劵的扣减、积分的扣减、支付单的创建等等
277 265 *
278 266 * @param userId 用户编号
279 267 * @param createReqVO 创建订单请求
280   - * @param tradeOrderDO 交易订单
  268 + * @param order 交易订单
281 269 * @param calculateRespBO 订单价格计算结果
282 270 */
283 271 private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
284   - TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems,
  272 + TradeOrderDO order, List<TradeOrderItemDO> orderItems,
285 273 TradePriceCalculateRespBO calculateRespBO) {
286   - // 执行订单创建后置处理器
287   - tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(TradeOrderConvert.INSTANCE.convert(userId, createReqVO, tradeOrderDO, orderItems.get(0))));
288   -
289   - // 扣减积分 TODO 芋艿:待实现,需要前置;
290   - // 这个是不是应该放到支付成功之后?如果支付后的话,可能积分可以重复使用哈。资源类,都要预扣
  274 + // 1. 执行订单创建后置处理器
  275 + // TODO @puhui999:从通用性来说,应该不用 orderItems.get(0)
  276 + tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(
  277 + TradeOrderConvert.INSTANCE.convert(userId, createReqVO, order, orderItems.get(0))));
291 278  
292   - // 有使用优惠券时更新 TODO 芋艿:需要前置;
  279 + // 2. 有使用优惠券时更新
  280 + // 不在前置扣减的原因,是因为优惠劵要记录使用的订单号
293 281 if (createReqVO.getCouponId() != null) {
294 282 couponApi.useCoupon(new CouponUseReqDTO().setId(createReqVO.getCouponId()).setUserId(userId)
295   - .setOrderId(tradeOrderDO.getId()));
  283 + .setOrderId(order.getId()));
296 284 }
297 285  
298   - // 下单时扣减商品库存
299   - productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
  286 + // 3. 扣减积分
  287 + // 不在前置扣减的原因,是因为积分扣减时,需要记录关联业务
  288 + if (order.getUsePoint() != null && order.getUsePoint() > 0) {
  289 + memberPointApi.reducePoint(userId, calculateRespBO.getUsePoint(),
  290 + MemberPointBizTypeEnum.ORDER_USE.getType(), String.valueOf(order.getId()));
  291 + }
300 292  
301   - // 删除购物车商品
  293 + // 4. 删除购物车商品
302 294 Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
303 295 if (CollUtil.isNotEmpty(cartIds)) {
304 296 cartService.deleteCart(userId, cartIds);
305 297 }
306 298  
307   - // 生成预支付
308   - createPayOrder(tradeOrderDO, orderItems, calculateRespBO);
  299 + // 5. 生成预支付
  300 + createPayOrder(order, orderItems, calculateRespBO);
309 301  
310   - // 增加订单日志 TODO 芋艿:待实现
  302 + // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
311 303 }
312 304  
313   -
314   - private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, TradePriceCalculateRespBO calculateRespBO) {
  305 + private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
  306 + TradePriceCalculateRespBO calculateRespBO) {
315 307 // 创建支付单,用于后续的支付
316 308 PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert(
317 309 order, orderItems, calculateRespBO, tradeOrderProperties);
... ... @@ -339,6 +331,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
339 331 }
340 332 // 校验活动
341 333 // 1、拼团活动
  334 + // TODO @puhui999:这块也抽象到 handler 里
342 335 if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
343 336 // 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录
344 337 combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), LocalDateTime.now());
... ... @@ -416,7 +409,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
416 409 // 1.1 校验并获得交易订单(可发货)
417 410 TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId());
418 411 // 1.2 校验 deliveryType 是否为快递,是快递才可以发货
419   - if (ObjectUtil.notEqual(order.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getMode())) {
  412 + if (ObjectUtil.notEqual(order.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getType())) {
420 413 throw exception(ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS);
421 414 }
422 415  
... ... @@ -726,7 +719,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
726 719 }
727 720  
728 721 // TODO 活动相关库存回滚需要活动 id,活动 id 怎么获取?app 端能否传过来
729   - tradeOrderHandlers.forEach(handler -> handler.rollbackStock());
  722 + tradeOrderHandlers.forEach(handler -> handler.rollback());
730 723  
731 724 // 2.回滚库存
732 725 List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
... ... @@ -736,6 +729,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
736 729 couponApi.returnUsedCoupon(order.getCouponId());
737 730  
738 731 // 4.回滚积分:积分是支付成功后才增加的吧? 回复:每个项目不同,目前看下来,确认收货貌似更合适,我再看看其它项目的业务选择;
  732 + // TODO @疯狂:有赞是可配置(支付 or 确认收货),我们按照支付好列;然后这里的退积分,指的是下单时的积分抵扣。
739 733  
740 734 // TODO 芋艿:OrderLog
741 735  
... ... @@ -768,22 +762,26 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
768 762  
769 763 @Async
770 764 protected void addUserPointAsync(Long userId, Integer payPrice, Long orderId) {
771   - int bizType = MemberPointBizTypeEnum.ORDER_BUY.getType();
  765 + // TODO @疯狂:具体多少积分,需要分成 2 不分:1. 支付金额;2. 商品金额
  766 + int bizType = MemberPointBizTypeEnum.ORDER_REWARD.getType();
772 767 memberPointApi.addPoint(userId, payPrice, bizType, String.valueOf(orderId));
773 768 }
774 769  
775 770 @Async
776 771 protected void reduceUserPointAsync(Long userId, Integer refundPrice, Long afterSaleId) {
  772 + // TODO @疯狂:退款时,按照金额比例,退还积分;https://help.youzan.com/displaylist/detail_4_4-1-49185
777 773 int bizType = MemberPointBizTypeEnum.ORDER_CANCEL.getType();
778 774 memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
779 775 }
780 776  
781   -
782 777 @Async
783 778 protected void addBrokerageAsync(Long userId, Long orderId) {
  779 + MemberUserRespDTO user = memberUserApi.getUser(userId);
  780 + Assert.notNull(user);
  781 +
784 782 List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
785 783 List<BrokerageAddReqBO> list = convertList(orderItems,
786   - item -> TradeOrderConvert.INSTANCE.convert(item, productSkuApi.getSku(item.getSkuId())));
  784 + item -> TradeOrderConvert.INSTANCE.convert(user, item, productSkuApi.getSku(item.getSkuId())));
787 785 brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, list);
788 786 }
789 787  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/bo/TradeBeforeOrderCreateReqBO.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.service.order.bo;
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6   -import javax.validation.Valid;
7 6 import javax.validation.constraints.NotNull;
8 7  
9 8 // TODO 芋艿:在想想这些参数的定义
... ... @@ -15,6 +14,8 @@ import javax.validation.constraints.NotNull;
15 14 @Data
16 15 public class TradeBeforeOrderCreateReqBO {
17 16  
  17 + // TODO @puhui999:注释也写下哈;bo 还是写注释噢
  18 +
18 19 @NotNull(message = "订单类型不能为空")
19 20 private Integer orderType;
20 21  
... ... @@ -34,25 +35,16 @@ public class TradeBeforeOrderCreateReqBO {
34 35 @Schema(description = "砍价活动编号", example = "123")
35 36 private Long bargainActivityId;
36 37  
37   - @NotNull(message = "购买数量不能为空")
38   - private Integer count;
39   -
40   - @NotNull(message = "活动商品不能为空")
41   - private Item item;
  38 + @NotNull(message = "SPU 编号不能为空")
  39 + private Long spuId;
42 40  
43   - @Data
44   - @Valid
45   - public static class Item {
  41 + @NotNull(message = "SKU 编号活动商品不能为空")
  42 + private Long skuId;
46 43  
47   - @NotNull(message = "SPU 编号不能为空")
48   - private Long spuId;
  44 + @NotNull(message = "用户编号不能为空")
  45 + private Long userId;
49 46  
50   - @NotNull(message = "SKU 编号活动商品不能为空")
51   - private Long skuId;
52   -
53   - @NotNull(message = "购买数量不能为空")
54   - private Integer count;
55   -
56   - }
  47 + @NotNull(message = "购买数量不能为空")
  48 + private Integer count;
57 49  
58 50 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java
... ... @@ -22,7 +22,7 @@ public class TradeBargainHandler implements TradeOrderHandler {
22 22  
23 23 @Override
24 24 public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
25   - // 如果是秒杀订单
  25 + // 如果是砍价订单
26 26 if (ObjectUtil.notEqual(TradeOrderTypeEnum.BARGAIN.getType(), reqBO.getOrderType())) {
27 27 return;
28 28 }
... ... @@ -37,7 +37,7 @@ public class TradeBargainHandler implements TradeOrderHandler {
37 37 }
38 38  
39 39 @Override
40   - public void rollbackStock() {
  40 + public void rollback() {
41 41  
42 42 }
43 43  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java
1 1 package cn.iocoder.yudao.module.trade.service.order.handler;
2 2  
3 3 import cn.hutool.core.util.ObjectUtil;
4   -import cn.iocoder.yudao.module.promotion.api.combination.CombinationApi;
  4 +import cn.iocoder.yudao.module.promotion.api.combination.CombinationActivityApi;
5 5 import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
6 6 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
7 7 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
... ... @@ -20,7 +20,7 @@ import javax.annotation.Resource;
20 20 public class TradeCombinationHandler implements TradeOrderHandler {
21 21  
22 22 @Resource
23   - private CombinationApi combinationApi;
  23 + private CombinationActivityApi combinationActivityApi;
24 24 @Resource
25 25 private CombinationRecordApi combinationRecordApi;
26 26  
... ... @@ -32,17 +32,22 @@ public class TradeCombinationHandler implements TradeOrderHandler {
32 32 }
33 33  
34 34 // 校验是否满足拼团活动相关限制
35   - combinationApi.validateCombination(TradeOrderConvert.INSTANCE.convert1(reqBO));
  35 + combinationActivityApi.validateCombination(reqBO.getCombinationActivityId(), reqBO.getUserId(), reqBO.getSkuId(), reqBO.getCount());
36 36 }
37 37  
38 38 @Override
39 39 public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {
  40 + // TODO @puhui999:需要判断下;
  41 + if (true) {
  42 + return;
  43 + }
  44 +
40 45 // 创建砍价记录
41 46 combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(reqBO));
42 47 }
43 48  
44 49 @Override
45   - public void rollbackStock() {
  50 + public void rollback() {
46 51  
47 52 }
48 53  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java
... ... @@ -25,8 +25,8 @@ public interface TradeOrderHandler {
25 25 void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO);
26 26  
27 27 /**
28   - * 回滚活动相关库存
  28 + * 回滚
29 29 */
30   - void rollbackStock();
  30 + void rollback();
31 31  
32 32 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.trade.service.order.handler;
2 2  
3 3 import cn.hutool.core.util.ObjectUtil;
4 4 import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
5   -import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
6 5 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
7 6 import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterOrderCreateReqBO;
8 7 import cn.iocoder.yudao.module.trade.service.order.bo.TradeBeforeOrderCreateReqBO;
... ... @@ -28,7 +27,7 @@ public class TradeSeckillHandler implements TradeOrderHandler {
28 27 return;
29 28 }
30 29  
31   - seckillActivityApi.updateSeckillStock(TradeOrderConvert.INSTANCE.convert(reqBO));
  30 + seckillActivityApi.updateSeckillStock(reqBO.getSeckillActivityId(), reqBO.getSkuId(), reqBO.getCount());
32 31 }
33 32  
34 33 @Override
... ... @@ -37,7 +36,7 @@ public class TradeSeckillHandler implements TradeOrderHandler {
37 36 }
38 37  
39 38 @Override
40   - public void rollbackStock() {
  39 + public void rollback() {
41 40  
42 41 }
43 42  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java
1 1 package cn.iocoder.yudao.module.trade.service.price.bo;
2 2  
3 3 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
4   -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
5 4 import lombok.Data;
6 5  
7 6 import javax.validation.Valid;
... ... @@ -18,13 +17,6 @@ import java.util.List;
18 17 public class TradePriceCalculateReqBO {
19 18  
20 19 /**
21   - * 订单类型
22   - *
23   - * 枚举 {@link TradeOrderTypeEnum}
24   - */
25   - private Integer type;
26   -
27   - /**
28 20 * 用户编号
29 21 *
30 22 * 对应 MemberUserDO 的 id 编号
... ... @@ -37,20 +29,31 @@ public class TradePriceCalculateReqBO {
37 29 * 对应 CouponDO 的 id 编号
38 30 */
39 31 private Long couponId;
  32 + // TODO @疯狂:需要增加一个 PriceCalculator 实现积分扣减的计算;写回到 TradePriceCalculateRespBO 的 usePoint
  33 + /**
  34 + * 是否使用积分
  35 + */
  36 + @NotNull(message = "是否使用积分不能为空")
  37 + private Boolean pointStatus;
40 38  
41 39 /**
  40 + * 配送方式
  41 + *
  42 + * 枚举 {@link DeliveryTypeEnum}
  43 + */
  44 + private Integer deliveryType;
  45 + /**
42 46 * 收货地址编号
43 47 *
44 48 * 对应 MemberAddressDO 的 id 编号
45 49 */
46 50 private Long addressId;
47   -
48 51 /**
49   - * 配送方式
  52 + * 自提门店编号
50 53 *
51   - * 枚举 {@link DeliveryTypeEnum}
  54 + * 对应 PickUpStoreDO 的 id 编号
52 55 */
53   - private Integer deliveryType;
  56 + private Long pickUpStoreId;
54 57  
55 58 /**
56 59 * 商品 SKU 数组
... ... @@ -58,6 +61,30 @@ public class TradePriceCalculateReqBO {
58 61 @NotNull(message = "商品数组不能为空")
59 62 private List<Item> items;
60 63  
  64 + // ========== 秒杀活动相关字段 ==========
  65 + /**
  66 + * 秒杀活动编号
  67 + */
  68 + private Long seckillActivityId;
  69 +
  70 + // ========== 拼团活动相关字段 ==========
  71 + // TODO @puhui999:是不是拼团记录的编号哈?
  72 + /**
  73 + * 拼团活动编号
  74 + */
  75 + private Long combinationActivityId;
  76 +
  77 + /**
  78 + * 拼团团长编号
  79 + */
  80 + private Long combinationHeadId;
  81 +
  82 + // ========== 砍价活动相关字段 ==========
  83 + /**
  84 + * 砍价活动编号
  85 + */
  86 + private Long bargainActivityId;
  87 +
61 88 /**
62 89 * 商品 SKU
63 90 */
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
... ... @@ -49,6 +49,11 @@ public class TradePriceCalculateRespBO {
49 49 private Long couponId;
50 50  
51 51 /**
  52 + * 使用的积分
  53 + */
  54 + private Integer usePoint;
  55 +
  56 + /**
52 57 * 订单价格
53 58 */
54 59 @Data
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
... ... @@ -2,11 +2,14 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 6 import cn.iocoder.yudao.module.member.api.address.AddressApi;
6 7 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
  8 +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
7 9 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
8 10 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
9 11 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
  12 +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService;
10 13 import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO;
11 14 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
12 15 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
... ... @@ -22,8 +25,7 @@ import java.util.Set;
22 25  
23 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
24 27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
25   -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY;
26   -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND;
  28 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
27 29  
28 30 /**
29 31 * 运费的 {@link TradePriceCalculator} 实现类
... ... @@ -37,20 +39,41 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
37 39  
38 40 @Resource
39 41 private AddressApi addressApi;
  42 +
  43 + @Resource
  44 + private DeliveryPickUpStoreService deliveryPickUpStoreService;
40 45 @Resource
41 46 private DeliveryExpressTemplateService deliveryExpressTemplateService;
42 47  
43 48 @Override
44 49 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
45   - // TODO @芋艿:如果门店自提,需要校验是否开启;
46   - // 1.1 判断配送方式
47   - if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
  50 + if (param.getDeliveryType() == null) {
48 51 return;
49 52 }
  53 + if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
  54 + calculateByPickUp(param, result);
  55 + } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
  56 + calculateExpress(param, result);
  57 + }
  58 + }
  59 +
  60 + private void calculateByPickUp(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  61 + if (param.getPickUpStoreId() == null) {
  62 + throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY);
  63 + }
  64 + DeliveryPickUpStoreDO pickUpStore = deliveryPickUpStoreService.getDeliveryPickUpStore(param.getPickUpStoreId());
  65 + if (pickUpStore == null || CommonStatusEnum.DISABLE.getStatus().equals(pickUpStore.getStatus())) {
  66 + throw exception(PICK_UP_STORE_NOT_EXISTS);
  67 + }
  68 + }
  69 +
  70 + // ========= 快递发货 ==========
  71 +
  72 + private void calculateExpress(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  73 + // 1. 得到收件地址区域
50 74 if (param.getAddressId() == null) {
51   - throw exception(PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY);
  75 + throw exception(PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDRESS_IS_EMPTY);
52 76 }
53   - // 1.2 得到收件地址区域
54 77 AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
55 78 Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
56 79  
... ... @@ -89,8 +112,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
89 112 for (OrderItem orderItem : orderItems) {
90 113 totalCount += orderItem.getCount();
91 114 totalPrice += orderItem.getPayPrice();
92   - totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
93   - totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
  115 + if (orderItem.getWeight() != null) {
  116 + totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
  117 + }
  118 + if (orderItem.getVolume() != null) {
  119 + totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
  120 + }
94 121 }
95 122 // 优先判断是否包邮. 如果包邮不计算快递运费
96 123 if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.price.calculator;
  2 +
  3 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
  4 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.core.annotation.Order;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +/**
  10 + * 使用积分的 {@link TradePriceCalculator} 实现类
  11 + *
  12 + * @author owen
  13 + */
  14 +@Component
  15 +@Order(TradePriceCalculator.ORDER_POINT_USE)
  16 +@Slf4j
  17 +public class TradePointUsePriceCalculator implements TradePriceCalculator {
  18 +
  19 + @Override
  20 + public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  21 + // TODO 疯狂:待实现,嘿嘿;
  22 + if (param.getPointStatus()) {
  23 + result.setUsePoint(10);
  24 + } else {
  25 + result.setUsePoint(0);
  26 + }
  27 + }
  28 +
  29 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculator.java
... ... @@ -6,6 +6,9 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
6 6 /**
7 7 * 价格计算的计算器接口
8 8 *
  9 + * 优惠计算顺序:
  10 + * 1. <a href="https://help.youzan.com/displaylist/detail_4_4-1-53316">积分抵现、会员价、优惠券、粉丝专享价、满减送哪个优先计算?</>
  11 + *
9 12 * @author 芋道源码
10 13 */
11 14 public interface TradePriceCalculator {
... ... @@ -13,12 +16,13 @@ public interface TradePriceCalculator {
13 16 int ORDER_DISCOUNT_ACTIVITY = 10;
14 17 int ORDER_REWARD_ACTIVITY = 20;
15 18 int ORDER_COUPON = 30;
  19 + int ORDER_POINT_USE = 40;
16 20 /**
17 21 * 快递运费的计算
18 22 *
19 23 * 放在各种营销活动、优惠劵后面 TODO
20 24 */
21   - int ORDER_DELIVERY = 40;
  25 + int ORDER_DELIVERY = 50;
22 26  
23 27 void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result);
24 28  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java
... ... @@ -4,6 +4,7 @@ import cn.hutool.core.lang.Assert;
4 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 5 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
6 6 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
  7 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
7 8 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
8 9 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
9 10  
... ... @@ -28,7 +29,7 @@ public class TradePriceCalculatorHelper {
28 29 List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) {
29 30 // 创建 PriceCalculateRespDTO 对象
30 31 TradePriceCalculateRespBO result = new TradePriceCalculateRespBO();
31   - result.setType(param.getType());
  32 + result.setType(getOrderType(param));
32 33 result.setPromotions(new ArrayList<>());
33 34  
34 35 // 创建它的 OrderItem 属性
... ... @@ -70,6 +71,23 @@ public class TradePriceCalculatorHelper {
70 71 }
71 72  
72 73 /**
  74 + * 计算订单类型
  75 + *
  76 + * @param param 计算参数
  77 + * @return 订单类型
  78 + */
  79 + private static Integer getOrderType(TradePriceCalculateReqBO param) {
  80 + if (param.getSeckillActivityId() != null) {
  81 + return TradeOrderTypeEnum.SECKILL.getType();
  82 + }
  83 + if (param.getCombinationActivityId() != null) {
  84 + return TradeOrderTypeEnum.COMBINATION.getType();
  85 + }
  86 + // TODO 砍价敬请期待
  87 + return TradeOrderTypeEnum.NORMAL.getType();
  88 + }
  89 +
  90 + /**
73 91 * 基于订单项,重新计算 price 总价
74 92 *
75 93 * @param result 计算结果
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java
... ... @@ -45,7 +45,7 @@ public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
45 45 public void testCalculatePrice() {
46 46 // 准备参数
47 47 TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO()
48   - .setType(TradeOrderTypeEnum.NORMAL.getType()).setUserId(10L)
  48 + .setUserId(10L)
49 49 .setCouponId(20L).setAddressId(30L)
50 50 .setItems(Arrays.asList(
51 51 new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true),
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java
... ... @@ -50,7 +50,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
50 50 public void init(){
51 51 // 准备参数
52 52 reqBO = new TradePriceCalculateReqBO()
53   - .setDeliveryType(DeliveryTypeEnum.EXPRESS.getMode())
  53 + .setDeliveryType(DeliveryTypeEnum.EXPRESS.getType())
54 54 .setAddressId(10L)
55 55 .setUserId(1L)
56 56 .setItems(asList(
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/withdraw/BrokerageWithdrawServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.withdraw;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  5 +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawPageReqVO;
  6 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
  7 +import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.withdraw.BrokerageWithdrawMapper;
  8 +import cn.iocoder.yudao.module.trade.service.brokerage.withdraw.BrokerageWithdrawServiceImpl;
  9 +import org.junit.jupiter.api.Disabled;
  10 +import org.junit.jupiter.api.Test;
  11 +import org.springframework.context.annotation.Import;
  12 +
  13 +import javax.annotation.Resource;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
  16 +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
  17 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  18 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  19 +import static org.junit.jupiter.api.Assertions.assertEquals;
  20 +
  21 +// TODO 芋艿:后续 review
  22 +/**
  23 + * {@link BrokerageWithdrawServiceImpl} 的单元测试类
  24 + *
  25 + * @author 芋道源码
  26 + */
  27 +@Import(BrokerageWithdrawServiceImpl.class)
  28 +public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
  29 +
  30 + @Resource
  31 + private BrokerageWithdrawServiceImpl brokerageWithdrawService;
  32 +
  33 + @Resource
  34 + private BrokerageWithdrawMapper brokerageWithdrawMapper;
  35 +
  36 + @Test
  37 + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
  38 + public void testGetBrokerageWithdrawPage() {
  39 + // mock 数据
  40 + BrokerageWithdrawDO dbBrokerageWithdraw = randomPojo(BrokerageWithdrawDO.class, o -> { // 等会查询到
  41 + o.setUserId(null);
  42 + o.setType(null);
  43 + o.setName(null);
  44 + o.setAccountNo(null);
  45 + o.setBankName(null);
  46 + o.setStatus(null);
  47 + o.setCreateTime(null);
  48 + });
  49 + brokerageWithdrawMapper.insert(dbBrokerageWithdraw);
  50 + // 测试 userId 不匹配
  51 + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserId(null)));
  52 + // 测试 type 不匹配
  53 + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setType(null)));
  54 + // 测试 name 不匹配
  55 + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setName(null)));
  56 + // 测试 accountNo 不匹配
  57 + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAccountNo(null)));
  58 + // 测试 bankName 不匹配
  59 + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setBankName(null)));
  60 + // 测试 status 不匹配
  61 + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setStatus(null)));
  62 + // 测试 auditReason 不匹配
  63 + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAuditReason(null)));
  64 + // 测试 auditTime 不匹配
  65 + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAuditTime(null)));
  66 + // 测试 remark 不匹配
  67 + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setRemark(null)));
  68 + // 测试 createTime 不匹配
  69 + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setCreateTime(null)));
  70 + // 准备参数
  71 + BrokerageWithdrawPageReqVO reqVO = new BrokerageWithdrawPageReqVO();
  72 + reqVO.setUserId(null);
  73 + reqVO.setType(null);
  74 + reqVO.setName(null);
  75 + reqVO.setAccountNo(null);
  76 + reqVO.setBankName(null);
  77 + reqVO.setStatus(null);
  78 + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
  79 +
  80 + // 调用
  81 + PageResult<BrokerageWithdrawDO> pageResult = brokerageWithdrawService.getBrokerageWithdrawPage(reqVO);
  82 + // 断言
  83 + assertEquals(1, pageResult.getTotal());
  84 + assertEquals(1, pageResult.getList().size());
  85 + assertPojoEquals(dbBrokerageWithdraw, pageResult.getList().get(0));
  86 + }
  87 +
  88 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql
... ... @@ -4,3 +4,4 @@ DELETE FROM trade_after_sale;
4 4 DELETE FROM trade_after_sale_log;
5 5 DELETE FROM trade_brokerage_user;
6 6 DELETE FROM trade_brokerage_record;
  7 +DELETE FROM "trade_brokerage_withdraw";
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql
... ... @@ -163,4 +163,29 @@ CREATE TABLE IF NOT EXISTS &quot;trade_brokerage_record&quot;
163 163 "deleted" bit NOT NULL DEFAULT FALSE,
164 164 "tenant_id" bigint not null default '0',
165 165 PRIMARY KEY ("id")
166   -) COMMENT '佣金记录';
167 166 \ No newline at end of file
  167 +) COMMENT '佣金记录';
  168 +CREATE TABLE IF NOT EXISTS "trade_brokerage_withdraw"
  169 +(
  170 + "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  171 + "user_id" bigint NOT NULL,
  172 + "price" int NOT NULL,
  173 + "fee_price" int NOT NULL,
  174 + "total_price" int NOT NULL,
  175 + "type" varchar NOT NULL,
  176 + "name" varchar,
  177 + "account_no" varchar,
  178 + "bank_name" varchar,
  179 + "bank_address" varchar,
  180 + "account_qr_code_url" varchar,
  181 + "status" varchar NOT NULL,
  182 + "audit_reason" varchar,
  183 + "audit_time" varchar,
  184 + "remark" varchar,
  185 + "creator" varchar DEFAULT '',
  186 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  187 + "updater" varchar DEFAULT '',
  188 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  189 + "deleted" bit NOT NULL DEFAULT FALSE,
  190 + "tenant_id" bigint not null default '0',目
  191 + PRIMARY KEY ("id")
  192 +) COMMENT '佣金提现';
168 193 \ No newline at end of file
... ...
yudao-module-member/yudao-module-member-api/pom.xml
... ... @@ -21,6 +21,13 @@
21 21 <groupId>cn.iocoder.boot</groupId>
22 22 <artifactId>yudao-common</artifactId>
23 23 </dependency>
  24 +
  25 + <!-- 参数校验 -->
  26 + <dependency>
  27 + <groupId>org.springframework.boot</groupId>
  28 + <artifactId>spring-boot-starter-validation</artifactId>
  29 + <optional>true</optional>
  30 + </dependency>
24 31 </dependencies>
25 32  
26 33 </project>
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java
... ... @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.member.api.point;
2 2  
3 3 import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
4 4  
  5 +import javax.validation.constraints.Min;
  6 +
5 7 /**
6 8 * 用户积分的 API 接口
7 9 *
... ... @@ -17,6 +19,18 @@ public interface MemberPointApi {
17 19 * @param bizType 业务类型 {@link MemberPointBizTypeEnum}
18 20 * @param bizId 业务编号
19 21 */
20   - void addPoint(Long userId, Integer point, Integer bizType, String bizId);
  22 + void addPoint(Long userId, @Min(value = 1L, message = "积分必须是正数") Integer point,
  23 + Integer bizType, String bizId);
  24 +
  25 + /**
  26 + * 减少用户积分
  27 + *
  28 + * @param userId 用户编号
  29 + * @param point 积分
  30 + * @param bizType 业务类型 {@link MemberPointBizTypeEnum}
  31 + * @param bizId 业务编号
  32 + */
  33 + void reducePoint(Long userId, @Min(value = 1L, message = "积分必须是正数") Integer point,
  34 + Integer bizType, String bizId);
21 35  
22 36 }
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
... ... @@ -39,7 +39,7 @@ public interface ErrorCodeConstants {
39 39 ErrorCode SIGN_IN_CONFIG_EXISTS = new ErrorCode(1004009001, "签到天数规则已存在");
40 40  
41 41 //========== 签到配置 1004010000 ==========
42   -
  42 + ErrorCode SIGN_IN_RECORD_TODAY_EXISTS = new ErrorCode(1004010000,"今日已签到,请勿重复签到");
43 43  
44 44 //========== 用户等级 1004011000 ==========
45 45 ErrorCode LEVEL_NOT_EXISTS = new ErrorCode(1004011000, "用户等级不存在");
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java
... ... @@ -17,8 +17,10 @@ import java.util.Objects;
17 17 public enum MemberPointBizTypeEnum implements IntArrayValuable {
18 18  
19 19 SIGN(1, "签到", "签到获得 {} 积分", true),
20   - ORDER_BUY(10, "订单消费", "下单获得 {} 积分", true),
21   - ORDER_CANCEL(11, "订单取消", "退单获得 {} 积分", false); // 退回积分
  20 + ORDER_REWARD(10, "订单奖励", "下单获得 {} 积分", true),
  21 + ORDER_CANCEL(11, "订单取消", "退单获得 {} 积分", false), // 退回积分
  22 + ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 扣减积分
  23 + ;
22 24  
23 25 /**
24 26 * 类型
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApiImpl.java
... ... @@ -31,4 +31,13 @@ public class MemberPointApiImpl implements MemberPointApi {
31 31 memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId);
32 32 }
33 33  
  34 + @Override
  35 + public void reducePoint(Long userId, Integer point, Integer bizType, String bizId) {
  36 + MemberPointBizTypeEnum bizTypeEnum = MemberPointBizTypeEnum.getByType(bizType);
  37 + if (bizTypeEnum == null) {
  38 + throw exception(POINT_RECORD_BIZ_NOT_SUPPORT);
  39 + }
  40 + memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId);
  41 + }
  42 +
34 43 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.app.signin;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert;
  5 +import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
  6 +import cn.iocoder.yudao.module.member.service.signin.MemberSignInRecordService;
  7 +import io.swagger.v3.oas.annotations.Operation;
  8 +import io.swagger.v3.oas.annotations.tags.Tag;
  9 +import org.springframework.web.bind.annotation.GetMapping;
  10 +import org.springframework.web.bind.annotation.PostMapping;
  11 +import org.springframework.web.bind.annotation.RequestMapping;
  12 +import org.springframework.web.bind.annotation.RestController;
  13 +
  14 +import javax.annotation.Resource;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  17 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  18 +
  19 +// TODO @xiaqing:sign-in
  20 +@Tag(name = "签到APP - 签到")
  21 +@RestController
  22 +@RequestMapping("/member/signin")
  23 +public class AppMemberSignInController {
  24 +
  25 + @Resource
  26 + private MemberSignInRecordService signInRecordService;
  27 +
  28 + // TODO @xiaqing:泛型:
  29 + // TODO @xiaqing:合并到 AppMemberSignInRecordController 的 getSignInRecordSummary 里哈。
  30 + @Operation(summary = "个人签到信息")
  31 + @GetMapping("/get-summary")
  32 + public CommonResult getUserSummary(){
  33 + return success(signInRecordService.getSignInRecordSummary(getLoginUserId()));
  34 + }
  35 +
  36 + // TODO @xiaqing:泛型:
  37 + // TODO @xiaqing:合并到 AppMemberSignInRecordController 的 createSignInRecord 里哈。
  38 + @Operation(summary = "会员签到")
  39 + @PostMapping("/create")
  40 + public CommonResult create(){
  41 + MemberSignInRecordDO recordDO = signInRecordService.createSignRecord(getLoginUserId());
  42 + return success(MemberSignInRecordConvert.INSTANCE.coverRecordToAppRecordVo(recordDO));
  43 + }
  44 +
  45 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/AppMemberSignInRecordRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.app.signin.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "用户签到积分 Response VO")
  9 +@Data
  10 +public class AppMemberSignInRecordRespVO {
  11 +
  12 + @Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  13 + private Integer day;
  14 +
  15 + @Schema(description = "签到的分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  16 + private Integer point;
  17 +
  18 + @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
  19 + private LocalDateTime createTime;
  20 +
  21 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/AppMemberSignInSummaryRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.app.signin.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "用户签到统计信息 Response VO")
  9 +@Data
  10 +public class AppMemberSignInSummaryRespVO {
  11 +
  12 + @Schema(description = "持续签到天数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  13 + private Integer continuousDay;
  14 +
  15 + @Schema(description = "总签到天数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  16 + private Integer totalDay;
  17 +
  18 + @Schema(description = "当天是否签到", requiredMode = Schema.RequiredMode.REQUIRED,example = "true")
  19 + private Boolean todaySignIn ;
  20 +
  21 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInRecordConvert.java
... ... @@ -36,4 +36,6 @@ public interface MemberSignInRecordConvert {
36 36  
37 37 PageResult<AppMemberSignInRecordRespVO> convertPage02(PageResult<MemberSignInRecordDO> pageResult);
38 38  
  39 + AppMemberSignInRecordRespVO coverRecordToAppRecordVo(MemberSignInRecordDO memberSignInRecordDO);
  40 +
39 41 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInConfigMapper.java
... ... @@ -18,7 +18,7 @@ public interface MemberSignInConfigMapper extends BaseMapperX&lt;MemberSignInConfig
18 18 return selectOne(MemberSignInConfigDO::getDay, day);
19 19 }
20 20  
21   - default List<MemberSignInConfigDO> selectListByStatus(Integer status) {
  21 + default List <MemberSignInConfigDO> selectListByStatus(Integer status) {
22 22 return selectList(MemberSignInConfigDO::getStatus, status);
23 23 }
24 24 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInRecordMapper.java
... ... @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSi
8 8 import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
9 9 import org.apache.ibatis.annotations.Mapper;
10 10  
  11 +import java.util.List;
11 12 import java.util.Set;
12 13  
13 14 /**
... ... @@ -33,4 +34,12 @@ public interface MemberSignInRecordMapper extends BaseMapperX&lt;MemberSignInRecord
33 34 .orderByDesc(MemberSignInRecordDO::getId));
34 35 }
35 36  
  37 +
  38 + //获取用户的签到记录列表信息,根据签到时间倒序
  39 + default List<MemberSignInRecordDO> selectListByUserId(Long userId){
  40 + return selectList(new LambdaQueryWrapperX <MemberSignInRecordDO>()
  41 + .eq(MemberSignInRecordDO::getUserId, userId)
  42 + .orderByDesc(MemberSignInRecordDO::getCreateTime));
  43 + }
  44 +
36 45 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordService.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.service.signin;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO;
  6 +import cn.iocoder.yudao.module.member.controller.app.signin.vo.AppMemberSignInSummaryRespVO;
6 7 import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
7 8  
8 9 /**
... ... @@ -29,4 +30,21 @@ public interface MemberSignInRecordService {
29 30 */
30 31 PageResult<MemberSignInRecordDO> getSignRecordPage(Long userId, PageParam pageParam);
31 32  
  33 + /**
  34 + * 创建签到记录
  35 + *
  36 + * @param userId 用户编号
  37 + * @return 签到记录
  38 + */
  39 + MemberSignInRecordDO createSignRecord(Long userId);
  40 +
  41 + /**
  42 + * 根据用户编号,获得个人签到统计信息
  43 + *
  44 + * @param userId 用户编号
  45 + * @return 个人签到统计信息
  46 + */
  47 + AppMemberSignInSummaryRespVO getSignInRecordSummary(Long userId);
  48 +
  49 +
32 50 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordServiceImpl.java
1 1 package cn.iocoder.yudao.module.member.service.signin;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 7 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
6 8 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
7 9 import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO;
  10 +import cn.iocoder.yudao.module.member.controller.app.signin.vo.AppMemberSignInSummaryRespVO;
  11 +import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
8 12 import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
  13 +import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInConfigMapper;
9 14 import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInRecordMapper;
  15 +import cn.iocoder.yudao.module.member.enums.ErrorCodeConstants;
10 16 import org.apache.commons.lang3.StringUtils;
11 17 import org.springframework.stereotype.Service;
12 18 import org.springframework.util.CollectionUtils;
13 19 import org.springframework.validation.annotation.Validated;
14 20  
15 21 import javax.annotation.Resource;
  22 +import java.time.LocalDate;
  23 +import java.time.temporal.ChronoUnit;
16 24 import java.util.List;
17 25 import java.util.Set;
18 26  
  27 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
19 28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
20 29  
21 30 /**
... ... @@ -28,17 +37,66 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
28 37 public class MemberSignInRecordServiceImpl implements MemberSignInRecordService {
29 38  
30 39 @Resource
31   - private MemberSignInRecordMapper memberSignInRecordMapper;
  40 + private MemberSignInRecordMapper signInRecordMapper;
  41 + @Resource
  42 + private MemberSignInConfigMapper signInConfigMapper;
32 43  
33 44 @Resource
34 45 private MemberUserApi memberUserApi;
35 46  
36 47 @Override
37   - public PageResult<MemberSignInRecordDO> getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) {
38   - // 根据用户昵称查询出用户 ids
39   - Set<Long> userIds = null;
  48 + public AppMemberSignInSummaryRespVO getSignInRecordSummary(Long userId) {
  49 + AppMemberSignInSummaryRespVO vo = new AppMemberSignInSummaryRespVO();
  50 + vo.setTotalDay(0);
  51 + vo.setContinuousDay(0);
  52 + vo.setTodaySignIn(false);
  53 + //获取用户签到的记录,按照天数倒序获取
  54 + List <MemberSignInRecordDO> signInRecordDOList = signInRecordMapper.selectListByUserId(userId);
  55 + // TODO @xiaqing:if 空的时候,直接 return;这样括号少,逻辑更简洁;
  56 + if(!CollectionUtils.isEmpty(signInRecordDOList)){
  57 + //设置总签到天数
  58 + vo.setTotalDay(signInRecordDOList.size()); // TODO @xiaqing:是不是不用读取 signInRecordDOList 所有的,而是 count下,然后另外再读取一条最后一条;
  59 + //判断当天是否有签到复用校验方法
  60 + // TODO @xiaqing:不要用异常实现逻辑;还是判断哈;
  61 + try {
  62 + validSignDay(signInRecordDOList.get(0));
  63 + vo.setTodaySignIn(false);
  64 + }catch (Exception e){
  65 + vo.setTodaySignIn(true);
  66 + }
  67 + //如果当天签到了则说明连续签到天数有意义,否则直接用默认值0
  68 + if(vo.getTodaySignIn()){
  69 + //下方计算连续签到从2天开始,此处直接设置一天连续签到
  70 + vo.setContinuousDay(1);
  71 + //判断连续签到天数
  72 + // TODO @xiaqing:这里逻辑,想想怎么在简化下,可读性可以在提升下哈;
  73 + for (int i = 1; i < signInRecordDOList.size(); i++) {
  74 + //前一天减1等于当前天数则说明连续,继续循环
  75 + LocalDate cur = signInRecordDOList.get(i).getCreateTime().toLocalDate();
  76 + LocalDate pre = signInRecordDOList.get(i-1).getCreateTime().toLocalDate();
  77 + if(1==daysBetween(cur,pre)){
  78 + vo.setContinuousDay(i+1);
  79 + }else{
  80 + break;
  81 + }
  82 + }
  83 + }
  84 +
  85 +
  86 + }
  87 + return vo;
  88 + }
  89 +
  90 + private long daysBetween(LocalDate date1,LocalDate date2){
  91 + return ChronoUnit.DAYS.between(date1, date2);
  92 + }
  93 +
  94 + @Override
  95 + public PageResult <MemberSignInRecordDO> getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) {
  96 + // 根据用户昵称查询出用户ids
  97 + Set <Long> userIds = null;
40 98 if (StringUtils.isNotBlank(pageReqVO.getNickname())) {
41   - List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
  99 + List <MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
42 100 // 如果查询用户结果为空直接返回无需继续查询
43 101 if (CollectionUtils.isEmpty(users)) {
44 102 return PageResult.empty();
... ... @@ -46,12 +104,72 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
46 104 userIds = convertSet(users, MemberUserRespDTO::getId);
47 105 }
48 106 // 分页查询
49   - return memberSignInRecordMapper.selectPage(pageReqVO, userIds);
  107 + return signInRecordMapper.selectPage(pageReqVO, userIds);
50 108 }
51 109  
52 110 @Override
53 111 public PageResult<MemberSignInRecordDO> getSignRecordPage(Long userId, PageParam pageParam) {
54   - return memberSignInRecordMapper.selectPage(userId, pageParam);
  112 + return signInRecordMapper.selectPage(userId, pageParam);
  113 + }
  114 +
  115 + @Override
  116 + public MemberSignInRecordDO createSignRecord(Long userId) {
  117 + // 获取当前用户签到的最大天数
  118 + // TODO @xiaqing:db 操作,dou封装到 mapper 中;
  119 + // TODO @xiaqing:maxSignDay,是不是变量叫 lastRecord 会更容易理解哈;
  120 + MemberSignInRecordDO maxSignDay = signInRecordMapper.selectOne(new LambdaQueryWrapperX <MemberSignInRecordDO>()
  121 + .eq(MemberSignInRecordDO::getUserId, userId)
  122 + .orderByDesc(MemberSignInRecordDO::getDay)
  123 + .last("limit 1"));
  124 + // 判断是否重复签到
  125 + validSignDay(maxSignDay);
  126 +
  127 + // TODO @xiaqing:可以使用 // 进行注释
  128 + /**1.查询出当前签到的天数**/
  129 + MemberSignInRecordDO sign = new MemberSignInRecordDO().setUserId(userId); // TODO @xiaqing:应该使用 record 变量,会更合适
  130 + sign.setDay(1); // 设置签到初始化天数
  131 + sign.setPoint(0); // 设置签到分数默认为 0
  132 + // 如果不为空则修改当前签到对应的天数
  133 + // TODO @xiaqing:应该是要判断连续哈,就是昨天;
  134 + if (maxSignDay != null) {
  135 + sign.setDay(maxSignDay.getDay() + 1);
  136 + }
  137 + /**2.获取签到对应的分数**/
  138 + // 获取所有的签到规则,按照天数排序,只获取启用的 TODO @xiaqing:不要使用 signInConfigMapper 直接查询,而是要通过 SigninConfigService;
  139 + List <MemberSignInConfigDO> configDOList = signInConfigMapper.selectList(new LambdaQueryWrapperX <MemberSignInConfigDO>()
  140 + .eq(MemberSignInConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
  141 + .orderByAsc(MemberSignInConfigDO::getDay));
  142 + // 如果签到的天数大于最大启用的规则天数,直接给最大签到的分数
  143 + // TODO @xiaqing:超过最大配置的天数,应该直接重置到第一天哈;
  144 + MemberSignInConfigDO lastConfig = configDOList.get(configDOList.size() - 1);
  145 + if (sign.getDay() > lastConfig.getDay()) {
  146 + sign.setPoint(lastConfig.getPoint());
  147 + } else {
  148 + configDOList.forEach(el -> {
  149 + // 循环匹配对应天数,设置对应分数
  150 + // TODO @xiaqing:使用 equals;另外,这种不应该去遍历比较,从可读性来说,应该 CollUtil.findOne()
  151 + if (el.getDay() == sign.getDay()) {
  152 + sign.setPoint(el.getPoint());
  153 + }
  154 +
  155 + });
  156 + }
  157 +
  158 + // 3. 插入当前签到获取的分数
  159 + signInRecordMapper.insert(sign);
  160 + return sign;
  161 + }
  162 +
  163 + // TODO @xiaqing:校验使用 validate 动词哈;可以改成 validateSigned
  164 + private void validSignDay(MemberSignInRecordDO signInRecordDO) {
  165 + // TODO @xiaqing:代码格式:if () {} 要有括号哈
  166 + if (signInRecordDO == null)
  167 + return;
  168 + // TODO @xiaqing:可以直接使用 DateUtils.isToday()
  169 + LocalDate today = LocalDate.now();
  170 + if (today.equals(signInRecordDO.getCreateTime().toLocalDate())) {
  171 + throw exception(ErrorCodeConstants.SIGN_IN_RECORD_TODAY_EXISTS);
  172 + }
55 173 }
56 174  
57 175 }
... ...
yudao-server/src/main/resources/application-local.yaml
... ... @@ -56,7 +56,7 @@ spring:
56 56 # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
57 57 slave: # 模拟从库,可根据自己需要修改
58 58 name: ruoyi-vue-pro
59   - url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
  59 + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
60 60 # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
61 61 # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
62 62 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
... ...