Commit a58d7d63ed2dce437c155e4c45d304785ce3d671

Authored by 芋道源码
Committed by Gitee
2 parents eae8218f 090058a2

!606 会员:分销佣金计算

Merge pull request !606 from 疯狂的世界/brokerage
Showing 31 changed files with 1443 additions and 4 deletions
sql/mysql/brokerage.sql 0 → 100644
  1 +-- 增加配置
  2 +alter table member_point_config
  3 + add column brokerage_enabled bit default 1 not null comment '是否启用分佣';
  4 +alter table member_point_config
  5 + add column brokerage_enabled_condition tinyint default 0 not null comment '分佣模式:0-人人分销 1-指定分销';
  6 +alter table member_point_config
  7 + add column brokerage_bind_mode tinyint default 0 not null comment '分销关系绑定模式: 0-没有推广人,1-新用户';
  8 +alter table member_point_config
  9 + add column brokerage_post_urls varchar(2000) null comment '分销海报图地址数组';
  10 +alter table member_point_config
  11 + add column brokerage_first_percent int not null comment '一级返佣比例';
  12 +alter table member_point_config
  13 + add column brokerage_second_percent int not null comment '二级返佣比例';
  14 +alter table member_point_config
  15 + add column brokerage_withdraw_min_price int not null comment '用户提现最低金额';
  16 +alter table member_point_config
  17 + add column brokerage_bank_names varchar(200) not null comment '提现银行(字典类型=brokerage_bank_name)';
  18 +alter table member_point_config
  19 + add column brokerage_frozen_days int default 7 not null comment '佣金冻结时间(天)';
  20 +alter table member_point_config
  21 + add column brokerage_withdraw_type varchar(32) default '1,2,3,4' not null comment '提现方式:1-钱包;2-银行卡;3-微信;4-支付宝';
  22 +
  23 +-- 用户表增加分销相关字段
  24 +alter table member_user
  25 + add column brokerage_user_id bigint not null comment '推广员编号';
  26 +alter table member_user
  27 + add column brokerage_bind_time datetime null comment '推广员绑定时间';
  28 +alter table member_user
  29 + add column brokerage_enabled bit default 1 not null comment '是否成为推广员';
  30 +alter table member_user
  31 + add column brokerage_time datetime null comment '成为分销员时间';
  32 +alter table member_user
  33 + add column brokerage_price int default 0 not null comment '可用佣金';
  34 +alter table member_user
  35 + add column frozen_brokerage_price int default 0 not null comment '冻结佣金';
  36 +
  37 +create index idx_invite_user_id on member_user (brokerage_user_id) comment '推广员编号';
  38 +create index idx_agent on member_user (brokerage_enabled) comment '是否成为推广员';
  39 +
  40 +
  41 +create table member_brokerage_record
  42 +(
  43 + id int auto_increment comment '编号'
  44 + primary key,
  45 + user_id bigint not null comment '用户编号',
  46 + biz_id varchar(64) default '' not null comment '业务编号',
  47 + biz_type tinyint default 0 not null comment '业务类型:0-订单,1-提现',
  48 + title varchar(64) default '' not null comment '标题',
  49 + price int default 0 not null comment '金额',
  50 + total_price int default 0 not null comment '当前总佣金',
  51 + description varchar(500) default '' not null comment '说明',
  52 + status tinyint default 0 not null comment '状态:0-待结算,1-已结算,2-已取消',
  53 + frozen_days int default 0 not null comment '冻结时间(天)',
  54 + unfreeze_time datetime null comment '解冻时间',
  55 + creator varchar(64) collate utf8mb4_general_ci default '' null comment '创建者',
  56 + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
  57 + updater varchar(64) collate utf8mb4_general_ci default '' null comment '更新者',
  58 + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
  59 + deleted bit default b'0' not null comment '是否删除',
  60 + tenant_id bigint default 0 not null comment '租户编号'
  61 +)
  62 + comment '佣金记录';
  63 +
  64 +create index idx_user_id on member_brokerage_record (user_id) comment '用户编号';
  65 +create index idx_biz on member_brokerage_record (biz_type, biz_id) comment '业务';
  66 +create index idx_status on member_brokerage_record (status) comment '状态';
  67 +
  68 +
  69 +create table member_brokerage_withdraw
  70 +(
  71 + id int auto_increment comment '编号'
  72 + primary key,
  73 + user_id bigint not null comment '用户编号',
  74 + price int default 0 not null comment '提现金额',
  75 + fee_price int default 0 not null comment '提现手续费',
  76 + total_price int default 0 not null comment '当前总佣金',
  77 + type tinyint default 0 not null comment '提现类型:1-钱包;2-银行卡;3-微信;4-支付宝',
  78 + name varchar(64) null comment '真实姓名',
  79 + account_no varchar(64) null comment '账号',
  80 + bank_name varchar(100) null comment '银行名称',
  81 + bank_address varchar(200) null comment '开户地址',
  82 + account_qr_code_url varchar(512) null comment '收款码',
  83 + status tinyint(2) default 0 not null comment '状态:0-审核中,10-审核通过 20-审核不通过;预留:11 - 提现成功;21-提现失败',
  84 + audit_reason varchar(128) null comment '审核驳回原因',
  85 + audit_time datetime null comment '审核时间',
  86 + remark varchar(500) null comment '备注',
  87 + creator varchar(64) collate utf8mb4_general_ci default '' null comment '创建者',
  88 + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
  89 + updater varchar(64) collate utf8mb4_general_ci default '' null comment '更新者',
  90 + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
  91 + deleted bit default b'0' not null comment '是否删除',
  92 + tenant_id bigint default 0 not null comment '租户编号'
  93 +)
  94 + comment '佣金提现';
  95 +
  96 +create index idx_user_id on member_brokerage_withdraw (user_id) comment '用户编号';
  97 +create index idx_audit_status on member_brokerage_withdraw (status) comment '状态';
  98 +
  99 +-- 增加字典
  100 +insert into system_dict_type(type, name)
  101 +values ('brokerage_enabled_condition', '分佣模式');
  102 +insert into system_dict_data(dict_type, label, value, sort, remark)
  103 +values ('brokerage_enabled_condition', '人人分销', 0, 0, '所有用户都可以分销'),
  104 + ('brokerage_enabled_condition', '指定分销', 1, 1, '仅可后台手动设置推广员');
  105 +
  106 +insert into system_dict_type(type, name)
  107 +values ('brokerage_bind_mode', '分销关系绑定模式');
  108 +insert into system_dict_data(dict_type, label, value, sort, remark)
  109 +values ('brokerage_bind_mode', '没有推广人', 0, 0, '只要用户没有推广人,随时都可以绑定推广关系'),
  110 + ('brokerage_bind_mode', '新用户', 1, 1, '仅新用户注册时才能绑定推广关系');
  111 +
  112 +insert into system_dict_type(type, name)
  113 +values ('brokerage_withdraw_type', '佣金提现类型');
  114 +insert into system_dict_data(dict_type, label, value, sort)
  115 +values ('brokerage_withdraw_type', '钱包', 1, 1),
  116 + ('brokerage_withdraw_type', '银行卡', 2, 2),
  117 + ('brokerage_withdraw_type', '微信', 3, 3),
  118 + ('brokerage_withdraw_type', '支付宝', 4, 4);
  119 +
  120 +insert into system_dict_type(type, name)
  121 +values ('brokerage_record_biz_type', '佣金记录业务类型');
  122 +insert into system_dict_data(dict_type, label, value, sort)
  123 +values ('brokerage_record_biz_type', '订单返佣', 0, 0),
  124 + ('brokerage_record_biz_type', '申请提现', 1, 1);
  125 +
  126 +insert into system_dict_type(type, name)
  127 +values ('brokerage_record_status', '佣金记录状态');
  128 +insert into system_dict_data(dict_type, label, value, sort)
  129 +values ('brokerage_record_status', '待结算', 0, 0),
  130 + ('brokerage_record_status', '已结算', 1, 1),
  131 + ('brokerage_record_status', '已取消', 2, 2);
  132 +
  133 +insert into system_dict_type(type, name)
  134 +values ('brokerage_withdraw_status', '佣金提现状态');
  135 +insert into system_dict_data(dict_type, label, value, sort)
  136 +values ('brokerage_withdraw_status', '审核中', 0, 0),
  137 + ('brokerage_withdraw_status', '审核通过', 10, 10),
  138 + ('brokerage_withdraw_status', '提现成功', 11, 11),
  139 + ('brokerage_withdraw_status', '审核不通过', 20, 20),
  140 + ('brokerage_withdraw_status', '提现失败', 21, 21);
  141 +
  142 +insert into system_dict_type(type, name)
  143 +values ('brokerage_bank_name', '佣金提现银行');
  144 +insert into system_dict_data(dict_type, label, value, sort)
  145 +values ('brokerage_bank_name', '工商银行', 0, 0),
  146 + ('brokerage_bank_name', '建设银行', 1, 1),
  147 + ('brokerage_bank_name', '农业银行', 2, 2),
  148 + ('brokerage_bank_name', '中国银行', 3, 3),
  149 + ('brokerage_bank_name', '交通银行', 4, 4),
  150 + ('brokerage_bank_name', '招商银行', 5, 5);
  151 +
  152 +-- 增加菜单:分销员管理
  153 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
  154 +VALUES ('分销员', '', 2, 7, 2262, 'brokerage', 'user', 'member/brokerage/user/index', 0, 'MemberBrokerageUser');
  155 +-- 按钮父菜单ID
  156 +SELECT @parentId := LAST_INSERT_ID();
  157 +-- 按钮 SQL
  158 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  159 +VALUES ('分销员查询', 'member:brokerage-user:query', 3, 1, @parentId, '', '', '', 0);
  160 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  161 +VALUES ('分销员创建', 'member:brokerage-user:create', 3, 2, @parentId, '', '', '', 0);
  162 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  163 +VALUES ('分销员更新', 'member:brokerage-user:update', 3, 3, @parentId, '', '', '', 0);
  164 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  165 +VALUES ('分销员删除', 'member:brokerage-user:delete', 3, 4, @parentId, '', '', '', 0);
  166 +
  167 +-- 增加菜单:佣金记录
  168 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
  169 +VALUES ('佣金记录', '', 2, 8, 2262, 'brokerage-record', 'list', 'member/brokerage/record/index', 0,
  170 + 'MemberBrokerageRecord');
  171 +-- 按钮父菜单ID
  172 +-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
  173 +SELECT @parentId := LAST_INSERT_ID();
  174 +-- 按钮 SQL
  175 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  176 +VALUES ('佣金记录查询', 'member:member-brokerage-record:query', 3, 1, @parentId, '', 'table', '', 0);
  177 +
  178 +-- 增加菜单:佣金提现
  179 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
  180 +VALUES ('佣金提现', '', 2, 9, 2262, 'brokerage-withdraw', '', 'member/brokerage/withdraw/index', 0,
  181 + 'MemberBrokerageWithdraw');
  182 +
  183 +-- 按钮父菜单ID
  184 +-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
  185 +SELECT @parentId := LAST_INSERT_ID();
  186 +
  187 +-- 按钮 SQL
  188 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  189 +VALUES ('佣金提现查询', 'member:brokerage-withdraw:query', 3, 1, @parentId, '', '', '', 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 { @@ -60,4 +60,13 @@ public class ProductSkuRespDTO {
60 */ 60 */
61 private Double volume; 61 private Double volume;
62 62
  63 + /**
  64 + * 一级分销的佣金,单位:分
  65 + */
  66 + private Integer subCommissionFirstPrice;
  67 + /**
  68 + * 二级分销的佣金,单位:分
  69 + */
  70 + private Integer subCommissionSecondPrice;
  71 +
63 } 72 }
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; @@ -7,11 +7,13 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils;
7 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; 7 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
8 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; 8 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
9 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; 9 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
  10 +import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
10 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; 11 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
11 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; 12 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
12 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants; 13 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
13 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; 14 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
14 import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; 15 import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
  16 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
15 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; 17 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
16 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; 18 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
17 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO; 19 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
@@ -273,4 +275,10 @@ public interface TradeOrderConvert { @@ -273,4 +275,10 @@ public interface TradeOrderConvert {
273 275
274 TradeOrderDO convert(TradeOrderRemarkReqVO reqVO); 276 TradeOrderDO convert(TradeOrderRemarkReqVO reqVO);
275 277
  278 + default BrokerageAddReqDTO convert(TradeOrderItemDO item, ProductSkuRespDTO sku) {
  279 + return new BrokerageAddReqDTO().setBizId(String.valueOf(item.getId()))
  280 + .setPayPrice(item.getPayPrice()).setCount(item.getCount())
  281 + .setSkuFirstBrokeragePrice(sku.getSubCommissionFirstPrice())
  282 + .setSkuSecondBrokeragePrice(sku.getSubCommissionSecondPrice());
  283 + }
276 } 284 }
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; @@ -12,6 +12,8 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
12 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 12 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
13 import cn.iocoder.yudao.module.member.api.address.AddressApi; 13 import cn.iocoder.yudao.module.member.api.address.AddressApi;
14 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; 14 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
  15 +import cn.iocoder.yudao.module.member.api.brokerage.BrokerageApi;
  16 +import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
15 import cn.iocoder.yudao.module.member.api.level.MemberLevelApi; 17 import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
16 import cn.iocoder.yudao.module.member.api.point.MemberPointApi; 18 import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
17 import cn.iocoder.yudao.module.member.api.user.MemberUserApi; 19 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
@@ -118,6 +120,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -118,6 +120,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
118 @Resource 120 @Resource
119 private MemberPointApi memberPointApi; 121 private MemberPointApi memberPointApi;
120 @Resource 122 @Resource
  123 + private BrokerageApi brokerageApi;
  124 + @Resource
121 private ProductCommentApi productCommentApi; 125 private ProductCommentApi productCommentApi;
122 126
123 @Resource 127 @Resource
@@ -365,6 +369,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -365,6 +369,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
365 getSelf().addUserPointAsync(order.getUserId(), order.getPayPrice(), order.getId()); 369 getSelf().addUserPointAsync(order.getUserId(), order.getPayPrice(), order.getId());
366 // 增加用户经验 370 // 增加用户经验
367 getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId()); 371 getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
  372 + // 增加用户佣金
  373 + getSelf().addBrokerageAsync(order.getUserId(), order.getId());
