Przeglądaj źródła

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

# Conflicts:
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillActivityController.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java
puhui999 1 rok temu
rodzic
commit
52848d6341
56 zmienionych plików z 989 dodań i 411 usunięć
  1. 90 75
      sql/mysql/brokerage.sql
  2. 1 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
  3. 8 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperX.java
  4. 3 3
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm
  5. 2 2
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
  6. 1 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java
  7. 4 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/BargainActivityController.java
  8. 10 10
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/AppBargainActivityController.java
  9. 13 11
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillActivityController.java
  10. 1 8
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillConfigController.java
  11. 6 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/bargain/BargainActivityConvert.java
  12. 4 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java
  13. 3 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/seckillactivity/SeckillActivityConvert.java
  14. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java
  15. 1 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
  16. 5 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java
  17. 6 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
  18. 1 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java
  19. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java
  20. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java
  21. 0 52
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApi.java
  22. 0 51
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/dto/BrokerageUserDTO.java
  23. 5 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  24. 3 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/MessageTemplateConstants.java
  25. 0 40
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageUserTypeEnum.java
  26. 0 33
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApiImpl.java
  27. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordBaseVO.java
  28. 2 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordPageReqVO.java
  29. 1 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/BrokerageUserController.java
  30. 4 6
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserPageReqVO.java
  31. 78 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/BrokerageWithdrawController.java
  32. 68 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawBaseVO.java
  33. 47 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawPageReqVO.java
  34. 23 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawRejectReqVO.java
  35. 25 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawRespVO.java
  36. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/record/BrokerageRecordConvert.java
  37. 0 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/user/BrokerageUserConvert.java
  38. 41 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/withdraw/BrokerageWithdrawConvert.java
  39. 4 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  40. 3 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/record/BrokerageRecordDO.java
  41. 8 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/user/BrokerageUserDO.java
  42. 98 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/withdraw/BrokerageWithdrawDO.java
  43. 2 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/record/BrokerageRecordMapper.java
  44. 11 32
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/BrokerageUserMapper.java
  45. 37 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/withdraw/BrokerageWithdrawMapper.java
  46. 9 9
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/BrokerageRecordServiceImpl.java
  47. 19 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserService.java
  48. 59 26
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImpl.java
  49. 40 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/withdraw/BrokerageWithdrawService.java
  50. 104 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/withdraw/BrokerageWithdrawServiceImpl.java
  51. 8 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  52. 2 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/bo/TradeBeforeOrderCreateReqBO.java
  53. 88 0
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/withdraw/BrokerageWithdrawServiceImplTest.java
  54. 1 0
      yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql
  55. 26 1
      yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql
  56. 7 7
      yudao-server/src/main/resources/application-local.yaml

+ 90 - 75
sql/mysql/brokerage.sql

@@ -2,41 +2,50 @@
 create table trade_config
 (
     id                           bigint auto_increment comment '自增主键' primary key,
-    brokerage_enabled            bit                                    default 1                 not null comment '是否启用分佣',
-    brokerage_enabled_condition  tinyint                                default 0                 not null comment '分佣模式:1-人人分销 2-指定分销',
-    brokerage_bind_mode          tinyint                                default 0                 not null comment '分销关系绑定模式: 1-没有推广人,2-新用户, 3-扫码覆盖',
-    brokerage_post_urls          varchar(2000)                          default ''                null comment '分销海报图地址数组',
-    brokerage_first_percent      int                                    default 0                 not null comment '一级返佣比例',
-    brokerage_second_percent     int                                    default 0                 not null comment '二级返佣比例',
-    brokerage_withdraw_min_price int                                    default 0                 not null comment '用户提现最低金额',
-    brokerage_bank_names         varchar(200)                           default ''                not null comment '提现银行(字典类型=brokerage_bank_name)',
-    brokerage_frozen_days        int                                    default 7                 not null comment '佣金冻结时间(天)',
-    brokerage_withdraw_type      varchar(32)                            default '1,2,3,4'         not null comment '提现方式:1-钱包;2-银行卡;3-微信;4-支付宝',
-    creator                      varchar(64) collate utf8mb4_unicode_ci default ''                null comment '创建者',
-    create_time                  datetime                               default CURRENT_TIMESTAMP not null comment '创建时间',
-    updater                      varchar(64) collate utf8mb4_unicode_ci default ''                null comment '更新者',
-    update_time                  datetime                               default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
-    deleted                      bit                                    default b'0'              not null comment '是否删除',
-    tenant_id                    bigint                                 default 0                 not null comment '租户编号'
+    brokerage_enabled            bit           default 1                 not null comment '是否启用分佣',
+    brokerage_enabled_condition  tinyint       default 0                 not null comment '分佣模式:1-人人分销 2-指定分销',
+    brokerage_bind_mode          tinyint       default 0                 not null comment '分销关系绑定模式: 1-没有推广人,2-新用户, 3-扫码覆盖',
+    brokerage_post_urls          varchar(2000) default ''                null comment '分销海报图地址数组',
+    brokerage_first_percent      int           default 0                 not null comment '一级返佣比例',
+    brokerage_second_percent     int           default 0                 not null comment '二级返佣比例',
+    brokerage_withdraw_min_price int           default 0                 not null comment '用户提现最低金额',
+    brokerage_bank_names         varchar(200)  default ''                not null comment '提现银行(字典类型=brokerage_bank_name)',
+    brokerage_frozen_days        int           default 7                 not null comment '佣金冻结时间(天)',
+    brokerage_withdraw_type      varchar(32)   default '1,2,3,4'         not null comment '提现方式:1-钱包;2-银行卡;3-微信;4-支付宝',
+    creator                      varchar(64)   default ''                null comment '创建者',
+    create_time                  datetime      default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater                      varchar(64)   default ''                null comment '更新者',
+    update_time                  datetime      default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+    deleted                      bit           default b'0'              not null comment '是否删除',
+    tenant_id                    bigint        default 0                 not null comment '租户编号'
 ) comment '交易中心配置';
 
+# alter table trade_brokerage_user
+#     add level int not null default 1 comment '等级' after frozen_price;
+# alter table trade_brokerage_user
+#     add path varchar(2000) null comment '路径' after level;
+
+
 -- 增加分销用户扩展表
 create table trade_brokerage_user
 (
     id                bigint auto_increment comment '用户编号' primary key,
-    bind_user_id      bigint                                                           null comment '推广员编号',
-    bind_user_time    datetime                                                         null comment '推广员绑定时间',
-    brokerage_enabled bit                                    default 1                 not null comment '是否成为推广员',
-    brokerage_time    datetime                                                         null comment '成为分销员时间',
-    price             int                                    default 0                 not null comment '可用佣金',
-    frozen_price      int                                    default 0                 not null comment '冻结佣金',
-    creator           varchar(64) collate utf8mb4_unicode_ci default ''                null comment '创建者',
-    create_time       datetime                               default CURRENT_TIMESTAMP not null comment '创建时间',
-    updater           varchar(64) collate utf8mb4_unicode_ci default ''                null comment '更新者',
-    update_time       datetime                               default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
-    deleted           bit                                    default b'0'              not null comment '是否删除',
-    tenant_id         bigint                                 default 0                 not null comment '租户编号'
-) comment '分销用户';
+    bind_user_id      bigint                                null comment '推广员编号',
+    bind_user_time    datetime                              null comment '推广员绑定时间',
+    brokerage_enabled bit         default 1                 not null comment '是否成为推广员',
+    brokerage_time    datetime                              null comment '成为分销员时间',
+    price             int         default 0                 not null comment '可用佣金',
+    frozen_price      int         default 0                 not null comment '冻结佣金',
+    level             int         default 1                 not null comment '等级',
+    path              varchar(2000)                         null comment '路径',
+    creator           varchar(64) default ''                null comment '创建者',
+    create_time       datetime    default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater           varchar(64) default ''                null comment '更新者',
+    update_time       datetime    default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+    deleted           bit         default b'0'              not null comment '是否删除',
+    tenant_id         bigint      default 0                 not null comment '租户编号'
+)
+    comment '分销用户';
 
 create index idx_invite_user_id on trade_brokerage_user (bind_user_id) comment '推广员编号';
 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 '是
 
 create table trade_brokerage_record
 (
-    id               int auto_increment comment '编号'
+    id                int auto_increment comment '编号'
         primary key,
-    user_id          bigint                                                           not null comment '用户编号',
-    biz_id           varchar(64)                            default ''                not null comment '业务编号',
-    biz_type         tinyint                                default 0                 not null comment '业务类型:1-订单,2-提现',
-    title            varchar(64)                            default ''                not null comment '标题',
-    price            int                                    default 0                 not null comment '金额',
-    total_price      int                                    default 0                 not null comment '当前总佣金',
-    description      varchar(500)                           default ''                not null comment '说明',
-    status           tinyint                                default 0                 not null comment '状态:0-待结算,1-已结算,2-已取消',
-    frozen_days      int                                    default 0                 not null comment '冻结时间(天)',
-    unfreeze_time    datetime                                                         null comment '解冻时间',
-    source_user_type tinyint                                                          not null comment '来源用户类型:1-一级推广用户,2-二级推广用户',
-    source_user_id   bigint                                                           not null comment '来源用户编号',
-    creator          varchar(64) collate utf8mb4_general_ci default ''                null comment '创建者',
-    create_time      datetime                               default CURRENT_TIMESTAMP not null comment '创建时间',
-    updater          varchar(64) collate utf8mb4_general_ci default ''                null comment '更新者',
-    update_time      datetime                               default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
-    deleted          bit                                    default b'0'              not null comment '是否删除',
-    tenant_id        bigint                                 default 0                 not null comment '租户编号'
+    user_id           bigint                                 not null comment '用户编号',
+    biz_id            varchar(64)  default ''                not null comment '业务编号',
+    biz_type          tinyint      default 0                 not null comment '业务类型:1-订单,2-提现',
+    title             varchar(64)  default ''                not null comment '标题',
+    price             int          default 0                 not null comment '金额',
+    total_price       int          default 0                 not null comment '当前总佣金',
+    description       varchar(500) default ''                not null comment '说明',
+    status            tinyint      default 0                 not null comment '状态:0-待结算,1-已结算,2-已取消',
+    frozen_days       int          default 0                 not null comment '冻结时间(天)',
+    unfreeze_time     datetime                               null comment '解冻时间',
+    source_user_level int                                    not null comment '来源用户等级',
+    source_user_id    bigint                                 not null comment '来源用户编号',
+    creator           varchar(64)  default ''                null comment '创建者',
+    create_time       datetime     default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater           varchar(64)  default ''                null comment '更新者',
+    update_time       datetime     default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+    deleted           bit          default b'0'              not null comment '是否删除',
+    tenant_id         bigint       default 0                 not null comment '租户编号'
 )
     comment '佣金记录';
 
