Browse Source

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

jason 1 year ago
parent
commit
f99e2aec2d
40 changed files with 1667 additions and 6 deletions
  1. 189 0
      sql/mysql/brokerage.sql
  2. 9 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java
  3. 8 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  4. 15 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  5. 30 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/BrokerageApi.java
  6. 35 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/dto/BrokerageAddReqDTO.java
  7. 44 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageBindModeEnum.java
  8. 44 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageEnabledConditionEnum.java
  9. 46 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageRecordBizTypeEnum.java
  10. 39 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageRecordStatusEnum.java
  11. 41 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageWithdrawStatusEnum.java
  12. 40 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageWithdrawTypeEnum.java
  13. 32 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/BrokerageApiImpl.java
  14. 51 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/MemberBrokerageRecordController.java
  15. 59 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordBaseVO.java
  16. 33 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordPageReqVO.java
  17. 22 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordRespVO.java
  18. 57 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigBaseVO.java
  19. 43 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/AppBrokerageRecordController.java
  20. 24 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/AppBrokerageUserController.java
  21. 39 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/AppBrokerageWithdrawController.java
  22. 19 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/record/AppBrokerageRecordPageReqVO.java
  23. 27 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/record/AppBrokerageRecordRespVO.java
  24. 22 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/user/AppBrokerageUserRankPageReqVO.java
  25. 22 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/user/AppBrokerageUserRankRespVO.java
  26. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/AppBrokerageUserSummaryRespVO.java
  27. 27 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java
  28. 50 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/brokerage/record/MemberBrokerageRecordConvert.java
  29. 78 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/brokerage/record/MemberBrokerageRecordDO.java
  30. 60 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java
  31. 27 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java
  32. 27 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/brokerage/record/MemberBrokerageRecordMapper.java
  33. 30 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java
  34. 40 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordService.java
  35. 151 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordServiceImpl.java
  36. 24 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java
  37. 22 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java
  38. 116 0
      yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordServiceImplTest.java
  39. 2 1
      yudao-module-member/yudao-module-member-biz/src/test/resources/sql/clean.sql
  40. 22 1
      yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql

+ 189 - 0
sql/mysql/brokerage.sql

@@ -0,0 +1,189 @@
+-- 增加配置
+alter table member_point_config
+    add column brokerage_enabled bit default 1 not null comment '是否启用分佣';
+alter table member_point_config
+    add column brokerage_enabled_condition tinyint default 0 not null comment '分佣模式:0-人人分销 1-指定分销';
+alter table member_point_config
+    add column brokerage_bind_mode tinyint default 0 not null comment '分销关系绑定模式: 0-没有推广人,1-新用户';
+alter table member_point_config
+    add column brokerage_post_urls varchar(2000) null comment '分销海报图地址数组';
+alter table member_point_config
+    add column brokerage_first_percent int not null comment '一级返佣比例';
+alter table member_point_config
+    add column brokerage_second_percent int not null comment '二级返佣比例';
+alter table member_point_config
+    add column brokerage_withdraw_min_price int not null comment '用户提现最低金额';
+alter table member_point_config
+    add column brokerage_bank_names varchar(200) not null comment '提现银行(字典类型=brokerage_bank_name)';
+alter table member_point_config
+    add column brokerage_frozen_days int default 7 not null comment '佣金冻结时间(天)';
+alter table member_point_config
+    add column brokerage_withdraw_type varchar(32) default '1,2,3,4' not null comment '提现方式:1-钱包;2-银行卡;3-微信;4-支付宝';
+
+-- 用户表增加分销相关字段
+alter table member_user
+    add column brokerage_user_id bigint not null comment '推广员编号';
+alter table member_user
+    add column brokerage_bind_time datetime null comment '推广员绑定时间';
+alter table member_user
+    add column brokerage_enabled bit default 1 not null comment '是否成为推广员';
+alter table member_user
+    add column brokerage_time datetime null comment '成为分销员时间';
+alter table member_user
+    add column brokerage_price int default 0 not null comment '可用佣金';
+alter table member_user
+    add column frozen_brokerage_price int default 0 not null comment '冻结佣金';
+
+create index idx_invite_user_id on member_user (brokerage_user_id) comment '推广员编号';
+create index idx_agent on member_user (brokerage_enabled) comment '是否成为推广员';
+
+
+create table member_brokerage_record
+(
+    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 '业务类型:0-订单,1-提现',
+    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 '解冻时间',
+    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 '租户编号'
+)
+    comment '佣金记录';
+
+create index idx_user_id on member_brokerage_record (user_id) comment '用户编号';
+create index idx_biz on member_brokerage_record (biz_type, biz_id) comment '业务';
+create index idx_status on member_brokerage_record (status) comment '状态';
+
+
+create table member_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 '租户编号'
+)
+    comment '佣金提现';
+
+create index idx_user_id on member_brokerage_withdraw (user_id) comment '用户编号';
+create index idx_audit_status on member_brokerage_withdraw (status) comment '状态';
+
+-- 增加字典
+insert into system_dict_type(type, name)
+values ('brokerage_enabled_condition', '分佣模式');
+insert into system_dict_data(dict_type, label, value, sort, remark)
+values ('brokerage_enabled_condition', '人人分销', 0, 0, '所有用户都可以分销'),
+       ('brokerage_enabled_condition', '指定分销', 1, 1, '仅可后台手动设置推广员');
+
+insert into system_dict_type(type, name)
+values ('brokerage_bind_mode', '分销关系绑定模式');
+insert into system_dict_data(dict_type, label, value, sort, remark)
+values ('brokerage_bind_mode', '没有推广人', 0, 0, '只要用户没有推广人,随时都可以绑定推广关系'),
+       ('brokerage_bind_mode', '新用户', 1, 1, '仅新用户注册时才能绑定推广关系');
+
+insert into system_dict_type(type, name)
+values ('brokerage_withdraw_type', '佣金提现类型');
+insert into system_dict_data(dict_type, label, value, sort)
+values ('brokerage_withdraw_type', '钱包', 1, 1),
+       ('brokerage_withdraw_type', '银行卡', 2, 2),
+       ('brokerage_withdraw_type', '微信', 3, 3),
+       ('brokerage_withdraw_type', '支付宝', 4, 4);
+
+insert into system_dict_type(type, name)
+values ('brokerage_record_biz_type', '佣金记录业务类型');
+insert into system_dict_data(dict_type, label, value, sort)
+values ('brokerage_record_biz_type', '订单返佣', 0, 0),
+       ('brokerage_record_biz_type', '申请提现', 1, 1);
+
+insert into system_dict_type(type, name)
+values ('brokerage_record_status', '佣金记录状态');
+insert into system_dict_data(dict_type, label, value, sort)
+values ('brokerage_record_status', '待结算', 0, 0),
+       ('brokerage_record_status', '已结算', 1, 1),
+       ('brokerage_record_status', '已取消', 2, 2);
+
+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_type(type, name)
+values ('brokerage_bank_name', '佣金提现银行');
+insert into system_dict_data(dict_type, label, value, sort)
+values ('brokerage_bank_name', '工商银行', 0, 0),
+       ('brokerage_bank_name', '建设银行', 1, 1),
+       ('brokerage_bank_name', '农业银行', 2, 2),
+       ('brokerage_bank_name', '中国银行', 3, 3),
+       ('brokerage_bank_name', '交通银行', 4, 4),
+       ('brokerage_bank_name', '招商银行', 5, 5);
+
+-- 增加菜单:分销员管理
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('分销员', '', 2, 7, 2262, 'brokerage', 'user', 'member/brokerage/user/index', 0, 'MemberBrokerageUser');
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('分销员查询', 'member:brokerage-user:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('分销员创建', 'member:brokerage-user:create', 3, 2, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('分销员更新', 'member:brokerage-user:update', 3, 3, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('分销员删除', 'member:brokerage-user:delete', 3, 4, @parentId, '', '', '', 0);
+
+-- 增加菜单:佣金记录
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('佣金记录', '', 2, 8, 2262, 'brokerage-record', 'list', 'member/brokerage/record/index', 0,
+        'MemberBrokerageRecord');
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('佣金记录查询', 'member:member-brokerage-record:query', 3, 1, @parentId, '', 'table', '', 0);
+
+-- 增加菜单:佣金提现
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('佣金提现', '', 2, 9, 2262, 'brokerage-withdraw', '', 'member/brokerage/withdraw/index', 0,
+        'MemberBrokerageWithdraw');
+
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('佣金提现查询', 'member:brokerage-withdraw:query', 3, 1, @parentId, '', '', '', 0);

+ 9 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java

@@ -60,4 +60,13 @@ public class ProductSkuRespDTO {
      */
      */
     private Double volume;
     private Double volume;
 
 
+    /**
+     * 一级分销的佣金,单位:分
+     */
+    private Integer subCommissionFirstPrice;
+    /**
+     * 二级分销的佣金,单位:分
+     */
+    private Integer subCommissionSecondPrice;
+
 }
 }

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