368 } 374 }
369 375
370 /** 376 /**
@@ -742,6 +748,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -742,6 +748,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
742 memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId)); 748 memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
743 } 749 }
744 750
  751 +
  752 + @Async
  753 + protected void addBrokerageAsync(Long userId, Long orderId) {
  754 + List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
  755 + List<BrokerageAddReqDTO> list = convertList(orderItems,
  756 + item -> TradeOrderConvert.INSTANCE.convert(item, productSkuApi.getSku(item.getSkuId())));
  757 + brokerageApi.addBrokerage(userId, list);
  758 + }
  759 +
745 /** 760 /**
746 * 获得自身的代理对象,解决 AOP 生效问题 761 * 获得自身的代理对象,解决 AOP 生效问题
747 * 762 *
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/BrokerageApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.api.brokerage;
  2 +
  3 +import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
  4 +
  5 +import java.util.List;
  6 +
  7 +/**
  8 + * 佣金 API 接口
  9 + *
  10 + * @author owen
  11 + */
  12 +public interface BrokerageApi {
  13 +
  14 + /**
  15 + * 增加佣金
  16 + *
  17 + * @param userId 会员ID
  18 + * @param list 请求参数列表
  19 + */
  20 + void addBrokerage(Long userId, List<BrokerageAddReqDTO> list);
  21 +
  22 + /**
  23 + * 取消佣金
  24 + *
  25 + * @param userId 会员ID
  26 + * @param bizId 业务编号
  27 + */
  28 + void cancelBrokerage(Long userId, String bizId);
  29 +
  30 +}
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/dto/BrokerageAddReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.api.brokerage.dto;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +/**
  8 + * 佣金 增加 Request DTO
  9 + * @author owen
  10 + */
  11 +@Data
  12 +@NoArgsConstructor
  13 +@AllArgsConstructor
  14 +public class BrokerageAddReqDTO {
  15 + /**
  16 + * 业务ID
  17 + */
  18 + private String bizId;
  19 + /**
  20 + * 商品支付价格
  21 + */
  22 + private Integer payPrice;
  23 + /**
  24 + * SKU 一级佣金
  25 + */
  26 + private Integer skuFirstBrokeragePrice;
  27 + /**
  28 + * SKU 二级佣金
  29 + */
  30 + private Integer skuSecondBrokeragePrice;
  31 + /**
  32 + * 购买数量
  33 + */
  34 + private Integer count;
  35 +}
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageBindModeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 分销关系绑定模式枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageBindModeEnum implements IntArrayValuable {
  17 +
  18 + /**
  19 + * 只要用户没有推广人,随时都可以绑定分销关系
  20 + */
  21 + ANYTIME(0, "没有推广人"),
  22 + /**
  23 + * 仅新用户注册时才能绑定推广关系
  24 + */
  25 + REGISTER(1, "新用户"),
  26 + ;
  27 +
  28 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageBindModeEnum::getMode).toArray();
  29 +
  30 + /**
  31 + * 模式
  32 + */
  33 + private final Integer mode;
  34 + /**
  35 + * 名字
  36 + */
  37 + private final String name;
  38 +
  39 + @Override
  40 + public int[] array() {
  41 + return ARRAYS;
  42 + }
  43 +
  44 +}
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageEnabledConditionEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 分佣模式枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageEnabledConditionEnum implements IntArrayValuable {
  17 +
  18 + /**
  19 + * 所有用户都可以分销
  20 + */
  21 + ALL(0, "人人分销"),
  22 + /**
  23 + * 仅可后台手动设置推广员
  24 + */
  25 + ADMIN(1, "指定分销"),
  26 + ;
  27 +
  28 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageEnabledConditionEnum::getCondition).toArray();
  29 +
  30 + /**
  31 + * 模式
  32 + */
  33 + private final Integer condition;
  34 + /**
  35 + * 名字
  36 + */
  37 + private final String name;
  38 +
  39 + @Override
  40 + public int[] array() {
  41 + return ARRAYS;
  42 + }
  43 +
  44 +}
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageRecordBizTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 佣金记录业务类型枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageRecordBizTypeEnum implements IntArrayValuable {
  17 +
  18 + ORDER(0, "获得推广佣金", "获得推广佣金 {}", true),
  19 + WITHDRAW(1, "提现申请", "提现申请扣除佣金 {}", false),
  20 + ;
  21 +
  22 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageRecordBizTypeEnum::getType).toArray();
  23 +
  24 + /**
  25 + * 类型
  26 + */
  27 + private final Integer type;
  28 + /**
  29 + * 标题
  30 + */
  31 + private final String title;
  32 + /**
  33 + * 描述
  34 + */
  35 + private final String description;
  36 + /**
  37 + * 是否为增加佣金
  38 + */
  39 + private final boolean add;
  40 +
  41 + @Override
  42 + public int[] array() {
  43 + return ARRAYS;
  44 + }
  45 +
  46 +}
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageRecordStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 佣金记录状态枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageRecordStatusEnum implements IntArrayValuable {
  17 +
  18 + WAIT_SETTLEMENT(0, "待结算"),
  19 + SETTLEMENT(1, "已结算"),
  20 + CANCEL(2, "已取消"),
  21 + ;
  22 +
  23 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageRecordStatusEnum::getStatus).toArray();
  24 +
  25 + /**
  26 + * 状态
  27 + */
  28 + private final Integer status;
  29 + /**
  30 + * 名字
  31 + */
  32 + private final String name;
  33 +
  34 + @Override
  35 + public int[] array() {
  36 + return ARRAYS;
  37 + }
  38 +
  39 +}
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageWithdrawStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 佣金提现状态枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageWithdrawStatusEnum implements IntArrayValuable {
  17 +
  18 + AUDITING(0, "审核中"),
  19 + AUDIT_SUCCESS(10, "审核通过"),
  20 + WITHDRAW_SUCCESS(11, "提现成功"),
  21 + AUDIT_FAIL(20, "审核不通过"),
  22 + WITHDRAW_FAIL(21, "提现失败"),
  23 + ;
  24 +
  25 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawStatusEnum::getStatus).toArray();
  26 +
  27 + /**
  28 + * 状态
  29 + */
  30 + private final Integer status;
  31 + /**
  32 + * 名字
  33 + */
  34 + private final String name;
  35 +
  36 + @Override
  37 + public int[] array() {
  38 + return ARRAYS;
  39 + }
  40 +
  41 +}
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageWithdrawTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.enums.brokerage;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 佣金提现类型枚举
  11 + *
  12 + * @author owen
  13 + */
  14 +@AllArgsConstructor
  15 +@Getter
  16 +public enum BrokerageWithdrawTypeEnum implements IntArrayValuable {
  17 +
  18 + WALLET(1, "钱包"),
  19 + BANK(2, "银行卡"),
  20 + WECHAT(3, "微信"),
  21 + ALIPAY(4, "支付宝"),
  22 + ;
  23 +
  24 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawTypeEnum::getType).toArray();
  25 +
  26 + /**
  27 + * 类型
  28 + */
  29 + private final Integer type;
  30 + /**
  31 + * 名字
  32 + */
  33 + private final String name;
  34 +
  35 + @Override
  36 + public int[] array() {
  37 + return ARRAYS;
  38 + }
  39 +
  40 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/BrokerageApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.api.brokerage;
  2 +
  3 +import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
  4 +import cn.iocoder.yudao.module.member.service.brokerage.record.MemberBrokerageRecordService;
  5 +import org.springframework.stereotype.Service;
  6 +import org.springframework.validation.annotation.Validated;
  7 +
  8 +import javax.annotation.Resource;
  9 +import java.util.List;
  10 +
  11 +/**
  12 + * 佣金 API 实现类
  13 + *
  14 + * @author owen
  15 + */
  16 +@Service
  17 +@Validated
  18 +public class BrokerageApiImpl implements BrokerageApi {
  19 +
  20 + @Resource
  21 + private MemberBrokerageRecordService memberBrokerageRecordService;
  22 +
  23 + @Override
  24 + public void addBrokerage(Long userId, List<BrokerageAddReqDTO> list) {
  25 + memberBrokerageRecordService.addBrokerage(userId, list);
  26 + }
  27 +
  28 + @Override
  29 + public void cancelBrokerage(Long userId, String bizId) {
  30 +
  31 + }
  32 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/MemberBrokerageRecordController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.admin.brokerage.record;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
  6 +import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordRespVO;
  7 +import cn.iocoder.yudao.module.member.convert.brokerage.record.MemberBrokerageRecordConvert;
  8 +import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
  9 +import cn.iocoder.yudao.module.member.service.brokerage.record.MemberBrokerageRecordService;
  10 +import io.swagger.v3.oas.annotations.Operation;
  11 +import io.swagger.v3.oas.annotations.Parameter;
  12 +import io.swagger.v3.oas.annotations.tags.Tag;
  13 +import org.springframework.security.access.prepost.PreAuthorize;
  14 +import org.springframework.validation.annotation.Validated;
  15 +import org.springframework.web.bind.annotation.GetMapping;
  16 +import org.springframework.web.bind.annotation.RequestMapping;
  17 +import org.springframework.web.bind.annotation.RequestParam;
  18 +import org.springframework.web.bind.annotation.RestController;
  19 +
  20 +import javax.annotation.Resource;
  21 +import javax.validation.Valid;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +
  25 +@Tag(name = "管理后台 - 佣金记录")
  26 +@RestController
  27 +@RequestMapping("/member/member-brokerage-record")
  28 +@Validated
  29 +public class MemberBrokerageRecordController {
  30 +
  31 + @Resource
  32 + private MemberBrokerageRecordService memberBrokerageRecordService;
  33 +
  34 + @GetMapping("/get")
  35 + @Operation(summary = "获得佣金记录")
  36 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  37 + @PreAuthorize("@ss.hasPermission('member:member-brokerage-record:query')")
  38 + public CommonResult<MemberBrokerageRecordRespVO> getMemberBrokerageRecord(@RequestParam("id") Integer id) {
  39 + MemberBrokerageRecordDO memberBrokerageRecord = memberBrokerageRecordService.getMemberBrokerageRecord(id);
  40 + return success(MemberBrokerageRecordConvert.INSTANCE.convert(memberBrokerageRecord));
  41 + }
  42 +
  43 + @GetMapping("/page")
  44 + @Operation(summary = "获得佣金记录分页")
  45 + @PreAuthorize("@ss.hasPermission('member:member-brokerage-record:query')")
  46 + public CommonResult<PageResult<MemberBrokerageRecordRespVO>> getMemberBrokerageRecordPage(@Valid MemberBrokerageRecordPageReqVO pageVO) {
  47 + PageResult<MemberBrokerageRecordDO> pageResult = memberBrokerageRecordService.getMemberBrokerageRecordPage(pageVO);
  48 + return success(MemberBrokerageRecordConvert.INSTANCE.convertPage(pageResult));
  49 + }
  50 +
  51 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +/**
  13 + * 佣金记录 Base VO,提供给添加、修改、详细的子 VO 使用
  14 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  15 + */
  16 +@Data
  17 +public class MemberBrokerageRecordBaseVO {
  18 +
  19 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25973")
  20 + @NotNull(message = "用户编号不能为空")
  21 + private Long userId;
  22 +
  23 + @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23353")
  24 + @NotNull(message = "业务编号不能为空")
  25 + private String bizId;
  26 +
  27 + @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  28 + @NotNull(message = "业务类型不能为空")
  29 + private Integer bizType;
  30 +
  31 + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
  32 + @NotNull(message = "标题不能为空")
  33 + private String title;
  34 +
  35 + @Schema(description = "金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "28731")
  36 + @NotNull(message = "金额不能为空")
  37 + private Integer price;
  38 +
  39 + @Schema(description = "当前总佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "13226")
  40 + @NotNull(message = "当前总佣金不能为空")
  41 + private Integer totalPrice;
  42 +
  43 + @Schema(description = "说明", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
  44 + @NotNull(message = "说明不能为空")
  45 + private String description;
  46 +
  47 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  48 + @NotNull(message = "状态不能为空")
  49 + private Integer status;
  50 +
  51 + @Schema(description = "冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED)
  52 + @NotNull(message = "冻结时间(天)不能为空")
  53 + private Integer frozenDays;
  54 +
  55 + @Schema(description = "解冻时间")
  56 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  57 + private LocalDateTime unfreezeTime;
  58 +
  59 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  13 +
  14 +@Schema(description = "管理后台 - 佣金记录分页 Request VO")
  15 +@Data
  16 +@EqualsAndHashCode(callSuper = true)
  17 +@ToString(callSuper = true)
  18 +public class MemberBrokerageRecordPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "用户编号", example = "25973")
  21 + private Long userId;
  22 +
  23 + @Schema(description = "业务类型", example = "1")
  24 + private Integer bizType;
  25 +
  26 + @Schema(description = "状态", example = "1")
  27 + private Integer status;
  28 +
  29 + @Schema(description = "创建时间")
  30 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  31 + private LocalDateTime[] createTime;
  32 +
  33 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - 佣金记录 Response VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class MemberBrokerageRecordRespVO extends MemberBrokerageRecordBaseVO {
  15 +
  16 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28896")
  17 + private Integer id;
  18 +
  19 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  20 + private LocalDateTime createTime;
  21 +
  22 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigBaseVO.java
1 package cn.iocoder.yudao.module.member.controller.admin.point.vo.config; 1 package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
2 2
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageBindModeEnum;
  5 +import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageEnabledConditionEnum;
  6 +import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageWithdrawTypeEnum;
3 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 8 import lombok.Data;
  9 +import org.hibernate.validator.constraints.Range;
5 10
  11 +import javax.validation.constraints.NotEmpty;
6 import javax.validation.constraints.NotNull; 12 import javax.validation.constraints.NotNull;
  13 +import javax.validation.constraints.PositiveOrZero;
  14 +import java.util.List;
7 15
8 /** 16 /**
9 * 会员积分配置 Base VO,提供给添加、修改、详细的子 VO 使用 17 * 会员积分配置 Base VO,提供给添加、修改、详细的子 VO 使用
@@ -28,4 +36,53 @@ public class MemberPointConfigBaseVO { @@ -28,4 +36,53 @@ public class MemberPointConfigBaseVO {
28 @NotNull(message = "1 元赠送积分不能为空") 36 @NotNull(message = "1 元赠送积分不能为空")
29 private Integer tradeGivePoint; 37 private Integer tradeGivePoint;
30 38
  39 + // ========== 分销相关 ==========
  40 +
  41 + @Schema(description = "是否启用分佣", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  42 + @NotNull(message = "是否启用分佣不能为空")
  43 + private Boolean brokerageEnabled;
  44 +
  45 + @Schema(description = "分佣模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  46 + @NotNull(message = "分佣模式不能为空")
  47 + @InEnum(value = BrokerageEnabledConditionEnum.class, message = "分佣模式必须是 {value}")
  48 + private Integer brokerageEnabledCondition;
  49 +
  50 + @Schema(description = "分销关系绑定模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  51 + @NotNull(message = "分销关系绑定模式不能为空")
  52 + @InEnum(value = BrokerageBindModeEnum.class, message = "分销关系绑定模式必须是 {value}")
  53 + private Integer brokerageBindMode;
  54 +
  55 + @Schema(description = "分销海报图地址数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/yudao.jpg]")
  56 + private List<String> brokeragePostUrls;
  57 +
  58 + @Schema(description = "一级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  59 + @NotNull(message = "一级返佣比例不能为空")
  60 + @Range(min = 0, max = 100, message = "一级返佣比例必须在 0 - 100 之间")
  61 + private Integer brokerageFirstPercent;
  62 +
  63 + @Schema(description = "二级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  64 + @NotNull(message = "二级返佣比例不能为空")
  65 + @Range(min = 0, max = 100, message = "二级返佣比例必须在 0 - 100 之间")
  66 + private Integer brokerageSecondPercent;
  67 +
  68 + @Schema(description = "用户提现最低金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
  69 + @NotNull(message = "用户提现最低金额不能为空")
  70 + @PositiveOrZero(message = "用户提现最低金额不能是负数")
  71 + private Integer brokerageWithdrawMinPrice;
  72 +
  73 + @Schema(description = "提现银行", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
  74 + @NotEmpty(message = "提现银行不能为空")
  75 + private List<Integer> brokerageBankNames;
  76 +
  77 + @Schema(description = "佣金冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
  78 + @NotNull(message = "佣金冻结时间(天)不能为空")
  79 + @PositiveOrZero(message = "佣金冻结时间不能是负数")
  80 + private Integer brokerageFrozenDays;
  81 +
  82 + @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
  83 + @NotNull(message = "提现方式不能为空")
  84 + @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
  85 + private List<Integer> brokerageWithdrawType;
  86 +
  87 +
31 } 88 }
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/brokerage/record/MemberBrokerageRecordConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.convert.brokerage.record;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordRespVO;
  6 +import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
  7 +import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
  8 +import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordBizTypeEnum;
  9 +import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordStatusEnum;
  10 +import org.mapstruct.Mapper;
  11 +import org.mapstruct.factory.Mappers;
  12 +
  13 +import java.time.LocalDateTime;
  14 +import java.util.List;
  15 +
  16 +/**
  17 + * 佣金记录 Convert
  18 + *
  19 + * @author owen
  20 + */
  21 +@Mapper
  22 +public interface MemberBrokerageRecordConvert {
  23 +
  24 + MemberBrokerageRecordConvert INSTANCE = Mappers.getMapper(MemberBrokerageRecordConvert.class);
  25 +
  26 + MemberBrokerageRecordRespVO convert(MemberBrokerageRecordDO bean);
  27 +
  28 + List<MemberBrokerageRecordRespVO> convertList(List<MemberBrokerageRecordDO> list);
  29 +
  30 + PageResult<MemberBrokerageRecordRespVO> convertPage(PageResult<MemberBrokerageRecordDO> page);
  31 +
  32 + default MemberBrokerageRecordDO convert(MemberUserDO user, String bizId, int brokerageFrozenDays, int brokerage, LocalDateTime unfreezeTime) {
  33 + // 不冻结时,佣金直接就是结算状态
  34 + Integer status = brokerageFrozenDays > 0
  35 + ? BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus()
  36 + : BrokerageRecordStatusEnum.SETTLEMENT.getStatus();
  37 + return new MemberBrokerageRecordDO()
  38 + .setUserId(user.getId())
  39 + .setBizType(BrokerageRecordBizTypeEnum.ORDER.getType())
  40 + .setBizId(bizId)
  41 + .setPrice(brokerage)
  42 + .setTotalPrice(user.getBrokeragePrice())
  43 + .setTitle(BrokerageRecordBizTypeEnum.ORDER.getTitle())
  44 + .setDescription(StrUtil.format(BrokerageRecordBizTypeEnum.ORDER.getDescription(), String.valueOf(brokerage / 100.0)))
  45 + .setStatus(status)
  46 + .setFrozenDays(brokerageFrozenDays)
  47 + .setUnfreezeTime(unfreezeTime);
  48 + }
  49 +
  50 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/brokerage/record/MemberBrokerageRecordDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordBizTypeEnum;
  5 +import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordStatusEnum;
  6 +import com.baomidou.mybatisplus.annotation.KeySequence;
  7 +import com.baomidou.mybatisplus.annotation.TableId;
  8 +import com.baomidou.mybatisplus.annotation.TableName;
  9 +import lombok.*;
  10 +
  11 +import java.time.LocalDateTime;
  12 +
  13 +/**
  14 + * 佣金记录 DO
  15 + *
  16 + * @author owen
  17 + */
  18 +@TableName("member_brokerage_record")
  19 +@KeySequence("member_brokerage_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  20 +@Data
  21 +@EqualsAndHashCode(callSuper = true)
  22 +@ToString(callSuper = true)
  23 +@Builder
  24 +@NoArgsConstructor
  25 +@AllArgsConstructor
  26 +public class MemberBrokerageRecordDO extends BaseDO {
  27 +
  28 + /**
  29 + * 编号
  30 + */
  31 + @TableId
  32 + private Integer id;
  33 + /**
  34 + * 用户编号
  35 + */
  36 + private Long userId;
  37 + /**
  38 + * 业务编号
  39 + */
  40 + private String bizId;
  41 + /**
  42 + * 业务类型
  43 + * <p>
  44 + * 枚举 {@link BrokerageRecordBizTypeEnum 对应的类}
  45 + */
  46 + private Integer bizType;
  47 + /**
  48 + * 标题
  49 + */
  50 + private String title;
  51 + /**
  52 + * 金额
  53 + */
  54 + private Integer price;
  55 + /**
  56 + * 当前总佣金
  57 + */
  58 + private Integer totalPrice;
  59 + /**
  60 + * 说明
  61 + */
  62 + private String description;
  63 + /**
  64 + * 状态
  65 + * <p>
  66 + * 枚举 {@link BrokerageRecordStatusEnum 对应的类}
  67 + */
  68 + private Integer status;
  69 + /**
  70 + * 冻结时间(天)
  71 + */
  72 + private Integer frozenDays;
  73 + /**
  74 + * 解冻时间
  75 + */
  76 + private LocalDateTime unfreezeTime;
  77 +
  78 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java
1 package cn.iocoder.yudao.module.member.dal.dataobject.point; 1 package cn.iocoder.yudao.module.member.dal.dataobject.point;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
  5 +import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageBindModeEnum;
  6 +import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageEnabledConditionEnum;
  7 +import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageWithdrawTypeEnum;
4 import com.baomidou.mybatisplus.annotation.KeySequence; 8 import com.baomidou.mybatisplus.annotation.KeySequence;
  9 +import com.baomidou.mybatisplus.annotation.TableField;
5 import com.baomidou.mybatisplus.annotation.TableId; 10 import com.baomidou.mybatisplus.annotation.TableId;
6 import com.baomidou.mybatisplus.annotation.TableName; 11 import com.baomidou.mybatisplus.annotation.TableName;
  12 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
7 import lombok.*; 13 import lombok.*;
8 14
9 -import java.math.BigDecimal; 15 +import java.util.List;
10 16
11 /** 17 /**
12 * 会员积分配置 DO 18 * 会员积分配置 DO
13 * 19 *
14 * @author QingX 20 * @author QingX
15 */ 21 */
16 -@TableName("member_point_config") 22 +@TableName(value = "member_point_config", autoResultMap = true)
17 @KeySequence("member_point_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 23 @KeySequence("member_point_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
18 @Data 24 @Data
19 @EqualsAndHashCode(callSuper = true) 25 @EqualsAndHashCode(callSuper = true)
@@ -47,4 +53,56 @@ public class MemberPointConfigDO extends BaseDO { @@ -47,4 +53,56 @@ public class MemberPointConfigDO extends BaseDO {
47 */ 53 */
48 private Integer tradeGivePoint; 54 private Integer tradeGivePoint;
49 55
  56 + // ========== 分销相关 ==========
  57 +
  58 + /**
  59 + * 是否启用分佣
  60 + */
  61 + private Boolean brokerageEnabled;
  62 + /**
  63 + * 分佣模式
  64 + *
  65 + * 枚举 {@link BrokerageEnabledConditionEnum 对应的类}
  66 + */
  67 + private Integer brokerageEnabledCondition;
  68 + /**
  69 + * 分销关系绑定模式
  70 + *
  71 + * 枚举 {@link BrokerageBindModeEnum 对应的类}
  72 + */
  73 + private Integer brokerageBindMode;
  74 + /**
  75 + * 分销海报图地址数组
  76 + */
  77 + @TableField(typeHandler = JacksonTypeHandler.class)
  78 + private List<String> brokeragePostUrls;
  79 + /**
  80 + * 一级返佣比例
  81 + */
  82 + private Integer brokerageFirstPercent;
  83 + /**
  84 + * 二级返佣比例
  85 + */
  86 + private Integer brokerageSecondPercent;
  87 + /**
  88 + * 用户提现最低金额
  89 + */
  90 + private Integer brokerageWithdrawMinPrice;
  91 + /**
  92 + * 提现银行
  93 + */
  94 + @TableField(typeHandler = IntegerListTypeHandler.class)
  95 + private List<Integer> brokerageBankNames;
  96 + /**
  97 + * 佣金冻结时间(天)
  98 + */
  99 + private Integer brokerageFrozenDays;
  100 + /**
  101 + * 提现方式
  102 + *
  103 + * 枚举 {@link BrokerageWithdrawTypeEnum 对应的类}
  104 + */
  105 + @TableField(typeHandler = IntegerListTypeHandler.class)
  106 + private List<Integer> brokerageWithdrawType;
  107 +
50 } 108 }
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 { @@ -136,4 +136,31 @@ public class MemberUserDO extends TenantBaseDO {
136 */ 136 */
137 private Long groupId; 137 private Long groupId;
138 138
  139 + // ========== 分销相关 ==========
  140 +
  141 + /**
  142 + * 推广员编号
  143 + */
  144 + private Long brokerageUserId;
  145 + /**
  146 + * 推广员绑定时间
  147 + */
  148 + private LocalDateTime brokerageBindTime;
  149 + /**
  150 + * 是否成为推广员
  151 + */
  152 + private Boolean brokerageEnabled;
  153 + /**
  154 + * 成为分销员时间
  155 + */
  156 + private LocalDateTime brokerageTime;
  157 + /**
  158 + * 可用佣金
  159 + */
  160 + private Integer brokeragePrice;
  161 + /**
  162 + * 冻结佣金
  163 + */
  164 + private Integer frozenBrokeragePrice;
  165 +
139 } 166 }
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/brokerage/record/MemberBrokerageRecordMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.dal.mysql.brokerage.record;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
  7 +import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +/**
  11 + * 佣金记录 Mapper
  12 + *
  13 + * @author owen
  14 + */
  15 +@Mapper
  16 +public interface MemberBrokerageRecordMapper extends BaseMapperX<MemberBrokerageRecordDO> {
  17 +
  18 + default PageResult<MemberBrokerageRecordDO> selectPage(MemberBrokerageRecordPageReqVO reqVO) {
  19 + return selectPage(reqVO, new LambdaQueryWrapperX<MemberBrokerageRecordDO>()
  20 + .eqIfPresent(MemberBrokerageRecordDO::getUserId, reqVO.getUserId())
  21 + .eqIfPresent(MemberBrokerageRecordDO::getBizType, reqVO.getBizType())
  22 + .eqIfPresent(MemberBrokerageRecordDO::getStatus, reqVO.getStatus())
  23 + .betweenIfPresent(MemberBrokerageRecordDO::getCreateTime, reqVO.getCreateTime())
  24 + .orderByDesc(MemberBrokerageRecordDO::getId));
  25 + }
  26 +
  27 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java
1 package cn.iocoder.yudao.module.member.dal.mysql.user; 1 package cn.iocoder.yudao.module.member.dal.mysql.user;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 7 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
7 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 8 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
8 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO; 9 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
9 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; 10 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
  11 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
10 import org.apache.ibatis.annotations.Mapper; 12 import org.apache.ibatis.annotations.Mapper;
11 13
12 import java.util.List; 14 import java.util.List;
@@ -62,4 +64,32 @@ public interface MemberUserMapper extends BaseMapperX&lt;MemberUserDO&gt; { @@ -62,4 +64,32 @@ public interface MemberUserMapper extends BaseMapperX&lt;MemberUserDO&gt; {
62 .apply("FIND_IN_SET({0}, tag_ids)", tagId)); 64 .apply("FIND_IN_SET({0}, tag_ids)", tagId));
63 } 65 }
64 66
  67 + /**
  68 + * 更新用户可用佣金(增加)
  69 + *
  70 + * @param id 用户编号
  71 + * @param incrCount 增加佣金(正数)
  72 + */
  73 + default void updateBrokeragePriceIncr(Long id, int incrCount) {
  74 + Assert.isTrue(incrCount > 0);
  75 + LambdaUpdateWrapper<MemberUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<MemberUserDO>()
  76 + .setSql(" brokerage_price = brokerage_price + " + incrCount)
  77 + .eq(MemberUserDO::getId, id);
  78 + update(null, lambdaUpdateWrapper);
  79 + }
  80 +
  81 + /**
  82 + * 更新用户冻结佣金(增加)
  83 + *
  84 + * @param id 用户编号
  85 + * @param incrCount 增加冻结佣金(正数)
  86 + */
  87 + default void updateFrozenBrokeragePriceIncr(Long id, int incrCount) {
  88 + Assert.isTrue(incrCount > 0);
  89 + LambdaUpdateWrapper<MemberUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<MemberUserDO>()
  90 + .setSql(" frozen_brokerage_price = frozen_brokerage_price + " + incrCount)
  91 + .eq(MemberUserDO::getId, id);
  92 + update(null, lambdaUpdateWrapper);
  93 + }
  94 +
65 } 95 }
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.service.brokerage.record;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
  5 +import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
  6 +import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * 佣金记录 Service 接口
  12 + *
  13 + * @author owen
  14 + */
  15 +public interface MemberBrokerageRecordService {
  16 +
  17 + /**
  18 + * 获得佣金记录
  19 + *
  20 + * @param id 编号
  21 + * @return 佣金记录
  22 + */
  23 + MemberBrokerageRecordDO getMemberBrokerageRecord(Integer id);
  24 +
  25 + /**
  26 + * 获得佣金记录分页
  27 + *
  28 + * @param pageReqVO 分页查询
  29 + * @return 佣金记录分页
  30 + */
  31 + PageResult<MemberBrokerageRecordDO> getMemberBrokerageRecordPage(MemberBrokerageRecordPageReqVO pageReqVO);
  32 +
  33 + /**
  34 + * 增加佣金
  35 + *
  36 + * @param userId 会员ID
  37 + * @param list 请求参数列表
  38 + */
  39 + void addBrokerage(Long userId, List<BrokerageAddReqDTO> list);
  40 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.service.brokerage.record;
  2 +
  3 +import cn.hutool.core.util.BooleanUtil;
  4 +import cn.hutool.core.util.NumberUtil;
  5 +import cn.hutool.core.util.ObjectUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
  8 +import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
  9 +import cn.iocoder.yudao.module.member.convert.brokerage.record.MemberBrokerageRecordConvert;
  10 +import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
  11 +import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
  12 +import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
  13 +import cn.iocoder.yudao.module.member.dal.mysql.brokerage.record.MemberBrokerageRecordMapper;
  14 +import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
  15 +import cn.iocoder.yudao.module.member.service.user.MemberUserService;
  16 +import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.stereotype.Service;
  18 +import org.springframework.validation.annotation.Validated;
  19 +
  20 +import javax.annotation.Resource;
  21 +import java.math.RoundingMode;
  22 +import java.time.LocalDateTime;
  23 +import java.util.ArrayList;
  24 +import java.util.List;
  25 +import java.util.function.Function;
  26 +
  27 +/**
  28 + * 佣金记录 Service 实现类
  29 + *
  30 + * @author owen
  31 + */
  32 +@Slf4j
  33 +@Service
  34 +@Validated
  35 +public class MemberBrokerageRecordServiceImpl implements MemberBrokerageRecordService {
  36 +
  37 + @Resource
  38 + private MemberBrokerageRecordMapper memberBrokerageRecordMapper;
  39 + @Resource
  40 + private MemberPointConfigService memberConfigService;
  41 + @Resource
  42 + private MemberUserService memberUserService;
  43 +
  44 + @Override
  45 + public MemberBrokerageRecordDO getMemberBrokerageRecord(Integer id) {
  46 + return memberBrokerageRecordMapper.selectById(id);
  47 + }
  48 +
  49 + @Override
  50 + public PageResult<MemberBrokerageRecordDO> getMemberBrokerageRecordPage(MemberBrokerageRecordPageReqVO pageReqVO) {
  51 + return memberBrokerageRecordMapper.selectPage(pageReqVO);
  52 + }
  53 +
  54 + @Override
  55 + public void addBrokerage(Long buyerId, List<BrokerageAddReqDTO> list) {
  56 + MemberPointConfigDO memberConfig = memberConfigService.getPointConfig();
  57 + // 0 未启用分销功能
  58 + if (memberConfig == null || !BooleanUtil.isTrue(memberConfig.getBrokerageEnabled())) {
  59 + log.warn("[addBrokerage][增加佣金失败:brokerageEnabled 未配置,buyerId({})", buyerId);
  60 + return;
  61 + }
  62 +
  63 + // 1.1 获得一级推广人
  64 + MemberUserDO firstUser = memberUserService.getBrokerageUser(buyerId);
  65 + if (firstUser == null || !BooleanUtil.isTrue(firstUser.getBrokerageEnabled())) {
  66 + return;
  67 + }
  68 +
  69 + // 1.2 计算一级分佣
  70 + addBrokerage(firstUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageFirstPercent(), BrokerageAddReqDTO::getSkuFirstBrokeragePrice);
  71 +
  72 +
  73 + // 2.1 获得二级推广员
  74 + MemberUserDO secondUser = memberUserService.getUser(firstUser.getBrokerageUserId());
  75 + if (secondUser == null || !BooleanUtil.isTrue(secondUser.getBrokerageEnabled())) {
  76 + return;
  77 + }
  78 +
  79 + // 2.2 计算二级分佣
  80 + addBrokerage(secondUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageSecondPercent(), BrokerageAddReqDTO::getSkuSecondBrokeragePrice);
  81 + }
  82 +
  83 + /**
  84 + * 计算佣金
  85 + *
  86 + * @param payPrice 订单支付金额
  87 + * @param percent 商品 SKU 设置的佣金
  88 + * @param skuBrokeragePrice 商品的佣金
  89 + * @return 佣金
  90 + */
  91 + int calculateBrokerage(Integer payPrice, Integer percent, Integer skuBrokeragePrice) {
  92 + // 1. 优先使用商品 SKU 设置的佣金
  93 + if (skuBrokeragePrice != null && skuBrokeragePrice > 0) {
  94 + return ObjectUtil.defaultIfNull(skuBrokeragePrice, 0);
  95 + }
  96 +
  97 + // 2. 根据订单支付金额计算佣金
  98 + if (payPrice != null && payPrice > 0 && percent != null && percent > 0) {
  99 + return NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue();
  100 + }
  101 +
  102 + return 0;
  103 + }
  104 +
  105 + /**
  106 + * 增加用户佣金
  107 + *
  108 + * @param user 用户
  109 + * @param list 佣金增加参数列表
  110 + * @param brokerageFrozenDays 冻结天数
  111 + * @param brokeragePercent 佣金比例
  112 + * @param skuBrokeragePriceFun 商品 SKU 设置的佣金
  113 + */
  114 + private void addBrokerage(MemberUserDO user, List<BrokerageAddReqDTO> list, Integer brokerageFrozenDays,
  115 + Integer brokeragePercent, Function<BrokerageAddReqDTO, Integer> skuBrokeragePriceFun) {
  116 + // 处理冻结时间
  117 + brokerageFrozenDays = ObjectUtil.defaultIfNull(brokerageFrozenDays, 0);
  118 + LocalDateTime unfreezeTime = null;
  119 + if (brokerageFrozenDays > 0) {
  120 + unfreezeTime = LocalDateTime.now().plusDays(brokerageFrozenDays);
  121 + }
  122 +
  123 + // 计算分佣
  124 + int totalBrokerage = 0;
  125 + List<MemberBrokerageRecordDO> records = new ArrayList<>();
  126 + for (BrokerageAddReqDTO dto : list) {
  127 + int brokeragePerItem = calculateBrokerage(dto.getPayPrice(), brokeragePercent, skuBrokeragePriceFun.apply(dto));
  128 + if (brokeragePerItem > 0) {
  129 + int brokerage = brokeragePerItem * dto.getCount();
  130 + records.add(MemberBrokerageRecordConvert.INSTANCE.convert(user, dto.getBizId(), brokerageFrozenDays, brokerage, unfreezeTime));
  131 + totalBrokerage += brokerage;
  132 + }
  133 + }
  134 +
  135 + if (records.isEmpty()) {
  136 + return;
  137 + }
  138 +
  139 + // 保存佣金记录
  140 + memberBrokerageRecordMapper.insertBatch(records);
  141 +
  142 + if (brokerageFrozenDays > 0) {
  143 + // 更新用户冻结佣金
  144 + memberUserService.updateUserFrozenBrokeragePrice(user.getId(), totalBrokerage);
  145 + } else {
  146 + // 更新用户可用佣金
  147 + memberUserService.updateUserBrokeragePrice(user.getId(), totalBrokerage);
  148 + }
  149 + }
  150 +
  151 +}
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 { @@ -166,4 +166,28 @@ public interface MemberUserService {
166 * @param point 积分数量 166 * @param point 积分数量
167 */ 167 */
168 void updateUserPoint(Long userId, Integer point); 168 void updateUserPoint(Long userId, Integer point);
  169 +
  170 + /**
  171 + * 获得用户的推广人
  172 + *
  173 + * @param id 用户编号
  174 + * @return 用户的推广人
  175 + */
  176 + MemberUserDO getBrokerageUser(Long id);
  177 +
  178 + /**
  179 + * 增加用户佣金
  180 + *
  181 + * @param id 用户编号
  182 + * @param brokeragePrice 用户可用佣金
  183 + */
  184 + void updateUserBrokeragePrice(Long id, int brokeragePrice);
  185 +
  186 + /**
  187 + * 增加用户佣金
  188 + *
  189 + * @param id 用户编号
  190 + * @param frozenBrokeragePrice 用户冻结佣金
  191 + */
  192 + void updateUserFrozenBrokeragePrice(Long id, int frozenBrokeragePrice);
169 } 193 }
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; @@ -30,6 +30,7 @@ import javax.validation.Valid;
30 import java.time.LocalDateTime; 30 import java.time.LocalDateTime;
31 import java.util.Collection; 31 import java.util.Collection;
32 import java.util.List; 32 import java.util.List;
  33 +import java.util.Optional;
33 34
34 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
35 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; 36 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
@@ -259,4 +260,25 @@ public class MemberUserServiceImpl implements MemberUserService { @@ -259,4 +260,25 @@ public class MemberUserServiceImpl implements MemberUserService {
259 memberUserMapper.updateById(new MemberUserDO().setId(userId).setPoint(point)); 260 memberUserMapper.updateById(new MemberUserDO().setId(userId).setPoint(point));
260 } 261 }
261 262
  263 + @Override
  264 + public MemberUserDO getBrokerageUser(Long id) {
  265 + return Optional.ofNullable(id)
  266 + .map(this::getUser)
  267 + .map(MemberUserDO::getBrokerageUserId)
  268 + .map(this::getUser)
  269 + .orElse(null);
  270 + }
  271 +
  272 + @Override
  273 + @Transactional(rollbackFor = Exception.class)
  274 + public void updateUserBrokeragePrice(Long id, int brokeragePrice) {
  275 + memberUserMapper.updateBrokeragePriceIncr(id, brokeragePrice);
  276 + }
  277 +
  278 + @Override
  279 + @Transactional(rollbackFor = Exception.class)
  280 + public void updateUserFrozenBrokeragePrice(Long id, int frozenBrokeragePrice) {
  281 + memberUserMapper.updateFrozenBrokeragePriceIncr(id, frozenBrokeragePrice);
  282 + }
  283 +
262 } 284 }
yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.service.brokerage.record;
  2 +
  3 +import cn.hutool.core.util.NumberUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  6 +import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
  7 +import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
  8 +import cn.iocoder.yudao.module.member.dal.mysql.brokerage.record.MemberBrokerageRecordMapper;
  9 +import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
  10 +import cn.iocoder.yudao.module.member.service.user.MemberUserService;
  11 +import org.junit.jupiter.api.Disabled;
  12 +import org.junit.jupiter.api.Test;
  13 +import org.springframework.boot.test.mock.mockito.MockBean;
  14 +import org.springframework.context.annotation.Import;
  15 +
  16 +import javax.annotation.Resource;
  17 +import java.math.RoundingMode;
  18 +
  19 +import static cn.hutool.core.util.RandomUtil.randomEle;
  20 +import static cn.hutool.core.util.RandomUtil.randomInt;
  21 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
  22 +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
  23 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  24 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomInteger;
  25 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  26 +import static org.junit.jupiter.api.Assertions.assertEquals;
  27 +
  28 +/**
  29 + * {@link MemberBrokerageRecordServiceImpl} 的单元测试类
  30 + *
  31 + * @author owen
  32 + */
  33 +@Import(MemberBrokerageRecordServiceImpl.class)
  34 +public class MemberBrokerageRecordServiceImplTest extends BaseDbUnitTest {
  35 +
  36 + @Resource
  37 + private MemberBrokerageRecordServiceImpl memberBrokerageRecordService;
  38 + @Resource
  39 + private MemberBrokerageRecordMapper memberBrokerageRecordMapper;
  40 +
  41 + @MockBean
  42 + private MemberPointConfigService memberPointConfigService;
  43 + @MockBean
  44 + private MemberUserService memberUserService;
  45 +
  46 + @Test
  47 + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
  48 + public void testGetMemberBrokerageRecordPage() {
  49 + // mock 数据
  50 + MemberBrokerageRecordDO dbMemberBrokerageRecord = randomPojo(MemberBrokerageRecordDO.class, o -> { // 等会查询到
  51 + o.setUserId(null);
  52 + o.setBizType(null);
  53 + o.setStatus(null);
  54 + o.setCreateTime(null);
  55 + });
  56 + memberBrokerageRecordMapper.insert(dbMemberBrokerageRecord);
  57 + // 测试 userId 不匹配
  58 + memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setUserId(null)));
  59 + // 测试 bizType 不匹配
  60 + memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setBizType(null)));
  61 + // 测试 status 不匹配
  62 + memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setStatus(null)));
  63 + // 测试 createTime 不匹配
  64 + memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setCreateTime(null)));
  65 + // 准备参数
  66 + MemberBrokerageRecordPageReqVO reqVO = new MemberBrokerageRecordPageReqVO();
  67 + reqVO.setUserId(null);
  68 + reqVO.setBizType(null);
  69 + reqVO.setStatus(null);
  70 + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
  71 +
  72 + // 调用
  73 + PageResult<MemberBrokerageRecordDO> pageResult = memberBrokerageRecordService.getMemberBrokerageRecordPage(reqVO);
  74 + // 断言
  75 + assertEquals(1, pageResult.getTotal());
  76 + assertEquals(1, pageResult.getList().size());
  77 + assertPojoEquals(dbMemberBrokerageRecord, pageResult.getList().get(0));
  78 + }
  79 +
  80 + @Test
  81 + public void testCalculateBrokerage_useSkuBrokeragePrice() {
  82 + // mock 数据
  83 + Integer payPrice = randomInteger();
  84 + Integer percent = randomInt(1, 101);
  85 + Integer skuBrokeragePrice = randomInt();
  86 + // 调用
  87 + int brokerage = memberBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
  88 + // 断言
  89 + assertEquals(brokerage, skuBrokeragePrice);
  90 + }
  91 +
  92 + @Test
  93 + public void testCalculateBrokerage_usePercent() {
  94 + // mock 数据
  95 + Integer payPrice = randomInteger();
  96 + Integer percent = randomInt(1, 101);
  97 + Integer skuBrokeragePrice = randomEle(new Integer[]{0, null});
  98 + System.out.println("skuBrokeragePrice=" + skuBrokeragePrice);
  99 + // 调用
  100 + int brokerage = memberBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
  101 + // 断言
  102 + assertEquals(brokerage, NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue());
  103 + }
  104 +
  105 + @Test
  106 + public void testCalculateBrokerage_equalsZero() {
  107 + // mock 数据
  108 + Integer payPrice = null;
  109 + Integer percent = null;
  110 + Integer skuBrokeragePrice = null;
  111 + // 调用
  112 + int brokerage = memberBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
  113 + // 断言
  114 + assertEquals(brokerage, 0);
  115 + }
  116 +}
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/clean.sql
@@ -2,4 +2,5 @@ DELETE FROM &quot;member_user&quot;; @@ -2,4 +2,5 @@ DELETE FROM &quot;member_user&quot;;
2 DELETE FROM "member_address"; 2 DELETE FROM "member_address";
3 DELETE FROM "member_tag"; 3 DELETE FROM "member_tag";
4 DELETE FROM "member_level"; 4 DELETE FROM "member_level";
5 -DELETE FROM "member_group";  
6 \ No newline at end of file 5 \ No newline at end of file
  6 +DELETE FROM "member_group";
  7 +DELETE FROM "member_brokerage_record";