@@ -76,26 +85,26 @@ create table trade_brokerage_withdraw
 (
     id                  int auto_increment comment '编号'
         primary key,
-    user_id             bigint                                                           not null comment '用户编号',
-    price               int                                    default 0                 not null comment '提现金额',
-    fee_price           int                                    default 0                 not null comment '提现手续费',
-    total_price         int                                    default 0                 not null comment '当前总佣金',
-    type                tinyint                                default 0                 not null comment '提现类型:1-钱包;2-银行卡;3-微信;4-支付宝',
-    name                varchar(64)                                                      null comment '真实姓名',
-    account_no          varchar(64)                                                      null comment '账号',
-    bank_name           varchar(100)                                                     null comment '银行名称',
-    bank_address        varchar(200)                                                     null comment '开户地址',
-    account_qr_code_url varchar(512)                                                     null comment '收款码',
-    status              tinyint(2)                             default 0                 not null comment '状态:0-审核中,10-审核通过 20-审核不通过;预留:11 - 提现成功;21-提现失败',
-    audit_reason        varchar(128)                                                     null comment '审核驳回原因',
-    audit_time          datetime                                                         null comment '审核时间',
-    remark              varchar(500)                                                     null comment '备注',
-    creator             varchar(64) collate utf8mb4_general_ci default ''                null comment '创建者',
-    create_time         datetime                               default CURRENT_TIMESTAMP not null comment '创建时间',
-    updater             varchar(64) collate utf8mb4_general_ci default ''                null comment '更新者',
-    update_time         datetime                               default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
-    deleted             bit                                    default b'0'              not null comment '是否删除',
-    tenant_id           bigint                                 default 0                 not null comment '租户编号'
+    user_id             bigint                                not null comment '用户编号',
+    price               int         default 0                 not null comment '提现金额',
+    fee_price           int         default 0                 not null comment '提现手续费',
+    total_price         int         default 0                 not null comment '当前总佣金',
+    type                tinyint     default 0                 not null comment '提现类型:1-钱包;2-银行卡;3-微信;4-支付宝',
+    name                varchar(64)                           null comment '真实姓名',
+    account_no          varchar(64)                           null comment '账号',
+    bank_name           varchar(100)                          null comment '银行名称',
+    bank_address        varchar(200)                          null comment '开户地址',
+    account_qr_code_url varchar(512)                          null comment '收款码',
+    status              tinyint(2)  default 0                 not null comment '状态:0-审核中,10-审核通过 20-审核不通过;预留:11 - 提现成功;21-提现失败',
+    audit_reason        varchar(128)                          null comment '审核驳回原因',
+    audit_time          datetime                              null comment '审核时间',
+    remark              varchar(500)                          null comment '备注',
+    creator             varchar(64) default ''                null comment '创建者',
+    create_time         datetime    default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater             varchar(64) default ''                null comment '更新者',
+    update_time         datetime    default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+    deleted             bit         default b'0'              not null comment '是否删除',
+    tenant_id           bigint      default 0                 not null comment '租户编号'
 )
     comment '佣金提现';
 
@@ -139,12 +148,12 @@ values ('brokerage_record_status', '待结算', 0, 0),
 
 insert into system_dict_type(type, name)
 values ('brokerage_withdraw_status', '佣金提现状态');
-insert into system_dict_data(dict_type, label, value, sort)
-values ('brokerage_withdraw_status', '审核中', 0, 0),
-       ('brokerage_withdraw_status', '审核通过', 10, 10),
-       ('brokerage_withdraw_status', '提现成功', 11, 11),
-       ('brokerage_withdraw_status', '审核不通过', 20, 20),
-       ('brokerage_withdraw_status', '提现失败', 21, 21);
+insert into system_dict_data(dict_type, label, value, sort, color_type)
+values ('brokerage_withdraw_status', '审核中', 0, 0, ''),
+       ('brokerage_withdraw_status', '审核通过', 10, 10, 'success'),
+       ('brokerage_withdraw_status', '提现成功', 11, 11, 'success'),
+       ('brokerage_withdraw_status', '审核不通过', 20, 20, 'danger'),
+       ('brokerage_withdraw_status', '提现失败', 21, 21, 'danger');
 
 insert into system_dict_type(type, name)
 values ('brokerage_bank_name', '佣金提现银行');
@@ -220,4 +229,10 @@ SELECT @parentId := LAST_INSERT_ID();
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
 VALUES ('佣金提现查询', 'trade:brokerage-withdraw:query', 3, 1, @parentId, '', '', '', 0);
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('佣金提现审核', 'trade:brokerage-withdraw:audit', 3, 2, @parentId, '', '', '', 0);
+VALUES ('佣金提现审核', 'trade:brokerage-withdraw:audit', 3, 2, @parentId, '', '', '', 0);
+
+-- 站内信模板
+INSERT INTO `ruoyi-vue-pro`.system_notify_template (name, code, nickname, content, type, params, status)
+VALUES
+    ('佣金提现(审核通过)', 'brokerage_withdraw_audit_approve', 'system', '您在{createTime}提现¥{price}元的申请已通过审核', 2, '["createTime","price"]', 0),
+    ('佣金提现(审核不通过)', 'brokerage_withdraw_audit_reject', 'system', '您在{createTime}提现¥{price}元的申请未通过审核,原因:{reason}', 2, '["createTime","price","reason"]', 0);

+ 1 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java

@@ -50,6 +50,7 @@ public class LocalDateTimeUtils {
      * @return 指定时间
      */
     public static LocalDateTime buildTime(String timeStr) {
+        // TODO @puhui999:这个方法的实现,和 LocalDateTimeUtil.parse() 的差异点是啥呀
         return LocalDateTime.of(LocalDate.now(), LocalTime.parse(timeStr));
     }
 

+ 8 - 0
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> {
         return betweenIfPresent(column, val1, val2);
     }
 
+    // TODO @疯狂:这个是 mysql 独有的,不好做成通用的哈。如果多层级,有没可能先查询一个层级,再查询一个层级;形成 set 后,直接去 in?
+    public LambdaQueryWrapperX<T> findInSetIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.apply("FIND_IN_SET({0}, " + columnToString(column) + ")", val);
+        }
+        return this;
+    }
+
     // ========== 重写父类方法,方便链式调用 ==========
 
     @Override

+ 3 - 3
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
 
     @Override
     public ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id) {
-        if (CollUtil.isEmpty(ids)) {
-            return ListUtil.empty();
-        }
         return ${classNameVar}Mapper.selectById(id);
     }
 
     @Override
     public List<${table.className}DO> get${simpleClassName}List(Collection<${primaryColumn.javaType}> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return ListUtil.empty();
+        }
         return ${classNameVar}Mapper.selectBatchIds(ids);
     }
 

+ 2 - 2
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java