@@ -7,11 +7,13 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
 import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
 import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
@@ -273,4 +275,10 @@ public interface TradeOrderConvert {
 
 
     TradeOrderDO convert(TradeOrderRemarkReqVO reqVO);
     TradeOrderDO convert(TradeOrderRemarkReqVO reqVO);
 
 
+    default BrokerageAddReqDTO convert(TradeOrderItemDO item, ProductSkuRespDTO sku) {
+        return new BrokerageAddReqDTO().setBizId(String.valueOf(item.getId()))
+                .setPayPrice(item.getPayPrice()).setCount(item.getCount())
+                .setSkuFirstBrokeragePrice(sku.getSubCommissionFirstPrice())
+                .setSkuSecondBrokeragePrice(sku.getSubCommissionSecondPrice());
+    }
 }
 }

+ 15 - 0
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.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.module.member.api.address.AddressApi;
 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.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.member.api.brokerage.BrokerageApi;
+import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
 import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
 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.point.MemberPointApi;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
@@ -118,6 +120,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Resource
     @Resource
     private MemberPointApi memberPointApi;
     private MemberPointApi memberPointApi;
     @Resource
     @Resource
+    private BrokerageApi brokerageApi;
+    @Resource
     private ProductCommentApi productCommentApi;
     private ProductCommentApi productCommentApi;
 
 
     @Resource
     @Resource
@@ -365,6 +369,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         getSelf().addUserPointAsync(order.getUserId(), order.getPayPrice(), order.getId());
         getSelf().addUserPointAsync(order.getUserId(), order.getPayPrice(), order.getId());
         // 增加用户经验
         // 增加用户经验
         getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
         getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
+        // 增加用户佣金
+        getSelf().addBrokerageAsync(order.getUserId(), order.getId());
     }
     }
 
 
     /**
     /**
@@ -742,6 +748,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
         memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
     }
     }
 
 
+
+    @Async
+    protected void addBrokerageAsync(Long userId, Long orderId) {
+        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
+        List<BrokerageAddReqDTO> list = convertList(orderItems,
+                item -> TradeOrderConvert.INSTANCE.convert(item, productSkuApi.getSku(item.getSkuId())));
+        brokerageApi.addBrokerage(userId, list);
+    }
+
     /**
     /**
      * 获得自身的代理对象,解决 AOP 生效问题
      * 获得自身的代理对象,解决 AOP 生效问题
      *
      *

+ 30 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/BrokerageApi.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.member.api.brokerage;
+
+import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
+
+import java.util.List;
+
+/**
+ * 佣金 API 接口
+ *
+ * @author owen
+ */
+public interface BrokerageApi {
+
+    /**
+     * 增加佣金
+     *
+     * @param userId 会员ID
+     * @param list   请求参数列表
+     */
+    void addBrokerage(Long userId, List<BrokerageAddReqDTO> list);
+
+    /**
+     * 取消佣金
+     *
+     * @param userId 会员ID
+     * @param bizId  业务编号
+     */
+    void cancelBrokerage(Long userId, String bizId);
+
+}

+ 35 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/dto/BrokerageAddReqDTO.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.member.api.brokerage.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 佣金 增加 Request DTO
+ * @author owen
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BrokerageAddReqDTO {
+    /**
+     * 业务ID
+     */
+    private String bizId;
+    /**
+     * 商品支付价格
+     */
+    private Integer payPrice;
+    /**
+     * SKU 一级佣金
+     */
+    private Integer skuFirstBrokeragePrice;
+    /**
+     * SKU 二级佣金
+     */
+    private Integer skuSecondBrokeragePrice;
+    /**
+     * 购买数量
+     */
+    private Integer count;
+}