7 \ No newline at end of file 8 \ No newline at end of file
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql
@@ -89,4 +89,25 @@ CREATE TABLE IF NOT EXISTS &quot;member_group&quot; @@ -89,4 +89,25 @@ CREATE TABLE IF NOT EXISTS &quot;member_group&quot;
89 "deleted" bit NOT NULL DEFAULT FALSE, 89 "deleted" bit NOT NULL DEFAULT FALSE,
90 "tenant_id" bigint not null default '0', 90 "tenant_id" bigint not null default '0',
91 PRIMARY KEY ("id") 91 PRIMARY KEY ("id")
92 -) COMMENT '用户分组';  
93 \ No newline at end of file 92 \ No newline at end of file
  93 +) COMMENT '用户分组';
  94 +CREATE TABLE IF NOT EXISTS "member_brokerage_record"
  95 +(
  96 + "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  97 + "user_id" bigint NOT NULL,
  98 + "biz_id" varchar NOT NULL,
  99 + "biz_type" varchar NOT NULL,
  100 + "title" varchar NOT NULL,
  101 + "price" int NOT NULL,
  102 + "total_price" int NOT NULL,
  103 + "description" varchar NOT NULL,
  104 + "status" varchar NOT NULL,
  105 + "frozen_days" int NOT NULL,
  106 + "unfreeze_time" varchar,
  107 + "creator" varchar DEFAULT '',
  108 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  109 + "updater" varchar DEFAULT '',
  110 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  111 + "deleted" bit NOT NULL DEFAULT FALSE,
  112 + "tenant_id" bigint not null default '0',
  113 + PRIMARY KEY ("id")
  114 +) COMMENT '佣金记录';
94 \ No newline at end of file 115 \ No newline at end of file