@@ -31,7 +31,7 @@ public interface CombinationRecordApi {
     boolean isCombinationRecordSuccess(Long userId, Long orderId);
 
     /**
-     * 更新拼团状态为 成功
+     * 更新拼团状态为【成功】
      *
      * @param userId  用户编号
      * @param orderId 订单编号
@@ -39,7 +39,7 @@ public interface CombinationRecordApi {
     void updateRecordStatusToSuccess(Long userId, Long orderId);
 
     /**
-     * 更新拼团状态为 失败
+     * 更新拼团状态为【失败】
      *
      * @param userId  用户编号
      * @param orderId 订单编号

+ 1 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java

@@ -7,6 +7,7 @@ package cn.iocoder.yudao.module.promotion.api.seckill;
  */
 public interface SeckillActivityApi {
 
+    // TODO @puhui999:activityId 改成 id 好点哈;
     /**
      * 更新秒杀库存
      *

+ 4 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/BargainActivityController.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.promotion.controller.admin.bargain;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityCreateReqVO;
@@ -25,6 +24,7 @@ import javax.validation.Valid;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 
 @Tag(name = "管理后台 - 砍价活动")
 @RestController
@@ -79,7 +79,9 @@ public class BargainActivityController {
         if (CollUtil.isEmpty(pageResult.getList())) {
             return success(PageResult.empty(pageResult.getTotal()));
         }
-        List<ProductSpuRespDTO> spuList = spuApi.getSpuList(CollectionUtils.convertList(pageResult.getList(), BargainActivityDO::getSpuId));
+
+        // 拼接数据
+        List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(pageResult.getList(), BargainActivityDO::getSpuId));
         return success(BargainActivityConvert.INSTANCE.convertPage(pageResult, spuList));
     }
 

+ 10 - 10
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/AppBargainActivityController.java

@@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.app.bargain.vo.activity.AppBargainActivityDetailRespVO;
@@ -25,6 +24,7 @@ import javax.annotation.Resource;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 
 @Tag(name = "用户 App - 砍价活动")
 @RestController
@@ -39,27 +39,27 @@ public class AppBargainActivityController {
     @GetMapping("/page")
     @Operation(summary = "获得砍价活动分页")
     public CommonResult<PageResult<AppBargainActivityRespVO>> getBargainActivityPage(PageParam pageReqVO) {
-        PageResult<BargainActivityDO> result = bargainActivityService.getBargainActivityAppPage(pageReqVO);
+        PageResult<BargainActivityDO> result = bargainActivityService.getBargainActivityPageForApp(pageReqVO);
         if (CollUtil.isEmpty(result.getList())) {
             return success(PageResult.empty(result.getTotal()));
         }
-
-        List<ProductSpuRespDTO> spuList = spuApi.getSpuList(CollectionUtils.convertList(result.getList(), BargainActivityDO::getSpuId));
+        // 拼接数据
+        List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(result.getList(), BargainActivityDO::getSpuId));
         return success(BargainActivityConvert.INSTANCE.convertAppPage(result, spuList));
     }
 
+    // TODO 芋艿:增加 Spring Cache
     @GetMapping("/list")
     @Operation(summary = "获得砍价活动列表", description = "用于小程序首页")
     @Parameter(name = "count", description = "需要展示的数量", example = "6")
     public CommonResult<List<AppBargainActivityRespVO>> getBargainActivityList(
             @RequestParam(name = "count", defaultValue = "6") Integer count) {
-        List<BargainActivityDO> list = bargainActivityService.getBargainActivityAppList(count);
+        List<BargainActivityDO> list = bargainActivityService.getBargainActivityListForApp(count);
         if (CollUtil.isEmpty(list)) {
             return success(BargainActivityConvert.INSTANCE.convertAppList(list));
         }
-
-        List<ProductSpuRespDTO> spuList = spuApi.getSpuList(CollectionUtils.convertList(list, BargainActivityDO::getSpuId));
-        // TODO 芋艿:增加 Spring Cache
+        // 拼接数据
+        List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(list, BargainActivityDO::getSpuId));
         return success(BargainActivityConvert.INSTANCE.convertAppList(list, spuList));
     }
 
@@ -71,9 +71,9 @@ public class AppBargainActivityController {
         if (activity == null) {
             return success(null);
         }
-
+        // 拼接数据
         ProductSpuRespDTO spu = spuApi.getSpu(activity.getSpuId());
-        return success(BargainActivityConvert.INSTANCE.convert1(activity, spu));
+        return success(BargainActivityConvert.INSTANCE.convert(activity, spu));
     }
 
 }

+ 13 - 11
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillActivityController.java

@@ -53,19 +53,20 @@ public class AppSeckillActivityController {
     @GetMapping("/get-now")
     @Operation(summary = "获得当前秒杀活动", description = "获取当前正在进行的活动,提供给首页使用")
     public CommonResult<AppSeckillActivityNowRespVO> getNowSeckillActivity() {
-        // 1、获取当前时间处在哪个秒杀阶段
+        // 1. 获取当前时间处在哪个秒杀阶段
+        // TODO @puhui999:可以考虑在 service 写个方法;这样 controller 不用关注过多逻辑
         List<SeckillConfigDO> configList = configService.getSeckillConfigList();
         SeckillConfigDO filteredConfig = findFirst(configList, config -> ObjectUtil.equal(config.getStatus(),
                 CommonStatusEnum.ENABLE.getStatus()) && isBetween(config.getStartTime(), config.getEndTime()));
-        // 1、1 时段不存在直接返回 null
-        if (filteredConfig == null) {
+        if (filteredConfig == null) { // 时段不存在直接返回 null
             return success(null);
         }
 
-        // 2、查询满足当前阶段的活动
+        // 2. 查询满足当前阶段的活动
+        // TODO @puhui999:最好直接返回开启的;不多查询数据
         List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByConfigIds(Arrays.asList(filteredConfig.getId()));
         List<SeckillActivityDO> filteredList = filterList(activityList, item -> ObjectUtil.equal(item.getStatus(), CommonStatusEnum.ENABLE.getStatus()));
-        // 2、1 获取 spu 信息
+        // 3 获取 spu 信息
         List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(filteredList, SeckillActivityDO::getSpuId));
         // TODO 芋艿:需要增加 spring cache
         return success(SeckillActivityConvert.INSTANCE.convert(filteredConfig, filteredList, spuList));
@@ -74,9 +75,10 @@ public class AppSeckillActivityController {
     @GetMapping("/page")
     @Operation(summary = "获得秒杀活动分页")
     public CommonResult<PageResult<AppSeckillActivityRespVO>> getSeckillActivityPage(AppSeckillActivityPageReqVO pageReqVO) {
-        // 1查询满足当前阶段的活动
+        // 1. 查询满足当前阶段的活动
         PageResult<SeckillActivityDO> pageResult = activityService.getSeckillActivityAppPageByConfigId(pageReqVO);
-        // 1、1 获取 spu 信息
+
+        // 2. 拼接数据
         List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(pageResult.getList(), SeckillActivityDO::getSpuId));
         return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, spuList));
     }
@@ -86,15 +88,15 @@ public class AppSeckillActivityController {
     @Parameter(name = "id", description = "活动编号", required = true, example = "1024")
     public CommonResult<AppSeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
         // 1、获取当前时间处在哪个秒杀阶段
+        // TODO puhui999:这里,和 58 行是雷同的
         List<SeckillConfigDO> configList = configService.getSeckillConfigList();
         SeckillConfigDO filteredConfig = findFirst(configList, config -> ObjectUtil.equal(config.getStatus(),
                 CommonStatusEnum.ENABLE.getStatus()) && isBetween(config.getStartTime(), config.getEndTime()));
-        // 1、1 时段不存在直接返回 null
-        if (filteredConfig == null) {
+        if (filteredConfig == null) { // 时段不存在直接返回 null
             return success(null);
         }
 
-        // 2获取活动
+        // 2. 获取活动
         SeckillActivityDO seckillActivity = activityService.getSeckillActivity(id);
         if (seckillActivity == null) {
             return success(null);
@@ -103,7 +105,7 @@ public class AppSeckillActivityController {
             throw exception(SECKILL_ACTIVITY_APP_STATUS_CLOSED);
         }
 
-        // 3、获取活动商品
+        // 3. 拼接数据
         List<SeckillProductDO> products = activityService.getSeckillProductListByActivityId(seckillActivity.getId());
         return success(SeckillActivityConvert.INSTANCE.convert3(seckillActivity, products, filteredConfig));
     }

+ 1 - 8
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillConfigController.java

@@ -32,19 +32,12 @@ public class AppSeckillConfigController {
     @Operation(summary = "获得秒杀时间段列表")
     public CommonResult<List<AppSeckillConfigRespVO>> getSeckillConfigList() {
         List<SeckillConfigDO> list = configService.getSeckillConfigListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        // TODO @puhui999:如果这种,不用判空也问题不大;
         if (CollectionUtil.isEmpty(list)) {
             return success(Collections.emptyList());
         }
 
         return success(SeckillConfigConvert.INSTANCE.convertList2(list));
-        //return success(Arrays.asList(
-        //        new AppSeckillConfigRespVO().setId(1L).setStartTime("00:00").setEndTime("09:59")
-        //                .setSliderPicUrls(Arrays.asList("https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg",
-        //                        "https://static.iocoder.cn/mall/132.jpeg")),
-        //        new AppSeckillConfigRespVO().setId(2L).setStartTime("10:00").setEndTime("12:59"),
-        //        new AppSeckillConfigRespVO().setId(2L).setStartTime("13:00").setEndTime("22:59"),
-        //        new AppSeckillConfigRespVO().setId(2L).setStartTime("23:00").setEndTime("23:59")
-        //));
     }
 
 }

+ 6 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/bargain/BargainActivityConvert.java

@@ -42,9 +42,11 @@ public interface BargainActivityConvert {
 
     default PageResult<BargainActivityRespVO> convertPage(PageResult<BargainActivityDO> page, List<ProductSpuRespDTO> spuList) {
         PageResult<BargainActivityRespVO> result = convertPage(page);
+        // 拼接关联属性
         Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
         List<BargainActivityRespVO> list = CollectionUtils.convertList(result.getList(), item -> {
             findAndThen(spuMap, item.getSpuId(), spu -> {
+                // TODO @puhui999:这里可以使用链式哈
                 item.setPicUrl(spu.getPicUrl());
                 item.setSpuName(spu.getName());
             });
@@ -56,7 +58,7 @@ public interface BargainActivityConvert {
 
     AppBargainActivityDetailRespVO convert1(BargainActivityDO bean);
 
-    default AppBargainActivityDetailRespVO convert1(BargainActivityDO bean, ProductSpuRespDTO spu) {
+    default AppBargainActivityDetailRespVO convert(BargainActivityDO bean, ProductSpuRespDTO spu) {
         AppBargainActivityDetailRespVO detail = convert1(bean);
         if (spu != null) {
             detail.setPicUrl(spu.getPicUrl());
@@ -70,9 +72,11 @@ public interface BargainActivityConvert {
 
     default PageResult<AppBargainActivityRespVO> convertAppPage(PageResult<BargainActivityDO> page, List<ProductSpuRespDTO> spuList) {
         PageResult<AppBargainActivityRespVO> result = convertAppPage(page);
+        // 拼接关联属性
         Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
         List<AppBargainActivityRespVO> list = CollectionUtils.convertList(result.getList(), item -> {
             findAndThen(spuMap, item.getSpuId(), spu -> {
+                // TODO @puhui999:这里可以使用链式哈
                 item.setPicUrl(spu.getPicUrl());
                 item.setMarketPrice(spu.getMarketPrice());
             });
@@ -89,6 +93,7 @@ public interface BargainActivityConvert {
         Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
         return CollectionUtils.convertList(activityList, item -> {
             findAndThen(spuMap, item.getSpuId(), spu -> {
+                // TODO @puhui999:这里可以使用链式哈
                 item.setPicUrl(spu.getPicUrl());
                 item.setMarketPrice(spu.getMarketPrice());
             });

+ 4 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java

@@ -97,8 +97,10 @@ public interface CombinationActivityConvert {
 
     CombinationRecordDO convert(CombinationRecordCreateReqDTO reqDTO);
 
-    default CombinationRecordDO convert1(CombinationRecordCreateReqDTO reqDTO, CombinationActivityDO activity, MemberUserRespDTO user,
-                                         ProductSpuRespDTO spu, ProductSkuRespDTO sku) {
+    default CombinationRecordDO convert(CombinationRecordCreateReqDTO reqDTO,
+                                        CombinationActivityDO activity, MemberUserRespDTO user,
+                                        ProductSpuRespDTO spu, ProductSkuRespDTO sku) {
+        // TODO @puhui999:搞成链式的 set;这样会更规整一点;
         CombinationRecordDO record = convert(reqDTO);
         record.setVirtualGroup(false);
         record.setExpireTime(record.getStartTime().plusHours(activity.getLimitDuration()));

+ 3 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/seckillactivity/SeckillActivityConvert.java

@@ -98,6 +98,7 @@ public interface SeckillActivityConvert {
         Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
         respVO.setActivities(CollectionUtils.convertList(convertList3(activityList), item -> {
             findAndThen(spuMap, item.getSpuId(), spu -> {
+                // TODO @puhui999:可以尝试链式 set 哈;
                 item.setPicUrl(spu.getPicUrl());
                 item.setMarketPrice(spu.getMarketPrice());
                 item.setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit()));
@@ -114,6 +115,7 @@ public interface SeckillActivityConvert {
         Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
         List<AppSeckillActivityRespVO> list = CollectionUtils.convertList(result.getList(), item -> {
             findAndThen(spuMap, item.getSpuId(), spu -> {
+                // TODO @puhui999:可以尝试链式 set 哈;
                 item.setPicUrl(spu.getPicUrl());
                 item.setMarketPrice(spu.getMarketPrice());
                 item.setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit()));
@@ -131,6 +133,7 @@ public interface SeckillActivityConvert {
     default AppSeckillActivityDetailRespVO convert3(SeckillActivityDO seckillActivity, List<SeckillProductDO> products, SeckillConfigDO filteredConfig) {
         AppSeckillActivityDetailRespVO respVO = convert2(seckillActivity);
         respVO.setProducts(convertList1(products));
+        // TODO @puhui999:可以尝试链式 set 哈;
         respVO.setStartTime(buildTime(filteredConfig.getStartTime()));
         respVO.setEndTime(buildTime(filteredConfig.getEndTime()));
         return respVO;

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java

@@ -41,7 +41,7 @@ public interface BargainActivityMapper extends BaseMapperX<BargainActivityDO> {
     default int updateActivityStock(Long id, int count) {
         return update(null, new LambdaUpdateWrapper<BargainActivityDO>()
                 .eq(BargainActivityDO::getId, id)
-                .gt(BargainActivityDO::getStock, count)
+                .ge(BargainActivityDO::getStock, count)
                 .setSql("stock = stock - " + count));
     }
 

+ 1 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java

@@ -52,6 +52,7 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
     default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) {
         return selectPage(pageReqVO, new LambdaQueryWrapperX<SeckillActivityDO>()
                 .eqIfPresent(SeckillActivityDO::getStatus, status)
+                // TODO 芋艿:对 find in set 的想法;
                 .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0"));
     }
 

+ 5 - 3
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java

@@ -63,20 +63,22 @@ public interface BargainActivityService {
      */
     PageResult<BargainActivityDO> getBargainActivityPage(BargainActivityPageReqVO pageReqVO);
 
+    // TODO @puhui999:这里可以改成进行中的活动;尽量避免专门为 app 定制,或者类似的名字哈;mapper 那也是
+
     /**
      * 获取 APP 端活动分页数据
      *
      * @param pageReqVO 分页请求
      * @return 砍价活动分页
      */
-    PageResult<BargainActivityDO> getBargainActivityAppPage(PageParam pageReqVO);
+    PageResult<BargainActivityDO> getBargainActivityPageForApp(PageParam pageReqVO);
 
     /**
      * 获取 APP 端活动展示数据
      *
      * @param count 需要的数量
-     * @return 活动列表
+     * @return 砍价活动分页
      */
-    List<BargainActivityDO> getBargainActivityAppList(Integer count);
+    List<BargainActivityDO> getBargainActivityListForApp(Integer count);
 
 }

+ 6 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java

@@ -142,18 +142,20 @@ public class BargainActivityServiceImpl implements BargainActivityService {
     }
 
     @Override
-    public PageResult<BargainActivityDO> getBargainActivityAppPage(PageParam pageReqVO) {
+    public PageResult<BargainActivityDO> getBargainActivityPageForApp(PageParam pageReqVO) {
         // 只查询进行中,且在时间范围内的
         return bargainActivityMapper.selectAppPage(pageReqVO, CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now());
-
     }
 
     @Override
-    public List<BargainActivityDO> getBargainActivityAppList(Integer count) {
+    public List<BargainActivityDO> getBargainActivityListForApp(Integer count) {
+        // TODO @puhui999:这种 default count 的逻辑,可以放到 controller 哈;然后可以使用 ObjectUtils.default 方法
         if (count == null) {
             count = 6;
         }
-        PageResult<BargainActivityDO> result = bargainActivityMapper.selectAppPage(new PageParam().setPageSize(count), CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now());
+        // TODO @puhui999:这种不要用 page;会浪费一次 count;
+        PageResult<BargainActivityDO> result = bargainActivityMapper.selectAppPage(new PageParam().setPageSize(count),
+                CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now());
         return result.getList();
     }
 

+ 1 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java

@@ -75,6 +75,7 @@ public interface CombinationActivityService {
 
     /**
      * 校验是否满足拼团条件
+     * 如果不满足,会抛出异常
      *
      * @param activityId 活动编号
      * @param userId     用户编号

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java

@@ -224,6 +224,7 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
             throw exception(COMBINATION_ACTIVITY_STATUS_DISABLE);
         }
         // 1.3 校验是否超出单次限购数量
+        // TODO puhui999:count > activity.getSingleLimitCount() 会更好理解点;
         if (activity.getSingleLimitCount() < count) {
             throw exception(COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED);
         }
@@ -243,7 +244,6 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
         if (activity.getTotalLimitCount() < countSum) {
             throw exception(COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED);
         }
-
     }
 
     @Override

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java

@@ -132,7 +132,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
         MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId());
         ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId());
         ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId());
-        recordMapper.insert(CombinationActivityConvert.INSTANCE.convert1(reqDTO, activity, user, spu, sku));
+        recordMapper.insert(CombinationActivityConvert.INSTANCE.convert(reqDTO, activity, user, spu, sku));
     }
 
     @Override

+ 0 - 52
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApi.java

@@ -1,52 +0,0 @@
-package cn.iocoder.yudao.module.trade.api.brokerage;
-
-import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
-import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
-
-import javax.validation.constraints.NotNull;
-import java.time.LocalDateTime;
-
-// TODO @疯狂:是不是不需要这个啦?
-/**
- * 分销 API 接口
- *
- * @author owen
- */
-public interface BrokerageApi {
-
-    /**
-     * 获得分销用户
-     *
-     * @param userId 用户编号
-     * @return 分销用户信息
-     */
-    BrokerageUserDTO getBrokerageUser(Long userId);
-
-    /**
-     * 【会员】绑定推广员
-     *
-     * @param userId       用户编号
-     * @param bindUserId   推广员编号
-     * @param registerTime 用户注册时间
-     * @return 是否绑定
-     */
-    default boolean bindUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime) {
-        // 注册时间在30秒内的,都算新用户
-        // TODO @疯狂:这个要不抽到 service 里哈?
-        boolean isNewUser = LocalDateTimeUtils.afterNow(registerTime.minusSeconds(30));
-        return bindUser(userId, bindUserId, isNewUser);
-    }
-
-    /**
-     * 绑定推广员
-     *
-     * @param userId     用户编号
-     * @param bindUserId 推广员编号
-     * @param isNewUser  是否为新用户
-     * @return 是否绑定
-     */
-    boolean bindUser(@NotNull(message = "用户编号不能为空") Long userId,
-                     @NotNull(message = "推广员编号不能为空") Long bindUserId,
-                     @NotNull Boolean isNewUser);
-
-}

+ 0 - 51
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/dto/BrokerageUserDTO.java

@@ -1,51 +0,0 @@
-package cn.iocoder.yudao.module.trade.api.brokerage.dto;
-
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-/**
- * 分销用户 DTO
- *
- * @author owen
- */
-@Data
-public class BrokerageUserDTO {
-
-    /**
-     * 用户编号
-     * <p>
-     * 对应 MemberUserDO 的 id 字段
-     */
-    private Long id;
-
-    /**
-     * 推广员编号
-     * <p>
-     * 关联 MemberUserDO 的 id 字段
-     */
-    private Long bindUserId;
-    /**
-     * 推广员绑定时间
-     */
-    private LocalDateTime bindUserTime;
-
-    /**
-     * 推广资格
-     */
-    private Boolean brokerageEnabled;
-    /**
-     * 成为分销员时间
-     */
-    private LocalDateTime brokerageTime;
-
-    /**
-     * 可用佣金
-     */
-    private Integer price;
-    /**
-     * 冻结佣金
-     */
-    private Integer frozenPrice;
-
-}

+ 5 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java

@@ -88,4 +88,9 @@ public interface ErrorCodeConstants {
     ErrorCode BROKERAGE_BIND_OVERRIDE = new ErrorCode(1011007006, "已绑定了推广人");
     ErrorCode BROKERAGE_BIND_LOOP = new ErrorCode(1011007007, "下级不能绑定自己的上级");
 
+
+    // ========== 分销提现 模块 1011008000 ==========
+    ErrorCode BROKERAGE_WITHDRAW_NOT_EXISTS = new ErrorCode(1011008000, "佣金提现记录不存在");
+    ErrorCode BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING = new ErrorCode(1011008001, "佣金提现记录状态不是审核中");
+
 }

+ 3 - 0
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 {
 
     String ORDER_DELIVERY = "order_delivery"; // 短信模版编号
 
+    String BROKERAGE_WITHDRAW_AUDIT_APPROVE = "brokerage_withdraw_audit_approve"; // 佣金提现(审核通过)
+    String BROKERAGE_WITHDRAW_AUDIT_REJECT = "brokerage_withdraw_audit_reject"; // 佣金提现(审核不通过)
+
 }

+ 0 - 40
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageUserTypeEnum.java

@@ -1,40 +0,0 @@
-package cn.iocoder.yudao.module.trade.enums.brokerage;
-
-import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.Arrays;
-
-// TODO @疯狂:是不是搞成层级,类似 level 这样?因为本质上,它是 1 级、2 级、3 级这样的关系哈
-/**
- * 分销用户类型枚举
- *
- * @author owen
- */
-@AllArgsConstructor
-@Getter
-public enum BrokerageUserTypeEnum implements IntArrayValuable {
-
-    ALL(0, "全部"),
-    FIRST(1, "一级推广人"),
-    SECOND(2, "二级推广人"),
-    ;
-
-    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageUserTypeEnum::getType).toArray();
-
-    /**
-     * 类型
-     */
-    private final Integer type;
-    /**
-     * 名字
-     */
-    private final String name;
-
-    @Override
-    public int[] array() {
-        return ARRAYS;
-    }
-
-}

+ 0 - 33
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/BrokerageApiImpl.java

@@ -1,33 +0,0 @@
-package cn.iocoder.yudao.module.trade.api.brokerage;
-
-import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
-import cn.iocoder.yudao.module.trade.convert.brokerage.user.BrokerageUserConvert;
-import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-
-/**
- * 分销 API 接口实现类
- *
- * @author owen
- */
-@Service
-@Validated
-public class BrokerageApiImpl implements BrokerageApi {
-
-    @Resource
-    private BrokerageUserService brokerageUserService;
-
-    @Override
-    public BrokerageUserDTO getBrokerageUser(Long userId) {
-        return BrokerageUserConvert.INSTANCE.convertDTO(brokerageUserService.getBrokerageUser(userId));
-    }
-
-    @Override
-    public boolean bindUser(Long userId, Long bindUserId, Boolean isNewUser) {
-        return brokerageUserService.bindBrokerageUser(userId, bindUserId, isNewUser);
-    }
-
-}

+ 2 - 2
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 {
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime unfreezeTime;
 
-    @Schema(description = "来源用户类型")
-    private Integer sourceUserType;
+    @Schema(description = "来源用户等级")
+    private Integer sourceUserLevel;
 
     @Schema(description = "来源用户编号")
     private Long sourceUserId;

+ 2 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/BrokerageRecordPageReqVO.java

@@ -1,8 +1,6 @@
 package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -32,8 +30,7 @@ public class BrokerageRecordPageReqVO extends PageParam {
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;
 
-    @Schema(description = "用户类型")
-    @InEnum(value = BrokerageUserTypeEnum.class, message = "用户类型必须是 {value}")
-    private Integer sourceUserType;
+    @Schema(description = "用户类型", example = "1")
+    private Integer sourceUserLevel;
 
 }

+ 1 - 2
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
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
 import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
 import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
@@ -96,7 +95,7 @@ public class BrokerageUserController {
         // 合计推广用户数量
         Map<Long, Long> brokerageUserCountMap = convertMap(userIds,
                 userId -> userId,
-                userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId, BrokerageUserTypeEnum.ALL));
+                userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId, null));
 
         // todo 合计提现
 

+ 4 - 6
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/BrokerageUserPageReqVO.java

@@ -1,8 +1,6 @@
 package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -22,18 +20,18 @@ public class BrokerageUserPageReqVO extends PageParam {
     @Schema(description = "推广员编号", example = "4587")
     private Long bindUserId;
 
-    @Schema(description = "推广资格")
+    @Schema(description = "推广资格", example = "true")
     private Boolean brokerageEnabled;
 
     @Schema(description = "创建时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;
 
-    @Schema(description = "用户类型")
-    @InEnum(value = BrokerageUserTypeEnum.class, message = "用户类型必须是 {value}")
-    private Integer userType;
+    @Schema(description = "用户等级", example = "1") // 注意,这了不是用户的会员等级,而是过滤推广的层级
+    private Integer level;
 
     @Schema(description = "绑定时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] bindUserTime;
+
 }

+ 78 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/BrokerageWithdrawController.java

@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawRejectReqVO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawPageReqVO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawRespVO;
+import cn.iocoder.yudao.module.trade.convert.brokerage.withdraw.BrokerageWithdrawConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
+import cn.iocoder.yudao.module.trade.service.brokerage.withdraw.BrokerageWithdrawService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "管理后台 - 佣金提现")
+@RestController
+@RequestMapping("/trade/brokerage-withdraw")
+@Validated
+public class BrokerageWithdrawController {
+
+    @Resource
+    private BrokerageWithdrawService brokerageWithdrawService;
+
+    @Resource
+    private MemberUserApi memberUserApi;
+
+    @PutMapping("/approve")
+    @Operation(summary = "通过申请")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')")
+    public CommonResult<Boolean> approveBrokerageWithdraw(@RequestParam("id") Integer id) {
+        brokerageWithdrawService.auditBrokerageWithdraw(id, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS, "");
+        return success(true);
+    }
+
+    @PutMapping("/reject")
+    @Operation(summary = "驳回申请")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')")
+    public CommonResult<Boolean> rejectBrokerageWithdraw(@Valid @RequestBody BrokerageWithdrawRejectReqVO reqVO) {
+        brokerageWithdrawService.auditBrokerageWithdraw(reqVO.getId(), BrokerageWithdrawStatusEnum.AUDIT_FAIL, reqVO.getAuditReason());
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得佣金提现")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:query')")
+    public CommonResult<BrokerageWithdrawRespVO> getBrokerageWithdraw(@RequestParam("id") Integer id) {
+        BrokerageWithdrawDO brokerageWithdraw = brokerageWithdrawService.getBrokerageWithdraw(id);
+        return success(BrokerageWithdrawConvert.INSTANCE.convert(brokerageWithdraw));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得佣金提现分页")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:query')")
+    public CommonResult<PageResult<BrokerageWithdrawRespVO>> getBrokerageWithdrawPage(@Valid BrokerageWithdrawPageReqVO pageVO) {
+        // 分页查询
+        PageResult<BrokerageWithdrawDO> pageResult = brokerageWithdrawService.getBrokerageWithdrawPage(pageVO);
+
+        // 拼接信息
+        Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(
+                convertSet(pageResult.getList(), BrokerageWithdrawDO::getUserId));
+        return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap));
+    }
+
+}