+ 44 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageBindModeEnum.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.member.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 分销关系绑定模式枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageBindModeEnum implements IntArrayValuable {
+
+    /**
+     * 只要用户没有推广人,随时都可以绑定分销关系
+     */
+    ANYTIME(0, "没有推广人"),
+    /**
+     * 仅新用户注册时才能绑定推广关系
+     */
+    REGISTER(1, "新用户"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageBindModeEnum::getMode).toArray();
+
+    /**
+     * 模式
+     */
+    private final Integer mode;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 44 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageEnabledConditionEnum.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.member.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 分佣模式枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageEnabledConditionEnum implements IntArrayValuable {
+
+    /**
+     * 所有用户都可以分销
+     */
+    ALL(0, "人人分销"),
+    /**
+     * 仅可后台手动设置推广员
+     */
+    ADMIN(1, "指定分销"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageEnabledConditionEnum::getCondition).toArray();
+
+    /**
+     * 模式
+     */
+    private final Integer condition;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 46 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageRecordBizTypeEnum.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.member.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 佣金记录业务类型枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageRecordBizTypeEnum implements IntArrayValuable {
+
+    ORDER(0, "获得推广佣金", "获得推广佣金 {}", true),
+    WITHDRAW(1, "提现申请", "提现申请扣除佣金 {}", false),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageRecordBizTypeEnum::getType).toArray();
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+    /**
+     * 标题
+     */
+    private final String title;
+    /**
+     * 描述
+     */
+    private final String description;
+    /**
+     * 是否为增加佣金
+     */
+    private final boolean add;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 39 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageRecordStatusEnum.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.member.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 佣金记录状态枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageRecordStatusEnum implements IntArrayValuable {
+
+    WAIT_SETTLEMENT(0, "待结算"),
+    SETTLEMENT(1, "已结算"),
+    CANCEL(2, "已取消"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageRecordStatusEnum::getStatus).toArray();
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 41 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageWithdrawStatusEnum.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.member.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 佣金提现状态枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageWithdrawStatusEnum implements IntArrayValuable {
+
+    AUDITING(0, "审核中"),
+    AUDIT_SUCCESS(10, "审核通过"),
+    WITHDRAW_SUCCESS(11, "提现成功"),
+    AUDIT_FAIL(20, "审核不通过"),
+    WITHDRAW_FAIL(21, "提现失败"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawStatusEnum::getStatus).toArray();
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 40 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageWithdrawTypeEnum.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.member.enums.brokerage;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 佣金提现类型枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum BrokerageWithdrawTypeEnum implements IntArrayValuable {
+
+    WALLET(1, "钱包"),
+    BANK(2, "银行卡"),
+    WECHAT(3, "微信"),
+    ALIPAY(4, "支付宝"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawTypeEnum::getType).toArray();
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 32 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/BrokerageApiImpl.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.member.api.brokerage;
+
+import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
+import cn.iocoder.yudao.module.member.service.brokerage.record.MemberBrokerageRecordService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 佣金 API 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class BrokerageApiImpl implements BrokerageApi {
+
+    @Resource
+    private MemberBrokerageRecordService memberBrokerageRecordService;
+
+    @Override
+    public void addBrokerage(Long userId, List<BrokerageAddReqDTO> list) {
+        memberBrokerageRecordService.addBrokerage(userId, list);
+    }
+
+    @Override
+    public void cancelBrokerage(Long userId, String bizId) {
+
+    }
+}

+ 51 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/MemberBrokerageRecordController.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.member.controller.admin.brokerage.record;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordRespVO;
+import cn.iocoder.yudao.module.member.convert.brokerage.record.MemberBrokerageRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
+import cn.iocoder.yudao.module.member.service.brokerage.record.MemberBrokerageRecordService;
+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.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 佣金记录")
+@RestController
+@RequestMapping("/member/member-brokerage-record")
+@Validated
+public class MemberBrokerageRecordController {
+
+    @Resource
+    private MemberBrokerageRecordService memberBrokerageRecordService;
+
+    @GetMapping("/get")
+    @Operation(summary = "获得佣金记录")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('member:member-brokerage-record:query')")
+    public CommonResult<MemberBrokerageRecordRespVO> getMemberBrokerageRecord(@RequestParam("id") Integer id) {
+        MemberBrokerageRecordDO memberBrokerageRecord = memberBrokerageRecordService.getMemberBrokerageRecord(id);
+        return success(MemberBrokerageRecordConvert.INSTANCE.convert(memberBrokerageRecord));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得佣金记录分页")
+    @PreAuthorize("@ss.hasPermission('member:member-brokerage-record:query')")
+    public CommonResult<PageResult<MemberBrokerageRecordRespVO>> getMemberBrokerageRecordPage(@Valid MemberBrokerageRecordPageReqVO pageVO) {
+        PageResult<MemberBrokerageRecordDO> pageResult = memberBrokerageRecordService.getMemberBrokerageRecordPage(pageVO);
+        return success(MemberBrokerageRecordConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 59 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordBaseVO.java

@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.member.controller.admin.brokerage.record.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 MemberBrokerageRecordBaseVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25973")
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+
+    @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23353")
+    @NotNull(message = "业务编号不能为空")
+    private String bizId;
+
+    @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "业务类型不能为空")
+    private Integer bizType;
+
+    @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "标题不能为空")
+    private String title;
+
+    @Schema(description = "金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "28731")
+    @NotNull(message = "金额不能为空")
+    private Integer price;
+
+    @Schema(description = "当前总佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "13226")
+    @NotNull(message = "当前总佣金不能为空")
+    private Integer totalPrice;
+
+    @Schema(description = "说明", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
+    @NotNull(message = "说明不能为空")
+    private String description;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @Schema(description = "冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "冻结时间(天)不能为空")
+    private Integer frozenDays;
+
+    @Schema(description = "解冻时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime unfreezeTime;
+
+}

+ 33 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordPageReqVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 佣金记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberBrokerageRecordPageReqVO extends PageParam {
+
+    @Schema(description = "用户编号", example = "25973")
+    private Long userId;
+
+    @Schema(description = "业务类型", example = "1")
+    private Integer bizType;
+
+    @Schema(description = "状态", example = "1")
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 22 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.controller.admin.brokerage.record.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 MemberBrokerageRecordRespVO extends MemberBrokerageRecordBaseVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28896")
+    private Integer id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 57 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigBaseVO.java

@@ -1,9 +1,17 @@
 package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
 package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
 
 
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageBindModeEnum;
+import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageEnabledConditionEnum;
+import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageWithdrawTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.Data;
+import org.hibernate.validator.constraints.Range;
 
 
+import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.NotNull;
+import javax.validation.constraints.PositiveOrZero;
+import java.util.List;
 
 
 /**
 /**
  * 会员积分配置 Base VO,提供给添加、修改、详细的子 VO 使用
  * 会员积分配置 Base VO,提供给添加、修改、详细的子 VO 使用
@@ -28,4 +36,53 @@ public class MemberPointConfigBaseVO {
     @NotNull(message = "1 元赠送积分不能为空")
     @NotNull(message = "1 元赠送积分不能为空")
     private Integer tradeGivePoint;
     private Integer tradeGivePoint;
 
 
+    // ========== 分销相关 ==========
+
+    @Schema(description = "是否启用分佣", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    @NotNull(message = "是否启用分佣不能为空")
+    private Boolean brokerageEnabled;
+
+    @Schema(description = "分佣模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "分佣模式不能为空")
+    @InEnum(value = BrokerageEnabledConditionEnum.class, message = "分佣模式必须是 {value}")
+    private Integer brokerageEnabledCondition;
+
+    @Schema(description = "分销关系绑定模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "分销关系绑定模式不能为空")
+    @InEnum(value = BrokerageBindModeEnum.class, message = "分销关系绑定模式必须是 {value}")
+    private Integer brokerageBindMode;
+
+    @Schema(description = "分销海报图地址数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/yudao.jpg]")
+    private List<String> brokeragePostUrls;
+
+    @Schema(description = "一级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+    @NotNull(message = "一级返佣比例不能为空")
+    @Range(min = 0, max = 100, message = "一级返佣比例必须在 0 - 100 之间")
+    private Integer brokerageFirstPercent;
+
+    @Schema(description = "二级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+    @NotNull(message = "二级返佣比例不能为空")
+    @Range(min = 0, max = 100, message = "二级返佣比例必须在 0 - 100 之间")
+    private Integer brokerageSecondPercent;
+
+    @Schema(description = "用户提现最低金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    @NotNull(message = "用户提现最低金额不能为空")
+    @PositiveOrZero(message = "用户提现最低金额不能是负数")
+    private Integer brokerageWithdrawMinPrice;
+
+    @Schema(description = "提现银行", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
+    @NotEmpty(message = "提现银行不能为空")
+    private List<Integer> brokerageBankNames;
+
+    @Schema(description = "佣金冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
+    @NotNull(message = "佣金冻结时间(天)不能为空")
+    @PositiveOrZero(message = "佣金冻结时间不能是负数")
+    private Integer brokerageFrozenDays;
+
+    @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
+    @NotNull(message = "提现方式不能为空")
+    @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
+    private List<Integer> brokerageWithdrawType;
+
+
 }
 }

+ 43 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/AppBrokerageRecordController.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.member.controller.app.brokerage;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.member.controller.app.brokerage.vo.record.AppBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.app.brokerage.vo.record.AppBrokerageRecordRespVO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static java.util.Arrays.asList;
+
+@Tag(name = "用户 APP - 分销用户")
+@RestController
+@RequestMapping("/member/brokerage-record")
+@Validated
+@Slf4j
+public class AppBrokerageRecordController {
+
+    // TODO 芋艿:临时 mock =>
+    @GetMapping("/page")
+    @Operation(summary = "获得分销记录分页")
+    @PreAuthenticated
+    public CommonResult<PageResult<AppBrokerageRecordRespVO>> getBrokerageRecordPage(@Valid AppBrokerageRecordPageReqVO pageReqVO) {
+        AppBrokerageRecordRespVO vo1 = new AppBrokerageRecordRespVO()
+                .setId(1L).setPrice(10).setTitle("收到钱").setCreateTime(LocalDateTime.now())
+                .setFinishTime(LocalDateTime.now());
+        AppBrokerageRecordRespVO vo2 = new AppBrokerageRecordRespVO()
+                .setId(2L).setPrice(-20).setTitle("提现钱").setCreateTime(LocalDateTime.now())
+                .setFinishTime(LocalDateTime.now());
+        return success(new PageResult<>(asList(vo1, vo2), 10L));
+    }
+
+}

+ 24 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/AppBrokerageUserController.java

@@ -1,8 +1,11 @@
 package cn.iocoder.yudao.module.member.controller.app.brokerage;
 package cn.iocoder.yudao.module.member.controller.app.brokerage;
 
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
-import cn.iocoder.yudao.module.member.controller.app.brokerage.vo.AppBrokerageUserSummaryRespVO;
+import cn.iocoder.yudao.module.member.controller.app.brokerage.vo.user.AppBrokerageUserRankPageReqVO;
+import cn.iocoder.yudao.module.member.controller.app.brokerage.vo.user.AppBrokerageUserRankRespVO;
+import cn.iocoder.yudao.module.member.controller.app.brokerage.vo.user.AppBrokerageUserSummaryRespVO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -12,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.bind.annotation.RestController;
 
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static java.util.Arrays.asList;
 
 
 @Tag(name = "用户 APP - 分销用户")
 @Tag(name = "用户 APP - 分销用户")
 @RestController
 @RestController
@@ -33,4 +37,23 @@ public class AppBrokerageUserController {
         return success(respVO);
         return success(respVO);
     }
     }
 
 
+    @GetMapping("/rank-page")
+    @Operation(summary = "获得分销用户排行分页")
+    @PreAuthenticated
+    public CommonResult<PageResult<AppBrokerageUserRankRespVO>> getBrokerageUserRankPage(AppBrokerageUserRankPageReqVO pageReqVO) {
+        AppBrokerageUserRankRespVO vo1 = new AppBrokerageUserRankRespVO()
+                .setUserId(1L).setNickname("芋1**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokerageUserCount(10);
+        AppBrokerageUserRankRespVO vo2 = new AppBrokerageUserRankRespVO()
+                .setUserId(2L).setNickname("芋2**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokerageUserCount(6);
+        AppBrokerageUserRankRespVO vo3 = new AppBrokerageUserRankRespVO()
+                .setUserId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokerageUserCount(4);
+        AppBrokerageUserRankRespVO vo4 = new AppBrokerageUserRankRespVO()
+                .setUserId(3L).setNickname("芋3**艿").setAvatar("http://www.iocoder.cn/images/common/wechat_mp_2017_07_31_bak.jpg")
+                .setBrokerageUserCount(4);
+        return success(new PageResult<>(asList(vo1, vo2, vo3, vo4), 10L));
+    }
+
 }
 }

+ 39 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/AppBrokerageWithdrawController.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.member.controller.app.brokerage;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.member.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawRespVO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static java.util.Arrays.asList;
+
+@Tag(name = "用户 APP - 分销提现")
+@RestController
+@RequestMapping("/member/brokerage-withdraw")
+@Validated
+@Slf4j
+public class AppBrokerageWithdrawController {
+
+    // TODO 芋艿:临时 mock =>
+    @GetMapping("/page")
+    @Operation(summary = "获得分销提现分页")
+    @PreAuthenticated
+    public CommonResult<PageResult<AppBrokerageWithdrawRespVO>> getBrokerageWithdrawPage() {
+        AppBrokerageWithdrawRespVO vo1 = new AppBrokerageWithdrawRespVO()
+                .setId(1L).setStatus(10).setPrice(10).setStatusName("审批通过").setCreateTime(LocalDateTime.now());
+        AppBrokerageWithdrawRespVO vo2 = new AppBrokerageWithdrawRespVO()
+                .setId(2L).setStatus(0).setPrice(20).setStatusName("审批中").setCreateTime(LocalDateTime.now());
+        return success(new PageResult<>(asList(vo1, vo2), 10L));
+    }
+
+}

+ 19 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/record/AppBrokerageRecordPageReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.member.controller.app.brokerage.vo.record;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "应用 App - 分销记录分页 Request VO")
+@Data
+public class AppBrokerageRecordPageReqVO extends PageParam {
+
+    // TODO @疯狂:要加下枚举校验
+
+    @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer bizType;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+}

+ 27 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/record/AppBrokerageRecordRespVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.member.controller.app.brokerage.vo.record;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 App - 分销记录 Response VO")
+@Data
+public class AppBrokerageRecordRespVO {
+
+    @Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Long id;
+
+    @Schema(description = "分销标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "用户下单")
+    private String title;
+
+    @Schema(description = "分销金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    private Integer price;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime finishTime;
+
+}

+ 22 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/user/AppBrokerageUserRankPageReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.controller.app.brokerage.vo.user;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotEmpty;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "应用 App - 分销用户排行 Request VO")
+@Data
+public class AppBrokerageUserRankPageReqVO extends PageParam {
+
+    @Schema(description = "开始 + 结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @NotEmpty(message = "时间不能为空")
+    private LocalDateTime[] times;
+
+}

+ 22 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/user/AppBrokerageUserRankRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.controller.app.brokerage.vo.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 分销排行用户 Response VO")
+@Data
+public class AppBrokerageUserRankRespVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Long userId;
+
+    @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
+    private String nickname;
+
+    @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://www.iocoder.cn/xxx.jpg")
+    private String avatar;
+
+    @Schema(description = "邀请用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer brokerageUserCount;
+
+}

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/AppBrokerageUserSummaryRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.controller.app.brokerage.vo;
+package cn.iocoder.yudao.module.member.controller.app.brokerage.vo.user;
 
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.Data;

+ 27 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.member.controller.app.brokerage.vo.withdraw;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 App - 分销提现 Response VO")
+@Data
+public class AppBrokerageWithdrawRespVO {
+
+    @Schema(description = "提现编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Long id;
+
+    @Schema(description = "提现状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer status;
+
+    @Schema(description = "提现状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "审核中")
+    private String statusName;
+
+    @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    private Integer price;
+
+    @Schema(description = "提现时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 50 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/brokerage/record/MemberBrokerageRecordConvert.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.member.convert.brokerage.record;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordRespVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordStatusEnum;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 佣金记录 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberBrokerageRecordConvert {
+
+    MemberBrokerageRecordConvert INSTANCE = Mappers.getMapper(MemberBrokerageRecordConvert.class);
+
+    MemberBrokerageRecordRespVO convert(MemberBrokerageRecordDO bean);
+
+    List<MemberBrokerageRecordRespVO> convertList(List<MemberBrokerageRecordDO> list);
+
+    PageResult<MemberBrokerageRecordRespVO> convertPage(PageResult<MemberBrokerageRecordDO> page);
+
+    default MemberBrokerageRecordDO convert(MemberUserDO user, String bizId, int brokerageFrozenDays, int brokerage, LocalDateTime unfreezeTime) {
+        // 不冻结时,佣金直接就是结算状态
+        Integer status = brokerageFrozenDays > 0
+                ? BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus()
+                : BrokerageRecordStatusEnum.SETTLEMENT.getStatus();
+        return new MemberBrokerageRecordDO()
+                .setUserId(user.getId())
+                .setBizType(BrokerageRecordBizTypeEnum.ORDER.getType())
+                .setBizId(bizId)
+                .setPrice(brokerage)
+                .setTotalPrice(user.getBrokeragePrice())
+                .setTitle(BrokerageRecordBizTypeEnum.ORDER.getTitle())
+                .setDescription(StrUtil.format(BrokerageRecordBizTypeEnum.ORDER.getDescription(), String.valueOf(brokerage / 100.0)))
+                .setStatus(status)
+                .setFrozenDays(brokerageFrozenDays)
+                .setUnfreezeTime(unfreezeTime);
+    }
+
+}

+ 78 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/brokerage/record/MemberBrokerageRecordDO.java

@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordStatusEnum;
+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 owen
+ */
+@TableName("member_brokerage_record")
+@KeySequence("member_brokerage_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MemberBrokerageRecordDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Integer id;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 业务编号
+     */
+    private String bizId;
+    /**
+     * 业务类型
+     * <p>
+     * 枚举 {@link BrokerageRecordBizTypeEnum 对应的类}
+     */
+    private Integer bizType;
+    /**
+     * 标题
+     */
+    private String title;
+    /**
+     * 金额
+     */
+    private Integer price;
+    /**
+     * 当前总佣金
+     */
+    private Integer totalPrice;
+    /**
+     * 说明
+     */
+    private String description;
+    /**
+     * 状态
+     * <p>
+     * 枚举 {@link BrokerageRecordStatusEnum 对应的类}
+     */
+    private Integer status;
+    /**
+     * 冻结时间(天)
+     */
+    private Integer frozenDays;
+    /**
+     * 解冻时间
+     */
+    private LocalDateTime unfreezeTime;
+
+}

+ 60 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java

@@ -1,19 +1,25 @@
 package cn.iocoder.yudao.module.member.dal.dataobject.point;
 package cn.iocoder.yudao.module.member.dal.dataobject.point;
 
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
+import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageBindModeEnum;
+import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageEnabledConditionEnum;
+import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageWithdrawTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
 import lombok.*;
 import lombok.*;
 
 
-import java.math.BigDecimal;
+import java.util.List;
 
 
 /**
 /**
  * 会员积分配置 DO
  * 会员积分配置 DO
  *
  *
  * @author QingX
  * @author QingX
  */
  */
-@TableName("member_point_config")
+@TableName(value = "member_point_config", autoResultMap = true)
 @KeySequence("member_point_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @KeySequence("member_point_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @Data
 @EqualsAndHashCode(callSuper = true)
 @EqualsAndHashCode(callSuper = true)
@@ -47,4 +53,56 @@ public class MemberPointConfigDO extends BaseDO {
      */
      */
     private Integer tradeGivePoint;
     private Integer tradeGivePoint;
 
 
+    // ========== 分销相关 ==========
+
+    /**
+     * 是否启用分佣
+     */
+    private Boolean brokerageEnabled;
+    /**
+     * 分佣模式
+     *
+     * 枚举 {@link BrokerageEnabledConditionEnum 对应的类}
+     */
+    private Integer brokerageEnabledCondition;
+    /**
+     * 分销关系绑定模式
+     *
+     * 枚举 {@link BrokerageBindModeEnum 对应的类}
+     */
+    private Integer brokerageBindMode;
+    /**
+     * 分销海报图地址数组
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<String> brokeragePostUrls;
+    /**
+     * 一级返佣比例
+     */
+    private Integer brokerageFirstPercent;
+    /**
+     * 二级返佣比例
+     */
+    private Integer brokerageSecondPercent;
+    /**
+     * 用户提现最低金额
+     */
+    private Integer brokerageWithdrawMinPrice;
+    /**
+     * 提现银行
+     */
+    @TableField(typeHandler = IntegerListTypeHandler.class)
+    private List<Integer> brokerageBankNames;
+    /**
+     * 佣金冻结时间(天)
+     */
+    private Integer brokerageFrozenDays;
+    /**
+     * 提现方式
+     *
+     * 枚举 {@link BrokerageWithdrawTypeEnum 对应的类}
+     */
+    @TableField(typeHandler = IntegerListTypeHandler.class)
+    private List<Integer> brokerageWithdrawType;
+
 }
 }

+ 27 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java

@@ -136,4 +136,31 @@ public class MemberUserDO extends TenantBaseDO {
      */
      */
     private Long groupId;
     private Long groupId;
 
 
+    // ========== 分销相关 ==========
+
+    /**
+     * 推广员编号
+     */
+    private Long brokerageUserId;
+    /**
+     * 推广员绑定时间
+     */
+    private LocalDateTime brokerageBindTime;
+    /**
+     * 是否成为推广员
+     */
+    private Boolean brokerageEnabled;
+    /**
+     * 成为分销员时间
+     */
+    private LocalDateTime brokerageTime;
+    /**
+     * 可用佣金
+     */
+    private Integer brokeragePrice;
+    /**
+     * 冻结佣金
+     */
+    private Integer frozenBrokeragePrice;
+
 }
 }

+ 27 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/brokerage/record/MemberBrokerageRecordMapper.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.member.dal.mysql.brokerage.record;
+
+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.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 佣金记录 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberBrokerageRecordMapper extends BaseMapperX<MemberBrokerageRecordDO> {
+
+    default PageResult<MemberBrokerageRecordDO> selectPage(MemberBrokerageRecordPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MemberBrokerageRecordDO>()
+                .eqIfPresent(MemberBrokerageRecordDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(MemberBrokerageRecordDO::getBizType, reqVO.getBizType())
+                .eqIfPresent(MemberBrokerageRecordDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(MemberBrokerageRecordDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(MemberBrokerageRecordDO::getId));
+    }
+
+}

+ 30 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java

@@ -1,12 +1,14 @@
 package cn.iocoder.yudao.module.member.dal.mysql.user;
 package cn.iocoder.yudao.module.member.dal.mysql.user;
 
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Mapper;
 
 
 import java.util.List;
 import java.util.List;
@@ -62,4 +64,32 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
                 .apply("FIND_IN_SET({0}, tag_ids)", tagId));
                 .apply("FIND_IN_SET({0}, tag_ids)", tagId));
     }
     }
 
 
+    /**
+     * 更新用户可用佣金(增加)
+     *
+     * @param id        用户编号
+     * @param incrCount 增加佣金(正数)
+     */
+    default void updateBrokeragePriceIncr(Long id, int incrCount) {
+        Assert.isTrue(incrCount > 0);
+        LambdaUpdateWrapper<MemberUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<MemberUserDO>()
+                .setSql(" brokerage_price = brokerage_price + " + incrCount)
+                .eq(MemberUserDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 更新用户冻结佣金(增加)
+     *
+     * @param id        用户编号
+     * @param incrCount 增加冻结佣金(正数)
+     */
+    default void updateFrozenBrokeragePriceIncr(Long id, int incrCount) {
+        Assert.isTrue(incrCount > 0);
+        LambdaUpdateWrapper<MemberUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<MemberUserDO>()
+                .setSql(" frozen_brokerage_price = frozen_brokerage_price + " + incrCount)
+                .eq(MemberUserDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
 }
 }

+ 40 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordService.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.member.service.brokerage.record;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
+import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
+
+import java.util.List;
+
+/**
+ * 佣金记录 Service 接口
+ *
+ * @author owen
+ */
+public interface MemberBrokerageRecordService {
+
+    /**
+     * 获得佣金记录
+     *
+     * @param id 编号
+     * @return 佣金记录
+     */
+    MemberBrokerageRecordDO getMemberBrokerageRecord(Integer id);
+
+    /**
+     * 获得佣金记录分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 佣金记录分页
+     */
+    PageResult<MemberBrokerageRecordDO> getMemberBrokerageRecordPage(MemberBrokerageRecordPageReqVO pageReqVO);
+
+    /**
+     * 增加佣金
+     *
+     * @param userId 会员ID
+     * @param list   请求参数列表
+     */
+    void addBrokerage(Long userId, List<BrokerageAddReqDTO> list);
+}

+ 151 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordServiceImpl.java

@@ -0,0 +1,151 @@
+package cn.iocoder.yudao.module.member.service.brokerage.record;
+
+import cn.hutool.core.util.BooleanUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
+import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.member.convert.brokerage.record.MemberBrokerageRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.member.dal.mysql.brokerage.record.MemberBrokerageRecordMapper;
+import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * 佣金记录 Service 实现类
+ *
+ * @author owen
+ */
+@Slf4j
+@Service
+@Validated
+public class MemberBrokerageRecordServiceImpl implements MemberBrokerageRecordService {
+
+    @Resource
+    private MemberBrokerageRecordMapper memberBrokerageRecordMapper;
+    @Resource
+    private MemberPointConfigService memberConfigService;
+    @Resource
+    private MemberUserService memberUserService;
+
+    @Override
+    public MemberBrokerageRecordDO getMemberBrokerageRecord(Integer id) {
+        return memberBrokerageRecordMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<MemberBrokerageRecordDO> getMemberBrokerageRecordPage(MemberBrokerageRecordPageReqVO pageReqVO) {
+        return memberBrokerageRecordMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public void addBrokerage(Long buyerId, List<BrokerageAddReqDTO> list) {
+        MemberPointConfigDO memberConfig = memberConfigService.getPointConfig();
+        // 0 未启用分销功能
+        if (memberConfig == null || !BooleanUtil.isTrue(memberConfig.getBrokerageEnabled())) {
+            log.warn("[addBrokerage][增加佣金失败:brokerageEnabled 未配置,buyerId({})", buyerId);
+            return;
+        }
+
+        // 1.1 获得一级推广人
+        MemberUserDO firstUser = memberUserService.getBrokerageUser(buyerId);
+        if (firstUser == null || !BooleanUtil.isTrue(firstUser.getBrokerageEnabled())) {
+            return;
+        }
+
+        // 1.2 计算一级分佣
+        addBrokerage(firstUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageFirstPercent(), BrokerageAddReqDTO::getSkuFirstBrokeragePrice);
+
+
+        // 2.1 获得二级推广员
+        MemberUserDO secondUser = memberUserService.getUser(firstUser.getBrokerageUserId());
+        if (secondUser == null || !BooleanUtil.isTrue(secondUser.getBrokerageEnabled())) {
+            return;
+        }
+
+        // 2.2 计算二级分佣
+        addBrokerage(secondUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageSecondPercent(), BrokerageAddReqDTO::getSkuSecondBrokeragePrice);
+    }
+
+    /**
+     * 计算佣金
+     *
+     * @param payPrice          订单支付金额
+     * @param percent           商品 SKU 设置的佣金
+     * @param skuBrokeragePrice 商品的佣金
+     * @return 佣金
+     */
+    int calculateBrokerage(Integer payPrice, Integer percent, Integer skuBrokeragePrice) {
+        // 1. 优先使用商品 SKU 设置的佣金
+        if (skuBrokeragePrice != null && skuBrokeragePrice > 0) {
+            return ObjectUtil.defaultIfNull(skuBrokeragePrice, 0);
+        }
+
+        // 2. 根据订单支付金额计算佣金
+        if (payPrice != null && payPrice > 0 && percent != null && percent > 0) {
+            return NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue();
+        }
+
+        return 0;
+    }
+
+    /**
+     * 增加用户佣金
+     *
+     * @param user                 用户
+     * @param list                 佣金增加参数列表
+     * @param brokerageFrozenDays  冻结天数
+     * @param brokeragePercent     佣金比例
+     * @param skuBrokeragePriceFun 商品 SKU 设置的佣金
+     */
+    private void addBrokerage(MemberUserDO user, List<BrokerageAddReqDTO> list, Integer brokerageFrozenDays,
+                              Integer brokeragePercent, Function<BrokerageAddReqDTO, Integer> skuBrokeragePriceFun) {
+        // 处理冻结时间
+        brokerageFrozenDays = ObjectUtil.defaultIfNull(brokerageFrozenDays, 0);
+        LocalDateTime unfreezeTime = null;
+        if (brokerageFrozenDays > 0) {
+            unfreezeTime = LocalDateTime.now().plusDays(brokerageFrozenDays);
+        }
+
+        // 计算分佣
+        int totalBrokerage = 0;
+        List<MemberBrokerageRecordDO> records = new ArrayList<>();
+        for (BrokerageAddReqDTO dto : list) {
+            int brokeragePerItem = calculateBrokerage(dto.getPayPrice(), brokeragePercent, skuBrokeragePriceFun.apply(dto));
+            if (brokeragePerItem > 0) {
+                int brokerage = brokeragePerItem * dto.getCount();
+                records.add(MemberBrokerageRecordConvert.INSTANCE.convert(user, dto.getBizId(), brokerageFrozenDays, brokerage, unfreezeTime));
+                totalBrokerage += brokerage;
+            }
+        }
+
+        if (records.isEmpty()) {
+            return;
+        }
+
+        // 保存佣金记录
+        memberBrokerageRecordMapper.insertBatch(records);
+
+        if (brokerageFrozenDays > 0) {
+            // 更新用户冻结佣金
+            memberUserService.updateUserFrozenBrokeragePrice(user.getId(), totalBrokerage);
+        } else {
+            // 更新用户可用佣金
+            memberUserService.updateUserBrokeragePrice(user.getId(), totalBrokerage);
+        }
+    }
+
+}

+ 24 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java

@@ -166,4 +166,28 @@ public interface MemberUserService {
      * @param point  积分数量
      * @param point  积分数量
      */
      */
     void updateUserPoint(Long userId, Integer point);
     void updateUserPoint(Long userId, Integer point);
+
+    /**
+     * 获得用户的推广人
+     *
+     * @param id 用户编号
+     * @return 用户的推广人
+     */
+    MemberUserDO getBrokerageUser(Long id);
+
+    /**
+     * 增加用户佣金
+     *
+     * @param id             用户编号
+     * @param brokeragePrice 用户可用佣金
+     */
+    void updateUserBrokeragePrice(Long id, int brokeragePrice);
+
+    /**
+     * 增加用户佣金
+     *
+     * @param id                   用户编号
+     * @param frozenBrokeragePrice 用户冻结佣金
+     */
+    void updateUserFrozenBrokeragePrice(Long id, int frozenBrokeragePrice);
 }
 }

+ 22 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java

@@ -30,6 +30,7 @@ import javax.validation.Valid;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.List;
 import java.util.List;
+import java.util.Optional;
 
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
@@ -259,4 +260,25 @@ public class MemberUserServiceImpl implements MemberUserService {
         memberUserMapper.updateById(new MemberUserDO().setId(userId).setPoint(point));
         memberUserMapper.updateById(new MemberUserDO().setId(userId).setPoint(point));
     }
     }
 
 
+    @Override
+    public MemberUserDO getBrokerageUser(Long id) {
+        return Optional.ofNullable(id)
+                .map(this::getUser)
+                .map(MemberUserDO::getBrokerageUserId)
+                .map(this::getUser)
+                .orElse(null);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateUserBrokeragePrice(Long id, int brokeragePrice) {
+        memberUserMapper.updateBrokeragePriceIncr(id, brokeragePrice);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateUserFrozenBrokeragePrice(Long id, int frozenBrokeragePrice) {
+        memberUserMapper.updateFrozenBrokeragePriceIncr(id, frozenBrokeragePrice);
+    }
+
 }
 }

+ 116 - 0
yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordServiceImplTest.java

@@ -0,0 +1,116 @@
+package cn.iocoder.yudao.module.member.service.brokerage.record;
+
+import cn.hutool.core.util.NumberUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
+import cn.iocoder.yudao.module.member.dal.mysql.brokerage.record.MemberBrokerageRecordMapper;
+import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.math.RoundingMode;
+
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.hutool.core.util.RandomUtil.randomInt;
+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.randomInteger;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link MemberBrokerageRecordServiceImpl} 的单元测试类
+ *
+ * @author owen
+ */
+@Import(MemberBrokerageRecordServiceImpl.class)
+public class MemberBrokerageRecordServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private MemberBrokerageRecordServiceImpl memberBrokerageRecordService;
+    @Resource
+    private MemberBrokerageRecordMapper memberBrokerageRecordMapper;
+
+    @MockBean
+    private MemberPointConfigService memberPointConfigService;
+    @MockBean
+    private MemberUserService memberUserService;
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetMemberBrokerageRecordPage() {
+        // mock 数据
+        MemberBrokerageRecordDO dbMemberBrokerageRecord = randomPojo(MemberBrokerageRecordDO.class, o -> { // 等会查询到
+            o.setUserId(null);
+            o.setBizType(null);
+            o.setStatus(null);
+            o.setCreateTime(null);
+        });
+        memberBrokerageRecordMapper.insert(dbMemberBrokerageRecord);
+        // 测试 userId 不匹配
+        memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setUserId(null)));
+        // 测试 bizType 不匹配
+        memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setBizType(null)));
+        // 测试 status 不匹配
+        memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setStatus(null)));
+        // 测试 createTime 不匹配
+        memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setCreateTime(null)));
+        // 准备参数
+        MemberBrokerageRecordPageReqVO reqVO = new MemberBrokerageRecordPageReqVO();
+        reqVO.setUserId(null);
+        reqVO.setBizType(null);
+        reqVO.setStatus(null);
+        reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+        // 调用
+        PageResult<MemberBrokerageRecordDO> pageResult = memberBrokerageRecordService.getMemberBrokerageRecordPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbMemberBrokerageRecord, pageResult.getList().get(0));
+    }
+
+    @Test
+    public void testCalculateBrokerage_useSkuBrokeragePrice() {
+        // mock 数据
+        Integer payPrice = randomInteger();
+        Integer percent = randomInt(1, 101);
+        Integer skuBrokeragePrice = randomInt();
+        // 调用
+        int brokerage = memberBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
+        // 断言
+        assertEquals(brokerage, skuBrokeragePrice);
+    }
+
+    @Test
+    public void testCalculateBrokerage_usePercent() {
+        // mock 数据
+        Integer payPrice = randomInteger();
+        Integer percent = randomInt(1, 101);
+        Integer skuBrokeragePrice = randomEle(new Integer[]{0, null});
+        System.out.println("skuBrokeragePrice=" + skuBrokeragePrice);
+        // 调用
+        int brokerage = memberBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
+        // 断言
+        assertEquals(brokerage, NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue());
+    }
+
+    @Test
+    public void testCalculateBrokerage_equalsZero() {
+        // mock 数据
+        Integer payPrice = null;
+        Integer percent = null;
+        Integer skuBrokeragePrice = null;
+        // 调用
+        int brokerage = memberBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
+        // 断言
+        assertEquals(brokerage, 0);
+    }
+}