+ 68 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawBaseVO.java

@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * 佣金提现 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class BrokerageWithdrawBaseVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11436")
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+
+    @Schema(description = "提现金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "18781")
+    @NotNull(message = "提现金额不能为空")
+    private Integer price;
+
+    @Schema(description = "提现手续费", requiredMode = Schema.RequiredMode.REQUIRED, example = "11417")
+    @NotNull(message = "提现手续费不能为空")
+    private Integer feePrice;
+
+    @Schema(description = "当前总佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "18576")
+    @NotNull(message = "当前总佣金不能为空")
+    private Integer totalPrice;
+
+    @Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "提现类型不能为空")
+    private Integer type;
+
+    @Schema(description = "真实姓名", example = "赵六")
+    private String name;
+
+    @Schema(description = "账号", example = "88677912132")
+    private String accountNo;
+
+    @Schema(description = "银行名称", example = "1")
+    private String bankName;
+
+    @Schema(description = "开户地址", example = "海淀支行")
+    private String bankAddress;
+
+    @Schema(description = "收款码", example = "https://www.iocoder.cn")
+    private String accountQrCodeUrl;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @Schema(description = "审核驳回原因", example = "不对")
+    private String auditReason;
+
+    @Schema(description = "审核时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime auditTime;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+}

+ 47 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawPageReqVO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 佣金提现分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BrokerageWithdrawPageReqVO extends PageParam {
+
+    @Schema(description = "用户编号", example = "11436")
+    private Long userId;
+
+    @Schema(description = "提现类型", example = "1")
+    @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现类型必须是 {value}")
+    private Integer type;
+
+    @Schema(description = "真实姓名", example = "赵六")
+    private String name;
+
+    @Schema(description = "账号", example = "886779132")
+    private String accountNo;
+
+    @Schema(description = "银行名称", example = "1")
+    private String bankName;
+
+    @Schema(description = "状态", example = "1")
+    @InEnum(value = BrokerageWithdrawStatusEnum.class, message = "状态必须是 {value}")
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 23 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawRejectReqVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 驳回申请 Request VO")
+@Data
+@ToString(callSuper = true)
+public class BrokerageWithdrawRejectReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7161")
+    @NotNull(message = "编号不能为空")
+    private Integer id;
+
+    @Schema(description = "审核驳回原因", example = "不对")
+    @NotEmpty(message = "审核驳回原因不能为空")
+    private String auditReason;
+
+}

+ 25 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/withdraw/vo/BrokerageWithdrawRespVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 佣金提现 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BrokerageWithdrawRespVO extends BrokerageWithdrawBaseVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7161")
+    private Integer id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    private String userNickname;
+
+}

+ 2 - 2
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 {
 
     default BrokerageRecordDO convert(BrokerageUserDO user, BrokerageRecordBizTypeEnum bizType, String bizId,
                                       Integer brokerageFrozenDays, int brokeragePrice, LocalDateTime unfreezeTime,
-                                      String title, Long sourceUserId, Integer sourceUserType) {
+                                      String title, Long sourceUserId, Integer sourceUserLevel) {
         brokerageFrozenDays = ObjectUtil.defaultIfNull(brokerageFrozenDays, 0);
         // 不冻结时,佣金直接就是结算状态
         Integer status = brokerageFrozenDays > 0
@@ -47,7 +47,7 @@ public interface BrokerageRecordConvert {
                 .setTitle(title)
                 .setDescription(StrUtil.format(bizType.getDescription(), String.format("¥%.2f", brokeragePrice / 100d)))
                 .setStatus(status).setFrozenDays(brokerageFrozenDays).setUnfreezeTime(unfreezeTime)
-                .setSourceUserType(sourceUserType).setSourceUserId(sourceUserId);
+                .setSourceUserLevel(sourceUserLevel).setSourceUserId(sourceUserId);
     }
 
     default PageResult<BrokerageRecordRespVO> convertPage(PageResult<BrokerageRecordDO> pageResult, Map<Long, MemberUserRespDTO> userMap) {

+ 0 - 3
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;
 import cn.hutool.core.map.MapUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
-import cn.iocoder.yudao.module.trade.api.brokerage.dto.BrokerageUserDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserRespVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
@@ -56,6 +55,4 @@ public interface BrokerageUserConvert {
                 user -> target.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
         return target;
     }
-
-    BrokerageUserDTO convertDTO(BrokerageUserDO brokerageUser);
 }

+ 41 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/withdraw/BrokerageWithdrawConvert.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.trade.convert.brokerage.withdraw;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawRejectReqVO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawRespVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 佣金提现 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface BrokerageWithdrawConvert {
+
+    BrokerageWithdrawConvert INSTANCE = Mappers.getMapper(BrokerageWithdrawConvert.class);
+
+    BrokerageWithdrawDO convert(BrokerageWithdrawRejectReqVO bean);
+
+    BrokerageWithdrawRespVO convert(BrokerageWithdrawDO bean);
+
+    List<BrokerageWithdrawRespVO> convertList(List<BrokerageWithdrawDO> list);
+
+    PageResult<BrokerageWithdrawRespVO> convertPage(PageResult<BrokerageWithdrawDO> page);
+
+    default PageResult<BrokerageWithdrawRespVO> convertPage(PageResult<BrokerageWithdrawDO> pageResult, Map<Long, MemberUserRespDTO> userMap) {
+        PageResult<BrokerageWithdrawRespVO> result = convertPage(pageResult);
+        for (BrokerageWithdrawRespVO vo : result.getList()) {
+            vo.setUserNickname(Optional.ofNullable(userMap.get(vo.getUserId())).map(MemberUserRespDTO::getNickname).orElse(null));
+        }
+        return result;
+    }
+
+}

+ 4 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java

@@ -27,7 +27,6 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
@@ -274,10 +273,10 @@ public interface TradeOrderConvert {
 
     TradeOrderDO convert(TradeOrderRemarkReqVO reqVO);
 
-    default BrokerageAddReqBO convert(TradeOrderItemDO item, ProductSkuRespDTO sku) {
+    default BrokerageAddReqBO convert(MemberUserRespDTO user, TradeOrderItemDO item, ProductSkuRespDTO sku) {
         return new BrokerageAddReqBO().setBizId(String.valueOf(item.getId())).setSourceUserId(item.getUserId())
                 .setBasePrice(item.getPayPrice() * item.getCount())
-                .setTitle(BrokerageRecordBizTypeEnum.ORDER.getTitle()) // TODO @疯狂:标题类似:木晴冰雪成功购买云时代的JVM原理与实战;茫农成功购买深入拆解消息队列47讲
+                .setTitle(StrUtil.format("{}成功购买{}", user.getNickname(), item.getSpuName()))
                 .setFirstFixedPrice(sku.getFirstBrokeragePrice()).setSecondFixedPrice(sku.getSecondBrokeragePrice());
     }
 
@@ -292,6 +291,7 @@ public interface TradeOrderConvert {
             @Mapping(target = "userId", source = "userId"),
             @Mapping(target = "payPrice", source = "tradeOrderDO.payPrice"),
     })
-    TradeAfterOrderCreateReqBO convert(Long userId, AppTradeOrderCreateReqVO createReqVO, TradeOrderDO tradeOrderDO, TradeOrderItemDO orderItem);
+    TradeAfterOrderCreateReqBO convert(Long userId, AppTradeOrderCreateReqVO createReqVO,
+                                       TradeOrderDO tradeOrderDO, TradeOrderItemDO orderItem);
 
 }

+ 3 - 4
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;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -83,11 +82,11 @@ public class BrokerageRecordDO extends BaseDO {
     private LocalDateTime unfreezeTime;
 
     /**
-     * 来源用户类型
+     * 来源用户等级
      * <p>
-     * 枚举 {@link BrokerageUserTypeEnum},被推广用户和 {@link #userId} 的推广层级关系
+     * 被推广用户和 {@link #userId} 的推广层级关系
      */
-    private Integer sourceUserType;
+    private Integer sourceUserLevel;
     /**
      * 来源用户编号
      * <p>

+ 8 - 0
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 {
      */
     private Integer frozenPrice;
 
+    /**
+     * 等级
+     */
+    private Integer level;
+    /**
+     * 路径
+     */
+    private String path;
 }

+ 98 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/withdraw/BrokerageWithdrawDO.java

@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 佣金提现 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("trade_brokerage_withdraw")
+@KeySequence("trade_brokerage_withdraw_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BrokerageWithdrawDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Integer id;
+    /**
+     * 用户编号
+     *
+     * 关联 MemberUserDO 的 id 字段
+     */
+    private Long userId;
+
+    /**
+     * 提现金额,单位:分
+     */
+    private Integer price;
+    /**
+     * 提现手续费,单位:分
+     */
+    private Integer feePrice;
+    /**
+     * 当前总佣金,单位:分
+     */
+    private Integer totalPrice;
+    /**
+     * 提现类型
+     * <p>
+     * 枚举 {@link BrokerageWithdrawTypeEnum}
+     */
+    private Integer type;
+
+    /**
+     * 真实姓名
+     */
+    private String name;
+    /**
+     * 账号
+     */
+    private String accountNo;
+    /**
+     * 银行名称
+     */
+    private String bankName;
+    /**
+     * 开户地址
+     */
+    private String bankAddress;
+    /**
+     * 收款码
+     */
+    private String accountQrCodeUrl;
+    /**
+     * 状态
+     * <p>
+     * 枚举 {@link BrokerageWithdrawStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 审核驳回原因
+     */
+    private String auditReason;
+    /**
+     * 审核时间
+     */
+    private LocalDateTime auditTime;
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 2 - 4
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;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.BrokerageRecordPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.BrokerageRecordDO;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
@@ -24,14 +23,12 @@ import java.util.List;
 public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
 
     default PageResult<BrokerageRecordDO> selectPage(BrokerageRecordPageReqVO reqVO) {
-        boolean sourceUserTypeCondition = reqVO.getSourceUserType() != null &&
-                !BrokerageUserTypeEnum.ALL.getType().equals(reqVO.getSourceUserType());
         // 分页查询
         return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageRecordDO>()
                 .eqIfPresent(BrokerageRecordDO::getUserId, reqVO.getUserId())
                 .eqIfPresent(BrokerageRecordDO::getBizType, reqVO.getBizType())
                 .eqIfPresent(BrokerageRecordDO::getStatus, reqVO.getStatus())
-                .eq(sourceUserTypeCondition, BrokerageRecordDO::getSourceUserType, reqVO.getSourceUserType())
+                .eqIfPresent(BrokerageRecordDO::getSourceUserLevel, reqVO.getSourceUserLevel())
                 .betweenIfPresent(BrokerageRecordDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(BrokerageRecordDO::getId));
     }
@@ -59,4 +56,5 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
     UserBrokerageSummaryBO selectCountAndSumPriceByUserIdAndBizTypeAndStatus(@Param("userId") Long userId,
                                                                              @Param("bizType") Integer bizType,
                                                                              @Param("status") Integer status);
+
 }

+ 11 - 32
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/BrokerageUserMapper.java

@@ -1,17 +1,15 @@
 package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user;
 
 import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
 
 /**
  * 分销用户 Mapper
@@ -21,35 +19,16 @@ import org.apache.ibatis.annotations.Select;
 @Mapper
 public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
 
-    default PageResult<BrokerageUserDO> selectPage(BrokerageUserPageReqVO reqVO) {
+    default PageResult<BrokerageUserDO> selectPage(BrokerageUserPageReqVO reqVO, List<Integer> levels) {
         return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageUserDO>()
                 .eqIfPresent(BrokerageUserDO::getBrokerageEnabled, reqVO.getBrokerageEnabled())
                 .betweenIfPresent(BrokerageUserDO::getCreateTime, reqVO.getCreateTime())
                 .betweenIfPresent(BrokerageUserDO::getBindUserTime, reqVO.getBindUserTime())
-                .and(reqVO.getBindUserId() != null, w -> buildBindUserCondition(reqVO, w))
+                .findInSetIfPresent(BrokerageUserDO::getPath, reqVO.getBindUserId())
+                .inIfPresent(BrokerageUserDO::getLevel, levels)
                 .orderByDesc(BrokerageUserDO::getId));
     }
 
-    static void buildBindUserCondition(BrokerageUserPageReqVO reqVO, LambdaQueryWrapper<BrokerageUserDO> wrapper) {
-        if (BrokerageUserTypeEnum.FIRST.getType().equals(reqVO.getUserType())) {
-            buildFirstBindUserCondition(reqVO.getBindUserId(), wrapper);
-        } else if (BrokerageUserTypeEnum.SECOND.getType().equals(reqVO.getUserType())) {
-            buildSecondBindUserCondition(reqVO.getBindUserId(), wrapper);
-        } else {
-            // TODO @疯狂:要不要把这个逻辑,挪到 Service 里,算出子用户有哪些,然后 IN?
-            buildFirstBindUserCondition(reqVO.getBindUserId(), wrapper);
-            buildSecondBindUserCondition(reqVO.getBindUserId(), wrapper.or()); // 通过 or 实现多个条件
-        }
-    }
-
-    static void buildFirstBindUserCondition(Long bindUserId, LambdaQueryWrapper<BrokerageUserDO> wrapper) {
-        wrapper.eq(BrokerageUserDO::getBindUserId, bindUserId);
-    }
-
-    static void buildSecondBindUserCondition(Long bindUserId, LambdaQueryWrapper<BrokerageUserDO> wrapper) {
-        wrapper.inSql(BrokerageUserDO::getBindUserId, StrUtil.format("SELECT id FROM trade_brokerage_user WHERE bind_user_id = {}", bindUserId));
-    }
-
     /**
      * 更新用户可用佣金(增加)
      *
@@ -128,7 +107,8 @@ public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
     default void updateBindUserIdAndBindUserTimeToNull(Long id) {
         update(null, new LambdaUpdateWrapper<BrokerageUserDO>()
                 .eq(BrokerageUserDO::getId, id)
-                .set(BrokerageUserDO::getBindUserId, null).set(BrokerageUserDO::getBindUserTime, null));
+                .set(BrokerageUserDO::getBindUserId, null).set(BrokerageUserDO::getBindUserTime, null)
+                .set(BrokerageUserDO::getLevel, 1).set(BrokerageUserDO::getPath, ""));
     }
 
     default void updateEnabledFalseAndBrokerageTimeToNull(Long id) {
@@ -137,11 +117,10 @@ public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
                 .set(BrokerageUserDO::getBrokerageEnabled, false).set(BrokerageUserDO::getBrokerageTime, null));
     }
 
-    default Long selectCountByBindUserId(Long bindUserId) {
-        return selectCount(BrokerageUserDO::getBindUserId, bindUserId);
+    default Long selectCountByBindUserIdAndLevelIn(Long bindUserId, List<Integer> levels) {
+        return selectCount(new LambdaQueryWrapperX<BrokerageUserDO>()
+                .findInSetIfPresent(BrokerageUserDO::getPath, bindUserId)
+                .inIfPresent(BrokerageUserDO::getLevel, levels));
     }
 
-    @Select("SELECT COUNT(1) from trade_brokerage_user WHERE bind_user_id IN (SELECT id FROM trade_brokerage_user WHERE bind_user_id = #{bindUserId})")
-    Long selectCountByBindUserIdInBindUserId(Long bindUserId);
-
 }

+ 37 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/withdraw/BrokerageWithdrawMapper.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.withdraw;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 佣金提现 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface BrokerageWithdrawMapper extends BaseMapperX<BrokerageWithdrawDO> {
+
+    default PageResult<BrokerageWithdrawDO> selectPage(BrokerageWithdrawPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageWithdrawDO>()
+                .eqIfPresent(BrokerageWithdrawDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(BrokerageWithdrawDO::getType, reqVO.getType())
+                .likeIfPresent(BrokerageWithdrawDO::getName, reqVO.getName())
+                .eqIfPresent(BrokerageWithdrawDO::getAccountNo, reqVO.getAccountNo())
+                .likeIfPresent(BrokerageWithdrawDO::getBankName, reqVO.getBankName())
+                .eqIfPresent(BrokerageWithdrawDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(BrokerageWithdrawDO::getCreateTime, reqVO.getCreateTime())
+                .orderByAsc(BrokerageWithdrawDO::getStatus).orderByDesc(BrokerageWithdrawDO::getId));
+    }
+
+    default int updateByIdAndStatus(Integer id, Integer status, BrokerageWithdrawDO updateObj) {
+        return update(updateObj, new LambdaUpdateWrapper<BrokerageWithdrawDO>()
+                .eq(BrokerageWithdrawDO::getId, id)
+                .eq(BrokerageWithdrawDO::getStatus, status));
+    }
+
+}

+ 9 - 9
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;
 import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record.BrokerageRecordMapper;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
 import cn.iocoder.yudao.module.trade.service.brokerage.user.BrokerageUserService;
@@ -29,6 +28,7 @@ import javax.annotation.Resource;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * 佣金记录 Service 实现类
@@ -74,7 +74,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
         }
         // 1.2 计算一级分佣
         addBrokerage(firstUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageFirstPercent(),
-                bizType, BrokerageUserTypeEnum.FIRST);
+                bizType, 1);
 
         // 2.1 获得二级推广员
         if (firstUser.getBindUserId() == null) {
@@ -86,7 +86,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
         }
         // 2.2 计算二级分佣
         addBrokerage(secondUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageSecondPercent(),
-                bizType, BrokerageUserTypeEnum.SECOND);
+                bizType, 2);
     }
 
     @Override
@@ -142,10 +142,10 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
      * @param brokerageFrozenDays 冻结天数
      * @param brokeragePercent    佣金比例
      * @param bizType             业务类型
-     * @param sourceUserType      来源用户类型
+     * @param sourceUserLevel     来源用户等级
      */
     private void addBrokerage(BrokerageUserDO user, List<BrokerageAddReqBO> list, Integer brokerageFrozenDays,
-                              Integer brokeragePercent, BrokerageRecordBizTypeEnum bizType, BrokerageUserTypeEnum sourceUserType) {
+                              Integer brokeragePercent, BrokerageRecordBizTypeEnum bizType, Integer sourceUserLevel) {
         // 1.1 处理冻结时间
         LocalDateTime unfreezeTime = null;
         if (brokerageFrozenDays != null && brokerageFrozenDays > 0) {
@@ -157,12 +157,12 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
         for (BrokerageAddReqBO item : list) {
             // 计算金额
             Integer fixedPrice;
-            if (BrokerageUserTypeEnum.FIRST.equals(sourceUserType)) {
+            if (Objects.equals(sourceUserLevel, 1)) {
                 fixedPrice = item.getFirstFixedPrice();
-            } else if (BrokerageUserTypeEnum.SECOND.equals(sourceUserType)) {
+            } else if (Objects.equals(sourceUserLevel, 2)) {
                 fixedPrice = item.getSecondFixedPrice();
             } else {
-                throw new IllegalArgumentException(StrUtil.format("来源用户({}) 不合法", sourceUserType));
+                throw new IllegalArgumentException(StrUtil.format("用户等级({}) 不合法", sourceUserLevel));
             }
             int brokeragePrice = calculatePrice(item.getBasePrice(), brokeragePercent, fixedPrice);
             if (brokeragePrice <= 0) {
@@ -172,7 +172,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
             // 创建记录实体
             records.add(BrokerageRecordConvert.INSTANCE.convert(user, bizType, item.getBizId(),
                     brokerageFrozenDays, brokeragePrice, unfreezeTime, item.getTitle(),
-                    item.getSourceUserId(), sourceUserType.getType()));
+                    item.getSourceUserId(), sourceUserLevel));
         }
         if (CollUtil.isEmpty(records)) {
             return;

+ 19 - 3
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserService.java

@@ -1,10 +1,12 @@
 package cn.iocoder.yudao.module.trade.service.brokerage.user;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.BrokerageUserDO;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
 
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 
@@ -91,10 +93,24 @@ public interface BrokerageUserService {
      * 获得推广用户数量
      *
      * @param bindUserId 绑定的推广员编号
-     * @param userType   用户类型
+     * @param level      推广用户等级
      * @return 推广用户数量
      */
-    Long getBrokerageUserCountByBindUserId(Long bindUserId, BrokerageUserTypeEnum userType);
+    Long getBrokerageUserCountByBindUserId(Long bindUserId, Integer level);
+
+    /**
+     * 【会员】绑定推广员
+     *
+     * @param userId       用户编号
+     * @param bindUserId   推广员编号
+     * @param registerTime 用户注册时间
+     * @return 是否绑定
+     */
+    default boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime) {
+        // 注册时间在30秒内的,都算新用户
+        boolean isNewUser = LocalDateTimeUtils.afterNow(registerTime.minusSeconds(30));
+        return bindBrokerageUser(userId, bindUserId, isNewUser);
+    }
 
     /**
      * 【会员】绑定推广员

+ 59 - 26
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/BrokerageUserServiceImpl.java

@@ -1,7 +1,10 @@
 package cn.iocoder.yudao.module.trade.service.brokerage.user;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.BooleanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.BrokerageUserPageReqVO;
 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;
 import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user.BrokerageUserMapper;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageUserTypeEnum;
 import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.Collection;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
@@ -51,7 +50,8 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
 
     @Override
     public PageResult<BrokerageUserDO> getBrokerageUserPage(BrokerageUserPageReqVO pageReqVO) {
-        return brokerageUserMapper.selectPage(pageReqVO);
+        List<Integer> levels = buildUserQueryLevels(pageReqVO.getBindUserId(), pageReqVO.getLevel());
+        return brokerageUserMapper.selectPage(pageReqVO, levels);
     }
 
     @Override
@@ -66,10 +66,15 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
             return;
         }
 
+        // 绑定关系未发生变化
+        // TODO @疯狂:这个放到“情况一”之前,貌似也没关系?
+        if (Objects.equals(brokerageUser.getBindUserId(), bindUserId)) {
+            return;
+        }
+
         // 情况二:修改推广员
         validateCanBindUser(brokerageUser, bindUserId);
-        brokerageUserMapper.updateById(new BrokerageUserDO().setId(id)
-                .setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now()));
+        brokerageUserMapper.updateById(fillBindUserData(bindUserId, new BrokerageUserDO().setId(id)));
     }
 
     @Override
@@ -132,19 +137,12 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
     }
 
     @Override
-    public Long getBrokerageUserCountByBindUserId(Long bindUserId, BrokerageUserTypeEnum userType) {
-        switch (userType) {
-            case ALL: // TODO @疯狂:ALL 是不是不用搞个枚举,默认为空就是不过滤哈~
-                Long firstCount = brokerageUserMapper.selectCountByBindUserId(bindUserId);
-                Long secondCount = brokerageUserMapper.selectCountByBindUserIdInBindUserId(bindUserId);
-                return firstCount + secondCount;
-            case FIRST:
-                return brokerageUserMapper.selectCountByBindUserId(bindUserId);
-            case SECOND:
-                return brokerageUserMapper.selectCountByBindUserIdInBindUserId(bindUserId);
-            default:
-                return 0L;
+    public Long getBrokerageUserCountByBindUserId(Long bindUserId, Integer level) {
+        List<Integer> levels = buildUserQueryLevels(bindUserId, level);
+        if (CollUtil.isEmpty(levels)) {
+            return 0L;
         }
+        return brokerageUserMapper.selectCountByBindUserIdAndLevelIn(bindUserId, levels);
     }
 
     @Override
@@ -171,14 +169,30 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
                 brokerageUser.setBrokerageEnabled(true).setBrokerageTime(LocalDateTime.now());
             }
             brokerageUser.setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now());
-            brokerageUserMapper.insert(brokerageUser);
+            brokerageUserMapper.insert(fillBindUserData(bindUserId, brokerageUser));
         } else {
-            brokerageUserMapper.updateById(new BrokerageUserDO().setId(userId)
-                    .setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now()));
+            brokerageUserMapper.updateById(fillBindUserData(bindUserId, new BrokerageUserDO().setId(userId)));
         }
         return true;
     }
 
+    private BrokerageUserDO fillBindUserData(Long bindUserId, BrokerageUserDO brokerageUser) {
+        BrokerageUserDO bindUser = getBrokerageUser(bindUserId);
+
+        Integer bindUserLevel = 0;
+        String bindUserPath = "";
+        if (bindUser != null) {
+            bindUserLevel = ObjectUtil.defaultIfNull(bindUser.getLevel(), 0);
+            bindUserPath = bindUser.getPath();
+        }
+
+        String path = StrUtil.isEmpty(bindUserPath)
+                ? String.valueOf(bindUserId)
+                : String.format("%s,%s", bindUserPath, bindUserId);
+        return brokerageUser.setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now())
+                .setLevel(bindUserLevel + 1).setPath(path);
+    }
+
     @Override
     public Boolean getUserBrokerageEnabled(Long userId) {
         // 全局分销功能是否开启
@@ -231,11 +245,30 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
             throw exception(BROKERAGE_BIND_SELF);
         }
 
-        // TODO @疯狂:这块是不是一直查询到根节点,中间不允许出现自己;就是不能形成环。虽然目前是 2 级,但是未来可能会改多级; = = 环的话,就会存在问题哈
-        // A->B->A:下级不能绑定自己的上级,   A->B->C->A可以!!
-        if (Objects.equals(user.getId(), bindUser.getBindUserId())) {
+        // 下级不能绑定自己的上级
+        if (StrUtil.split(bindUser.getPath(), ",").contains(String.valueOf(user.getId()))) {
             throw exception(BROKERAGE_BIND_LOOP);
         }
     }
 
+    // TODO @芋艿:这个层级,要微信讨论下;
+    private List<Integer> buildUserQueryLevels(Long bindUserId, Integer level) {
+        List<Integer> levels = new ArrayList<>(2);
+
+        BrokerageUserDO bindUser = getBrokerageUser(bindUserId);
+        if (bindUser == null) {
+            return levels;
+        }
+
+        if (level == null) {
+            // 默认查两层
+            levels.add(bindUser.getLevel() + 1);
+            levels.add(bindUser.getLevel() + 2);
+        } else {
+            levels.add(bindUser.getLevel() + level);
+        }
+        return levels;
+
+    }
+
 }

+ 40 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/withdraw/BrokerageWithdrawService.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.withdraw;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
+
+/**
+ * 佣金提现 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface BrokerageWithdrawService {
+
+    /**
+     * 审核佣金提现
+     *
+     * @param id          佣金编号
+     * @param status      审核状态
+     * @param auditReason 驳回原因
+     */
+
+    void auditBrokerageWithdraw(Integer id, BrokerageWithdrawStatusEnum status, String auditReason);
+
+    /**
+     * 获得佣金提现
+     *
+     * @param id 编号
+     * @return 佣金提现
+     */
+    BrokerageWithdrawDO getBrokerageWithdraw(Integer id);
+
+    /**
+     * 获得佣金提现分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 佣金提现分页
+     */
+    PageResult<BrokerageWithdrawDO> getBrokerageWithdrawPage(BrokerageWithdrawPageReqVO pageReqVO);
+}

+ 104 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/withdraw/BrokerageWithdrawServiceImpl.java

@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.withdraw;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
+import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.withdraw.BrokerageWithdrawMapper;
+import cn.iocoder.yudao.module.trade.enums.MessageTemplateConstants;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_WITHDRAW_NOT_EXISTS;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING;
+
+/**
+ * 佣金提现 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
+
+    @Resource
+    private BrokerageWithdrawMapper brokerageWithdrawMapper;
+
+    @Resource
+    private BrokerageRecordService brokerageRecordService;
+
+    @Resource
+    private NotifyMessageSendApi notifyMessageSendApi;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void auditBrokerageWithdraw(Integer id, BrokerageWithdrawStatusEnum status, String auditReason) {
+        // 1.1 校验存在
+        BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
+        // 1.2 校验状态为审核中
+        if (ObjectUtil.notEqual(BrokerageWithdrawStatusEnum.AUDITING.getStatus(), withdraw.getStatus())) {
+            throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
+        }
+
+        // 2. 更新
+        BrokerageWithdrawDO updateObj = new BrokerageWithdrawDO()
+                .setStatus(status.getStatus())
+                .setAuditReason(auditReason)
+                .setAuditTime(LocalDateTime.now());
+        int rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDITING.getStatus(), updateObj);
+        if (rows == 0) {
+            throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
+        }
+
+        // 3. 驳回时需要退还用户佣金
+        String templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_APPROVE;
+        if (BrokerageWithdrawStatusEnum.AUDIT_FAIL.equals(status)) {
+            templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_REJECT;
+
+            // todo @owen
+//            brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW, withdraw.getPrice(), "");
+        }
+
+        // 4. 通知用户
+        Map<String, Object> templateParams = MapUtil.<String, Object>builder()
+                .put("createTime", LocalDateTimeUtil.formatNormal(withdraw.getCreateTime()))
+                .put("price", String.format("%.2f", withdraw.getPrice() / 100d))
+                .put("reason", withdraw.getAuditReason())
+                .build();
+        NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO()
+                .setUserId(withdraw.getUserId())
+                .setTemplateCode(templateCode).setTemplateParams(templateParams);
+        notifyMessageSendApi.sendSingleMessageToMember(reqDTO);
+    }
+
+    private BrokerageWithdrawDO validateBrokerageWithdrawExists(Integer id) {
+        BrokerageWithdrawDO withdraw = brokerageWithdrawMapper.selectById(id);
+        if (withdraw == null) {
+            throw exception(BROKERAGE_WITHDRAW_NOT_EXISTS);
+        }
+        return withdraw;
+    }
+
+    @Override
+    public BrokerageWithdrawDO getBrokerageWithdraw(Integer id) {
+        return brokerageWithdrawMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<BrokerageWithdrawDO> getBrokerageWithdrawPage(BrokerageWithdrawPageReqVO pageReqVO) {
+        return brokerageWithdrawMapper.selectPage(pageReqVO);
+    }
+
+}

+ 8 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -12,6 +12,8 @@ import cn.iocoder.yudao.module.member.api.address.AddressApi;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
 import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
 import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
+import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
 import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
 import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
@@ -115,6 +117,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Resource
     private BargainRecordApi bargainRecordApi;
     @Resource
+    private MemberUserApi memberUserApi;
+    @Resource
     private MemberLevelApi memberLevelApi;
     @Resource
     private MemberPointApi memberPointApi;
@@ -782,9 +786,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
     @Async
     protected void addBrokerageAsync(Long userId, Long orderId) {
+        MemberUserRespDTO user = memberUserApi.getUser(userId);
+        Assert.notNull(user);
+
         List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
         List<BrokerageAddReqBO> list = convertList(orderItems,
-                item -> TradeOrderConvert.INSTANCE.convert(item, productSkuApi.getSku(item.getSkuId())));
+                item -> TradeOrderConvert.INSTANCE.convert(user, item, productSkuApi.getSku(item.getSkuId())));
         brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, list);
     }
 

+ 2 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/bo/TradeBeforeOrderCreateReqBO.java

@@ -14,6 +14,8 @@ import javax.validation.constraints.NotNull;
 @Data
 public class TradeBeforeOrderCreateReqBO {
 
+    // TODO @puhui999:注释也写下哈;bo 还是写注释噢
+
     @NotNull(message = "订单类型不能为空")
     private Integer orderType;
 

+ 88 - 0
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/withdraw/BrokerageWithdrawServiceImplTest.java

@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.module.trade.service.withdraw;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.withdraw.vo.BrokerageWithdrawPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.withdraw.BrokerageWithdrawDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.withdraw.BrokerageWithdrawMapper;
+import cn.iocoder.yudao.module.trade.service.brokerage.withdraw.BrokerageWithdrawServiceImpl;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+// TODO 芋艿:后续 review
+/**
+ * {@link BrokerageWithdrawServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(BrokerageWithdrawServiceImpl.class)
+public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private BrokerageWithdrawServiceImpl brokerageWithdrawService;
+
+    @Resource
+    private BrokerageWithdrawMapper brokerageWithdrawMapper;
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetBrokerageWithdrawPage() {
+        // mock 数据
+        BrokerageWithdrawDO dbBrokerageWithdraw = randomPojo(BrokerageWithdrawDO.class, o -> { // 等会查询到
+            o.setUserId(null);
+            o.setType(null);
+            o.setName(null);
+            o.setAccountNo(null);
+            o.setBankName(null);
+            o.setStatus(null);
+            o.setCreateTime(null);
+        });
+        brokerageWithdrawMapper.insert(dbBrokerageWithdraw);
+        // 测试 userId 不匹配
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserId(null)));
+        // 测试 type 不匹配
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setType(null)));
+        // 测试 name 不匹配
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setName(null)));
+        // 测试 accountNo 不匹配
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAccountNo(null)));
+        // 测试 bankName 不匹配
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setBankName(null)));
+        // 测试 status 不匹配
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setStatus(null)));
+        // 测试 auditReason 不匹配
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAuditReason(null)));
+        // 测试 auditTime 不匹配
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAuditTime(null)));
+        // 测试 remark 不匹配
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setRemark(null)));
+        // 测试 createTime 不匹配
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setCreateTime(null)));
+        // 准备参数
+        BrokerageWithdrawPageReqVO reqVO = new BrokerageWithdrawPageReqVO();
+        reqVO.setUserId(null);
+        reqVO.setType(null);
+        reqVO.setName(null);
+        reqVO.setAccountNo(null);
+        reqVO.setBankName(null);
+        reqVO.setStatus(null);
+        reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+        // 调用
+        PageResult<BrokerageWithdrawDO> pageResult = brokerageWithdrawService.getBrokerageWithdrawPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbBrokerageWithdraw, pageResult.getList().get(0));
+    }
+
+}

+ 1 - 0
yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql

@@ -4,3 +4,4 @@ DELETE FROM trade_after_sale;
 DELETE FROM trade_after_sale_log;
 DELETE FROM trade_brokerage_user;
 DELETE FROM trade_brokerage_record;
+DELETE FROM "trade_brokerage_withdraw";

+ 26 - 1
yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql

@@ -163,4 +163,29 @@ CREATE TABLE IF NOT EXISTS "trade_brokerage_record"
     "deleted"       bit      NOT NULL DEFAULT FALSE,
     "tenant_id"      bigint   not null default '0',
     PRIMARY KEY ("id")
-) COMMENT '佣金记录';
+) COMMENT '佣金记录';
+CREATE TABLE IF NOT EXISTS "trade_brokerage_withdraw"
+(
+    "id"                  int      NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "user_id"             bigint   NOT NULL,
+    "price"               int      NOT NULL,
+    "fee_price"           int      NOT NULL,
+    "total_price"         int      NOT NULL,
+    "type"                varchar  NOT NULL,
+    "name"                varchar,
+    "account_no"          varchar,
+    "bank_name"           varchar,
+    "bank_address"        varchar,
+    "account_qr_code_url" varchar,
+    "status"              varchar  NOT NULL,
+    "audit_reason"        varchar,
+    "audit_time"          varchar,
+    "remark"              varchar,
+    "creator"             varchar           DEFAULT '',
+    "create_time"         datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater"             varchar           DEFAULT '',
+    "update_time"         datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted"             bit      NOT NULL DEFAULT FALSE,
+    "tenant_id"      bigint   not null default '0',目
+    PRIMARY KEY ("id")
+) COMMENT '佣金提现';

+ 7 - 7
yudao-server/src/main/resources/application-local.yaml

@@ -44,31 +44,31 @@ spring:
       primary: master
       datasource:
         master:
-          name: mall
-          url: jdbc:mysql://10.211.55.5:3308/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
+          name: ruoyi-vue-pro
+          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 连接的示例
           #          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
           #          url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例
           #          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
           #          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
           username: root
-          password: 1qaz!QAZ
+          password: 123456
         #          username: sa
         #          password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
         slave: # 模拟从库,可根据自己需要修改
-          name: mall
-          url: jdbc:mysql://10.211.55.5:3308/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
+          name: ruoyi-vue-pro
+          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 连接的示例
           #          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 连接的示例
           #          url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
           #          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
           #          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
           username: root
-          password: 1qaz!QAZ
+          password: 123456
   #          username: sa
   #          password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
 
   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
   redis:
-    host: 10.211.55.5 # 地址
+    host: 127.0.0.1 # 地址
     port: 6379 # 端口
     database: 0 # 数据库索引
 #    password: dev # 密码,建议生产环境开启