+ 2 - 1
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/clean.sql

@@ -2,4 +2,5 @@ DELETE FROM "member_user";
 DELETE FROM "member_address";
 DELETE FROM "member_address";
 DELETE FROM "member_tag";
 DELETE FROM "member_tag";
 DELETE FROM "member_level";
 DELETE FROM "member_level";
-DELETE FROM "member_group";
+DELETE FROM "member_group";
+DELETE FROM "member_brokerage_record";

+ 22 - 1
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql

@@ -89,4 +89,25 @@ CREATE TABLE IF NOT EXISTS "member_group"
     "deleted"     bit      NOT NULL DEFAULT FALSE,
     "deleted"     bit      NOT NULL DEFAULT FALSE,
     "tenant_id"      bigint   not null default '0',
     "tenant_id"      bigint   not null default '0',
     PRIMARY KEY ("id")
     PRIMARY KEY ("id")
-) COMMENT '用户分组';
+) COMMENT '用户分组';
+CREATE TABLE IF NOT EXISTS "member_brokerage_record"
+(
+    "id"            int      NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "user_id"       bigint   NOT NULL,
+    "biz_id"        varchar  NOT NULL,
+    "biz_type"      varchar  NOT NULL,
+    "title"         varchar  NOT NULL,
+    "price"         int      NOT NULL,
+    "total_price"   int      NOT NULL,
+    "description"   varchar  NOT NULL,
+    "status"        varchar  NOT NULL,
+    "frozen_days"   int      NOT NULL,
+    "unfreeze_time" 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 '佣金记录';